djangodjango-modelsdjango-formsdjango-filtermodelchoicefield

Custom label for instance with django-filter


I have Observer model which extends User model via OneToOneRelation. I made filtering for model Checklist which has foreign key to Observer. This is the models.py code:

class Observer(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    rating = models.IntegerField(default=0)
    bird_ratings = models.ManyToManyField('BirdName', through='BirdRatings')

class Checklist(gismodels.Model):
    date = models.DateField()
    time_start = models.TimeField(null=True)
    time_end = models.TimeField(null=True)
    site_name = models.CharField(max_length=200)
    municipality = models.CharField(max_length=200)
    observer = models.ForeignKey(Observer, null=True, default='')
    other_observers = models.CharField(max_length=150, default='')
    note = models.CharField(max_length=2000)
    location_note = models.CharField(max_length=2000, default='')
    position = gismodels.PointField(null=True, srid=4326)
    created = models.DateTimeField(db_index=True, null=True)
    rating = models.IntegerField(default=0)

Then I made ChecklistFilter class in filters.py:

class ChecklistFilter(django_filters.FilterSet):
    date = DateFromToRangeFilter(label='Datum pozorování', widget=RangeWidget(attrs={'display': 'inline'}))
    #groups = django_filters.ModelMultipleChoiceFilter(queryset=User.objects.filter(), widget=forms.CheckboxSelectMultiple)
    created = DateFilter(label='Datum vložení')
    observer = ModelChoiceFilter(queryset=Observer.objects.filter(), label='Pozorovatel')
    municipality = CharFilter(label='Obec')
    oblast = CharFilter(label='Oblast')
    rating = NumberFilter(label='Hodnocení')

    class Meta:
        model = Checklist
        fields = ['date', 'created', 'observer', 'municipality', 'site_name', 'rating']

This is a part of my code from template, where I am using observer field:

<div class="col-sm-2">
    {{ filter.form.observer.label_tag }}
    {% render_field filter.form.observer class="form-control" %}
</div>

But the problem is that I dont't need get observer object but I need last_name from User model which is linked to Observer model. In forms I just extend ModelChoiceField and override label_from_instance() method. But this is not working here. Django-filter use this classes but overiding not working here. I tried this:

from .models import Checklist, Observer
from django.contrib.auth.models import User
import django_filters
from django_filters import DateFromToRangeFilter, DateFilter, CharFilter, NumberFilter, ModelChoiceFilter
from django_filters.widgets import RangeWidget
from django import forms


class ChecklistModelChoiceField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return obj.user.last_name

class ChecklistFilter(django_filters.FilterSet):
    date = DateFromToRangeFilter(label='Datum pozorování', widget=RangeWidget(attrs={'display': 'inline'}))
    #groups = django_filters.ModelMultipleChoiceFilter(queryset=User.objects.filter(), widget=forms.CheckboxSelectMultiple)
    created = DateFilter(label='Datum vložení')
    observer = ChecklistModelChoiceField(queryset=Observer.objects.filter(), label='Pozorovatel')
    municipality = CharFilter(label='Obec')
    oblast = CharFilter(label='Oblast')
    rating = NumberFilter(label='Hodnocení')

    class Meta:
        model = Checklist
        fields = ['date', 'created', 'observer', 'municipality', 'site_name', 'rating']

The choice field then on the website has observer object: enter image description here

EDIT
Yes, I tried both them. Then I tried:

class ChecklistModelChoiceField(django_filters.ModelChoiceFilter):
    def label_from_instance(self, obj):
        return obj.user.last_name

No error, but label_from_instance() is not used. Still Observer object in select.

I have made a mistake. ModelChoiceFilter don't extend ChoiceField. The code in filters looks like this. It is just property:

class ModelChoiceFilter(QuerySetRequestMixin, Filter):
    field_class = forms.ModelChoiceField

So my first idea won't work. Is there any other chance how to make what I need?


Solution

  • I found the solution. I use ChoiceFilter instead of ModelChoiceFilter and then for choices call a method where I create tuple of tuples with id of observer and the name from user model.

    def get_last_names():
        last_names = ()
        observers = Observer.objects.all()
        for obs in observers:
            last_names += (obs.id, obs.user.last_name),
        return last_names
    
    observer = ChoiceFilter(choices=get_last_names, label='Observer')