Search code examples
djangodjango-filter

Django-filter - group like values with different primary keys in form


My goal is to filter a model that refers to plants. Below is an abbreviated version of my model:

class Plant(models.Model):

    sku = models.CharField('SKU', max_length=14)
    name = models.CharField('Name', max_length=50)
    genus = models.ForeignKey(Genus, on_delete=models.SET_NULL, null=True, blank=True)

    class Meta:
        ordering = ['genus', 'name']

    def __str__(self):
        return self.name

My related model, Genus, is very basic with just two fields:

class Genus(models.Model):
    common = models.CharField('Common Genus', max_length=100)
    latin = models.CharField('Latin Genus', max_length=100)

    class Meta:
        ordering = ['common']
        verbose_name_plural = 'genera'

    def __str__(self):
        return self.common

The point here is that an entry for Genus will sometimes have the same value for latin. For example Cherry, Peach, and Almond are all prunus but each has its own entry.

{
    [
        'common': 'Cherry',
        'latin': 'Prunus'
    ],
    [
        'common': 'Almond',
        'latin': 'Prunus'
    ],
    [
        'common': 'Peach',
        'latin': 'Prunus'
    ]
}

My problem arises when I use django-filter to filter these values. I have a common name filter and a latin name filter. The common name filter is straightforward since the common names will always be unique, but the latin name might be common among many entries.

class LatinChoiceField(ModelChoiceField):
    def label_from_instance(self, obj):
        return obj.latin

class LatinFilter(django_filters.ModelChoiceFilter):
    field_class = LatinChoiceField

class ProductFilter(django_filters.FilterSet):
    genus__common = django_filters.ModelChoiceFilter(queryset=Genus.objects.all(), label='Genus')
    latin_q = Genus.objects.all().order_by('latin')
    genus__latin = LatinFilter(queryset=latin_q, label='Latin', field_name='genus')
    class Meta:
        model = Product
        fields = ['genus__common', 'genus__latin']

This provides me with mostly what I am after, but the problem is that the ModelChoiceFilter will repeat each value for latin names, giving me a select input which repeats "Prunus" many times and each one is only related to the primary key of the Genus model entry.

http://127.0.0.1:8000/plants/?genus__latin=7

Produces:

Almond

While:

http://127.0.0.1:8000/plants/?genus__latin=4

Produces:

Cherry

How can I group all the like latin values together either by name or a group of primary keys?

http://127.0.0.1:8000/plants/?genus__latin='Prunus'
http://127.0.0.1:8000/plants/?genus__latin=7,4,2

Should produce:

Almond, Cherry, Peach

Solution

  • It seems that what you're looking for is really the AllValuesFilter. With it, your code would look like this:

    class ProductFilter(django_filters.FilterSet):
        genus__common = django_filters.ModelChoiceFilter(queryset=Genus.objects.all(), label='Genus')
        genus__latin = AllValuesFilter(label='Latin')
        class Meta:
            model = Product
            fields = ['genus__common', 'genus__latin']