Django Error: 'DetailView' object has no attribute '_meta'

Question:

This has floored me. I’m building a model with Django & REST API, and I’m having trouble rendering the DetailView for individual cars to the browser. The ListView works fine, but I’ll include the code too since they are interlinked.

In particular, I can’t get the get_object() function to work properly.

Here’s the first approach I used

views.py

class CarDetailView(generics.RetrieveUpdateDestroyAPIView):
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'car-detail.html'
    queryset = Car.objects.all()
    lookup_field = Car.id
    
    @action(detail=True, renderer_classes=[TemplateHTMLRenderer])
    def get(self, request, *args, **kwargs):
        car = self.get_object()
        return Response({
            'car': car,
        })

While the page rendered properly with no errors, none of the template tags worked. Only {{ car.model }}, which returned None.

So I changed the code to use a self.get_object()

 class CarDetailView(generics.GenericAPIView):
        renderer_classes = [TemplateHTMLRenderer]
        template_name = 'car-detail.html'
    
        queryset = Car.objects.all()
        lookup_field = Car.id
    
        def get(self, request, id):
            serializer_class = CarSerializer
            car = self.get_object()
            id = Car.id
    
            return Response({
                'car': car,
            })

but that raises the Error:

"AttributeError at /rentals/car/43c9b98d-9f2d-473f-9c34-7b2e25630278
‘CarDetailView’ object has no attribute ‘_meta’"

Can you help with this? I’ve looked through most of the previous questions here (that’s where I learned to pass the self into the get function etc…) but I’m still stuck.

Here’s the code:

views.py

class CarListView(generics.ListCreateAPIView):
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'car-list.html'

    def get(self, request):
        car_list = Car.objects.all()
        serializer_class = CarSerializer

        return Response({
            'car_list': car_list,
        })

class CarDetailView(generics.GenericAPIView):
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'car-detail.html'

    queryset = Car.objects.all()
    lookup_field = Car.id

    def get(self, request, id):
        serializer_class = CarSerializer
        car = self.get_object()
        id = Car.id

        return Response({
            'car': car,
        })

serializers.py

class CarSerializer(serializers.ModelSerializer):
    url = serializers.HyperlinkedIdentityField(view_name='rental:car-detail', format='html')

    class Meta:
        model = Car
        fields = ['url', 'id', 'manufacturer', 'model', 'owner', 'color', 'year']

models.py

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.SET_NULL, null=True)
    model = models.ForeignKey(CarModel, on_delete=models.SET_NULL, null=True)
    # Model year. Validator ensures that no model year  can be set in the future. 
    year = models.IntegerField(validators=[MaxValueValidator(int(datetime.date.today().year) + 1)], null=True, default=2022)

    ...
    Other fields for drivetrain, engine etc
    ...

    # Fields for individual car in database
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text="Automatically generated unique ID. Do not change.")
    mileage = models.IntegerField(null=True)
    
    insurance = models.ForeignKey(Insurance, on_delete=models.RESTRICT, null=True)

    daily_rate = models.IntegerField(default=3500, help_text="Daily rate in Kenyan Shillings")


    def __str__(self):
        return f'{ self.manufacturer } { self.model }'

    @property
    def insurance_expired(self):
        """Determines if insurance is expired based on due date and current date."""
        return bool(self.insurance.expiry_date and datetime.date.today() > self.insurance.expiry_date)

    class Meta:
        ordering = []
        permissions = (("can_change_availability", "Set car as rented"),)

    def get_absolute_url(self):
        return reverse('rental:car-detail', args=[str(self.id)])

urls.py

from django.urls import path, include 
from . import views

app_name = 'rental'

# API endpoints
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('cars/', views.CarListView.as_view(), name='car-list'),
    path('car/<uuid:id>', views.CarDetailView.as_view(), name='car-detail'),
] 

Error Code

AttributeError at /rentals/car/43c9b98d-9f2d-473f-9c34-7b2e25630278
'CarDetailView' object has no attribute '_meta'
Request Method: GET
Request URL:    http://localhost:8000/rentals/car/43c9b98d-9f2d-473f-9c34-7b2e25630278
Django Version: 4.1.3
Exception Type: AttributeError
Exception Value:    
'CarDetailView' object has no attribute '_meta'
Asked By: Rene

||

Answers:

Usually you should not implement the get() method yourself, this is usually the task of the Django (API) views that will then pass it to the right renderer and template.

class CarDetailView(generics.RetrieveAPIView):
    renderer_classes = [TemplateHTMLRenderer]
    template_name = 'car-detail.html'
    serializer_class = CarSerializer
    queryset = Car.objects.all()

In the template you can then use the fields of the car, so: {{ url }}, {{ model }}, {{ owner }}, etc.

If you however plan to render a template, it might make more sense to work with a DetailView [Django-doc] instead:

from django.views.generic.detail import DetailView


class CarDetailView(DetailView):
    model = Car
    template_name = 'car-detail.html'

In the template, you can then use {{ object.url }}, {{ object.model }}, etc. and you can also "follow" relations.

Answered By: Willem Van Onsem
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.