Search code examples
djangomixinsdjango-class-based-views

Dynamic field names in Django Mixin


I have these class based ListView's which I would like to filter by date. I have a simple mixin to display the filterform, which works great:

class MonthYearFormMixin(object):
    def get_context_data(self, **kwargs):
        context = super(MonthYearFormMixin, self).get_context_data(**kwargs)
        context['monthyearform'] = MonthYearForm(self.request.GET)
        return context

I would like to extend the functionality of this mixin to include the queryset filtering, but my models have different date fields that need to be filtered on, one might be start_date, another might be invoice_date. Of course someone might say, "rename them all 'date'", but that's not representative of my models and furthermore, I might have a model with start_date and end_date, but only want to filter on start_date. Here are my views:

class SentList(MonthYearFormMixin, ListView):
    model = Sent
    context_object_name = 'object'
    template_name = 'sent_list.html'

    def get_queryset(self):
        qs = self.model.objects.all()
        if 'month' in self.request.GET:
            if int(self.request.GET['month']) > 0:
                qs = qs.filter(start_date__month=self.request.GET['month'])
        if 'year' in self.request.GET:
            if int(self.request.GET['year']) > 0:
                qs = qs.filter(start_date__year=self.request.GET['year'])
        return qs

class ReceivedList(MonthYearFormMixin, ListView):
    model = Received
    context_object_name = 'object'
    template_name = 'received_list.html'

    def get_queryset(self):
        qs = self.model.objects.all()
        if 'month' in self.request.GET:
            if int(self.request.GET['month']) > 0:
                qs = qs.filter(invoice_date__month=self.request.GET['month'])
        if 'year' in self.request.GET:
            if int(self.request.GET['year']) > 0:
                qs = qs.filter(invoice_date__year=self.request.GET['year'])
        return qs

The only thing that's different is the name of the date field, so it's frustrating to have to repeat this code.


Solution

  • Why not create a member variable called date_field_name where you store the name of the field names which should be processed by your QuerySet processor mixin?

    This list would be defined in the class which uses the mixin.

    Something like

    class MonthYearFormMixin(object):
        def get_context_data(self, **kwargs):
            context = super(MonthYearFormMixin, self).get_context_data(**kwargs)
            context['monthyearform'] = MonthYearForm(self.request.GET)
            return context
    
        def get_queryset(self):
            qs = self.model.objects.all()
            if 'month' in self.request.GET:
                if int(self.request.GET['month']) > 0:
                    kwargs = {('%s__month' % self.date_field_name): self.request.GET['month']}
                    qs = qs.filter(**kwargs)
            if 'year' in self.request.GET:
                if int(self.request.GET['year']) > 0:
                    kwargs = {('%s__year' % self.date_field_name): self.request.GET['year']}
                    qs = qs.filter(**kwargs)
        return qs
    

    your views would look like this:

    class SentList(MonthYearFormMixin, ListView):
        model = Sent
        context_object_name = 'object'
        template_name = 'sent_list.html'
        date_field_name = 'start_date'