How can I filter a ManyToMany field with django-filter
I would like to display an input field on a template where you can filter Student to get these results:
# models.py
class Student(models.Model):
name = models.CharField(...)
languages = models.ManyToManyField(Language)
class Language(models.Model):
language = models.CharField(...) # English / German / ...
level = models.CharField(...) # B1 / B2 / C1 / ...
#filters.py
import django_filters as filters
from .models import Employee
class EmployeeFilter(filters.FilterSet):
class Meta:
model = Employee
fields = ['name', 'languages']
How should I modify the EmployeeFilter to make it ready to filter the Students according to their spoken languages?
I tried declaring a class variable named languages like so:
class EmployeeFilter(filters.FilterSet):
languages = filters.ModelChoiceFilter(
queryset = Languages.objects.all()
)
class Meta:
model = Employee
fields = ['name', 'languages']
but it did not work, the filter had no effect.
You can filter on the language
field of the languages
:
class EmployeeFilter(filters.FilterSet):
languages = filters.CharField(
field_name='languages__language', lookup_expr='iexact'
)
class Meta:
model = Employee
fields = ['name', 'languages']
This means that you thus can filter with English
or english
for example.
You could also use ModelMultipleChoiceFilter
for example:
class EmployeeFilter(filters.FilterSet):
languages = filters.ModelMultipleChoiceFilter(
field_name='languages__language',
to_field_name='language',
conjoined=True,
queryset=Language.objects.all()
)
class Meta:
model = Employee
fields = ['name', 'languages']
That being said, the modeling looks weird. One would expect that the level is part of the junction table, not the language itself, so:
from django.db import Models
class Student(models.Model):
# …
languages = models.ManyToManyField('Language', through='LanguageSkill')
class Language(models.Model):
language = models.CharField(max_length=128, unique=True)
class LanguageSkill(models.Model):
language = models.ForeignKey(Language, on_delete=models.CASCADE)
student = models.ForeignKey(Student, on_delete=models.CASCADE)
level = models.CharField(
max_length=2, choices=[('B1', 'B1'), ('B2', 'B2'), ('C1', 'C1')]
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=('language', 'student'), name='unique_lang_per_student'
)
]
You thus then add information in the combination of a student with a language what the skill level is.