Search code examples
pythondjangodjango-modelsforeign-keysdjango-filter

How to use django-filter package for foreign key fields in Django?


Hi all!

New in Django, and confused, help is appreciated! I've created a table, , thanks to a stackoverflow users, like:

Organization Total amount of appeals Amount of written form appeals Amount of oral form appeals
Organization 1 3 1 2
Organization 2 2 1 1

Have three models:

class Organization(models.Model):
    organization_name = models.CharField(max_length=50)


class AppealForm(models.Model):
    form_name = models.CharField(max_length=50)


class Appeal(models.Model):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
    appeal_form = models.ForeignKey(AppealForm, on_delete=models.CASCADE)
    applicant_name = models.CharField(max_length=150)
    appeal_date = models.DateField()

Objects of Organization model:

organization_name
Organization 1
Organization 2

Objects of AppealForm model:

form_name
In written form
In oral form

Objects of Appeal model:

organization appeal_form applicant_name
Organization 1 In written form First and Last name
Organization 1 In oral form First and Last name
Organization 1 In oral form First and Last name
Organization 2 In written form First and Last name
Organization 2 In oral form First and Last name

In the function of views.py file, I've created a query to render, like:

from django.db.models import Count, Q

organizations = Organization.objects.annotate(
).annotate(
    total=Count('appeal'),
    total_written=Count('appeal', filter=Q(appeal__appeal_form__form_name='in written form')),
    total_oral=Count('appeal', filter=Q('appeal__appeal_form__form_name='in oral form'))
)

And now I want to filter table contents by AppealForm model and date of appeals (appeal_date field of Appeal model). Case: User opens a table and from search bar above the table chooses which appeal form and/or the range of dates to see.

Question: How to filter the query which is above in views.py using django-filter package?


Solution

  • The most general way to define a complicated filter is to use the method argument. I can't say I completely understand your problem, but you can apply any filter for which you can dream up a queryset in this way. In outline:

    import django-filters as DF
    
    class SomeFilters( DF.FilterSet):
    
        name = DF.xxxFilter( method='my_method', field_name='object_field', label='whatever',  ...)
        ...
    
        def my_method( self, qs, name, value):
            # in here you create a new more restrictive queryset based on qs
            # to implement your filter, and return it.
            # name is the field name. Note, you don't have to use or follow it
            # value is the value that the user typed
    
            qs = qs.filter( ...) # or .exclude, or complicated stuff  
            return qs
    

    Here's a fairly simple method that I wrote to create an annotation with the value of a field stripped of spaces, and then to do a text-contains filter on that.

        def filter_array_desc( self, qs, name, value):
            value = value.replace(' ','')
            qs = qs.annotate(
                arr_d_nospaces = Replace( 'array_desc', Value(' '), Value('')) # delete all spaces
            ).filter(
                arr_d_nospaces__icontains = value )
            return qs
    

    Here's a general one that can be applied to any field through a ChoiceFilter to filter whether the field is blank or not:

        YESNO = (('Y','Yes'), ('N','No'))
        marr_b = FD.ChoiceFilter( field_name='marr', label='M_array is blank',  method='filter_blank_yesno', 
                                  choices=YESNO, empty_label="Don't Care" )
        ...
    
        def filter_blank_yesno( self, qs, name, value):
            if value=="Y":
                return qs.filter(**{ name:'' })
            elif value=="N":
                return qs.exclude( **{ name:'' })
            raise ValueError(f'filter_blank_yesno received value="{value}" which is neither "Y" nor "N"')
    

    Hope this helps. You will basically be filtering by following relationships between your models, using double-underscores to move between models, and possibly annotating and filtering on the anotation, or doing things with Q objects and suchlike.