Tadgh
Tadgh

Reputation: 2049

How to handle changing dates in Django Queries

I'm having an issue with a Django 1.6 project.

I have a view that reports a bunch of web logs. When the view is loaded, it gets all logs from the DB. However, the user has access to two datepickers to limit the from_date and to_date on the query. To accomplish this, I added a check in the get_queryset() to verify if any of those datepickers had been used. This feels really stupid and clunky, and I'm sure there's a better way to do it, but I'm not sure what it is. Here's my view code, let me know if theres anything I'm missing here.

class SiteUsersView(generic.ListView):
    """
    EX: localhost/reports/site/facebook
    """
    model = Cleanedlog
    template_name = "trafficreport/site_chunk_log.html"

    def get_context_data(self, **kwargs):
        context = super(SiteUsersView, self).get_context_data(**kwargs)
        context["url_chunk"] = self.kwargs["url_chunk"]
        return context

    def get_queryset(self):
        from_date = self.request.GET.get("fromDate")
        to_date = self.request.GET.get("toDate")
        if from_date is not None and to_date is not None:
            return self.from_to_date()
        elif from_date is not None and to_date is None:
            return self.from_date()
        elif to_date is not None and to_date is None:
            return self.to_date()
        else:
            return self.all_dates()


    def from_date(self, from_date):
        return Cleanedlog.objects.filter(dest_url__contains=self.kwargs["url_chunk"],
                                         time_received__gte=from_date).values('user__name').annotate(
                                         count=Sum('size')).order_by('-count')

    def to_date(self, to_date):
        return Cleanedlog.objects.filter(dest_url__contains=self.kwargs["url_chunk"],
                                         time_received__lte=to_date).values('user__name').annotate(
                                          count=Sum('size')).order_by('-count')

    def from_to_date(self, to_date, from_date):
        return Cleanedlog.objects.filter(dest_url__contains=self.kwargs["url_chunk"],
                                         time_received__gte=from_date,
                                         time_received__lte=to_date).values('user__name').annotate(
                                         count=Sum('size')).order_by('-count')

    def all_dates(self):
        return Cleanedlog.objects.filter(dest_url__contains=self.kwargs["url_chunk"]).values('user__name').annotate(
                                         count=Sum('size')).order_by('-count')

What's more is that I have other reports (Users/IPs) that follow the exact same format, just with a different model and marginally different queries. The datepickers and all the data presentation is in a higher level template, and that works well, but it feels really stupid to just duplicate all this code. Am I missing something obvious?

Thanks!

Upvotes: 0

Views: 110

Answers (1)

Peter DeGlopper
Peter DeGlopper

Reputation: 37364

I've solved problems like this in the past by inverting the logic and using exclude, eg:

return Cleanedlog.objects.filter(dest_url__contains=self.kwargs["url_chunk"]) \
           .exclude(time_received__lt=from_date).exclude(time_received__gt=to_date)

And then take values and annotate and count as desired.

Comparisons against None always fail, so nothing gets excluded unless the date is provided.

That's probably the simplest way to write it, but you can also take advantage of the ability to chain querysets without evaluating them:

base_qs = Cleanedlog.objects.filter(dest_url__contains=self.kwargs["url_chunk"])
if from_date is not None:
    base_qs = base_qs.filter(time_received_gte=from_date)
if to_date is not None:
    base_qs = base_qs.filter(time_received_lte=to_date)
return base_qs.values(# as above

That avoids putting the unnecessary None comparisons into the query at all.

Upvotes: 1

Related Questions