Search code examples
djangoobjectmodeldjango-queryset

Django Model Manager


I have a Django Model where users can create objects and keep them private for a certain period of time.

class MyModel(models.Model):
    creator = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
    private_until = models.DateField(null=True, default=None, blank=True)
    objects = MyModelManager()

In my ListView, I want for visitors only the "non-private" objects to appear and for authenticated users the "non-private" objects plus their own private objects. So my Manager looks like this:

class MyModelManager(models.Manager):
    def include_his_private(self, user):
        if user.is_authenticated:
            include_his_private = super().get_queryset().filter(~Q(private_until__gt=datetime.date.today()))
            include_his_private |= super().get_queryset().filter(Q(creator=user))
            return include_his_private
        else:
            return super().get_queryset().filter(~Q(private_until__gt=datetime.date.today()))

    def get_queryset(self):
        return super().get_queryset().filter(~Q(private_until__gt=datetime.date.today()))

For my ListView this works great. But when I click on an object to get its DetailView, I get a 404 error right on "URL-Level". In my URLs I have:

    path('<slug:slug>', MyModelDetailView.as_view(), name='mymodel_detail'),

... and somehow, Django checks already in advance the whole slug against the per Manager allowed slugs, before I have the chance to pass the user in. What is the way to solve my problem? Any help is appreciated. Thanks in advance!

EDIT: My DetailView looks like this:

class MyModelDetailView(DetailView):
     model = MyModel     
     template_name = 'mymodel_detail.html'      

     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
         slug = self.kwargs['slug']
         if self.request.user.is_authenticated:
            obj = MyModel.objects.include_his_private(self.request.user).get(slug=slug)       
         else:
             obj = MyModel.objects.get(slug=slug)

Solution

  • A DetailView [Django-doc] will use the .get_queryset(…) [Django-doc] to obtain a queryset and in the .get_object(…) method [Django-doc], it will filter on the pk and/or slug automatically, if a path contains such URL parameters. This is thus done before you run the custom logic in the get_context_data.

    You thus should override the get_queryset method:

    class MyModelDetailView(DetailView):
         model = MyModel     
         template_name = 'mymodel_detail.html'
    
        def get_queryset(self, *args, **kwargs):
            return MyModel.objects.include_his_private(
                self.request.user
            )