Search code examples
djangodjango-class-based-views

Django Class Based View: Deny access to future dates unless logged in


I have a Promotion model containing a published_date field. I would like to restrict access to future promotions to logged in users who are 'previewing' their promotion.

class Promotion(models.Model):
    title = models.CharField(max_length=100, blank=False, null=False)
    ...
    promotion_date = models.DateField(blank=False, null=True)

At the moment I'm running a simple Class Based DetailView:

class PromotionDetailView(generic.DetailView):
    model = Promotion
    context_object_name = 'book'
    template_name = 'PromoManager/book_detail.html'

But I am unsure how or when to intercept this request and add the condition for non logged in users to redirect if promotion_date is greater than today. I currently have simple_tag which returns a bootstrap alert highlighting future promotions but this is purely visual.

in_the_future = timezone.now().date()
if hasattr(book, 'promotion_date') and book.promotion_date > in_the_future:
    return ...appropriate message...

If I was doing it via Function Based Views I would apprach it with an if statement combined with a redirect. I considered the same approach via DetailView/TemplateView and get_context_data but this doesn't feel right.

How can I best intercept the request and redirect the page for non-logged in users if the promotion_date is in the future?


Solution

  • You can just filter it out in get_queryset:

    from django.db.models import Q
    from django.utils.timezone import now
    
    
    class PromotionDetailView(generic.DetailView):
        model = Promotion
        context_object_name = 'book'
        template_name = 'PromoManager/book_detail.html'
    
        def get_queryset(self, *args, **kwargs):
            qs = super().get_queryset(*args, **kwargs)
            if not self.request.user.is_authenticated:
                qs = qs.filter(Q(promotion_date=None) | Q(promotion_date__lte=now()))
            return qs

    this will thus limit the queryset to items that are not in the future, in case the user was logged in. The filtering also happens at the database side.

    You can handle the 404 by overriding the dispatch and thus perform a redirect in that case:

    from django.db.models import Q
    from django.http import Http404
    from django.shortcuts import redirect
    from django.utils.timezone import now
    
    
    class PromotionDetailView(generic.DetailView):
        model = Promotion
        context_object_name = 'book'
        template_name = 'PromoManager/book_detail.html'
    
        def get_queryset(self, *args, **kwargs):
            qs = super().get_queryset(*args, **kwargs)
            if not self.request.user.is_authenticated:
                qs = qs.filter(Q(promotion_date=None) | Q(promotion_date__lte=now()))
            return qs
    
        def dispatch(self, *args, **kwargs):
            try:
                return super().dispatch(*args, **kwargs)
            except Http404:
                return redirect('name-of-some-view')