djangodjango-viewshttp-status-code-404

Django DetailView without 404 redirect


I have a little news app with a DetailView class like the following:

class DetailView(LoginRequiredMixin,generic.DetailView):
    model = NewsItem
    template_name = 'news/detail.html'

    def get_object(self):
        obj = super().get_object()

        if self.request.user.is_superuser or obj.published:
            return obj

and an urls.py config like this:

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('<int:itemId>/publish', views.publish, name='publish'),
]

Now when i pass an invalid ID to the detail view it automatically redirects to 404. I wanted to know if it's possible to just pass an empty object to the DetailView instead. The template then would handle the 404 on its own.

Also:Even though it works so far I feel like the way i handled the permission requirements (overriding the get_object method) isn't the correct way/Django way to do things

Solution: i changed the overriding of get_object like this:

class DetailView(LoginRequiredMixin,generic.DetailView):
    model = NewsItem
    template_name = 'news/detail.html'

    def get_object(self):
        queryset = self.get_queryset()
        pk = self.kwargs.get(self.pk_url_kwarg)

        if pk is not None:
            queryset = queryset.filter(pk=pk)
        else:
            raise AttributeError("No article id provided")

        try:
            return queryset.get()
        except queryset.model.DoesNotExist:
            return None

it is basically the same get_object method as the original just without the check for a slug value, and if the queryset.get() call catches the queryset.model.DoesNotExist Exception it returns None


Solution

  • Classy CBVs, as usual, makes all clear.

    If you don't like 404 for a bad id, you need to override get_object to a greater extent than you have done, because it raises the exception Http404 for a bad id. The code in question is

     try:
        # Get the single item from the filtered queryset
        obj = queryset.get()
     except queryset.model.DoesNotExist:
        raise Http404(_("No %(verbose_name)s found matching the query") %
                      {'verbose_name': queryset.model._meta.verbose_name})
     return obj
    

    You could catch the exception when you call obj = super().get_object()

    from django.http import Http404
    
    def get_object(self, queryset=None):
        try:
            obj = super().get_object(queryset)
        except Http404:
            obj = NewsItem( ... ) # a dummy empty NewsItem
        return obj
    

    Or you could simply not call super() at all, and supply your own code to return an appropriate object. The empty object might exist in the database as a special entity (best created by a data migration), or it might (as here) be created on the fly and never saved. In either case it would have some distinguishing characteristic that the template can easily test.