sretko
sretko

Reputation: 640

django-filter dropdown menu

I'm trying to turn my django-filter fields into dropdowns.

class UserFilter(django_filters.FilterSet):
    class Meta:
        model = Product
        fields = ['category', 'genre', 'instrument', ]


def filter(request):
    filter = UserFilter(request.GET, queryset=Product.objects.all())
    return render(request, 'filter.html', {'filter': filter})

I'm trying to achieve this by using ModelChoiceFilter, like this:

category=django_filters.ModelChoiceFilter(queryset=Product.objects.all())
genre = django_filters.ModelChoiceFilter(queryset=Product.objects.all())
instrument=django_filters.ModelChoiceFilter(queryset=Product.objects.all))) 

It works! However instead of returning the desire column, it returns title filed on all of the django-form fields. That is coming from my model.

def __str__(self):
    return self.title

The same behavior can be observed when working with simple Django model forms. In this case I'm just overriding label_from_instance function of the ModelChoiceField class like this:

class CategoryModelChoiceField(ModelChoiceField):
    def label_from_instance(self, obj):
        return obj.category

My question is how to override ModelChoiceFilter? Or probably there is another convenient way to achieve dropdowns with django-filter?

UPDATE

As djnago-filter is coming with dropdowns for ForeignKeys by default, I just changed my model. Now it looks like so:

class Category(models.Model):
    category = models.CharField(max_length=50, blank=True)

class Genre(models.Model):
    genre = models.CharField(max_length=50, blank=True)


class Instrument(models.Model):
    instrument = models.CharField(max_length=50, blank=True)


class Product(models.Model):
    category = models.ForeignKey(Category)
    genre = models.ForeignKey(Genre)
    instrument = models.ForeignKey(Instrument)
    title = models.TextField(max_length=200, blank=True)

    def __str__(self):
        return self.title

Now it changed it's behavior. It nows the number of the objects but not the actual strings. Like this:

Category object - Genre object - Instrument object - title1
Category object - Genre object - Instrument object - title2
Category object - Genre object - Instrument object - title3

The dropdowns itself are working fine without any intervention:

class UserFilter(django_filters.FilterSet):
    class Meta:
        model = Product
        fields = ['category', 'genre', 'instrument', ]

Upvotes: 1

Views: 12368

Answers (2)

jsutherl
jsutherl

Reputation: 141

I've used the same solution, but sometimes fully normalizing the model is something I want to avoid. Maybe I've missed it, but I'm surprised django_filters.modelChoiceFilter won't simply return any attribute in the queryset, not just foreign key names.

Here's another solution that generates the choices dynamically from the queryset.

class DynamicChoiceMixin(object):

    @property
    def field(self):
        queryset = self.parent.queryset
        field = super(DynamicChoiceMixin, self).field

        choices = list()
        have = list()
        # iterate through the queryset and pull out the values for the field name
        for item in queryset:
            name = getattr(item, self.field_name)
            if name in have:
                continue
            have.append(name)
            choices.append((name, name))
        field.choices.choices = choices
        return field


class DynamicChoiceFilter(DynamicChoiceMixin, django_filters.ChoiceFilter):
    pass


class UserFilter(django_filters.FilterSet):

    category = DynamicChoiceFilter(name=‘category’)
    genre = DynamicChoiceFilter(name=‘genre’)
    instrument = DynamicChoiceFilter(name=‘instrument’)

    class Meta:
        model = Product
        fields = ['category', 'genre', 'instrument', ]

Upvotes: 0

sretko
sretko

Reputation: 640

I finally made it work. This is the working model:

class Category(models.Model):
    category = models.CharField(max_length=50, blank=True)

    def __str__(self):
        return self.category


class Genre(models.Model):
    genre = models.CharField(max_length=50, blank=True)

    def __str__(self):
        return self.genre


class Instrument(models.Model):
    instrument = models.CharField(max_length=50, blank=True)

    def __str__(self):
        return self.instrument


class Product(models.Model):
    category = models.ForeignKey(Category)
    genre = models.ForeignKey(Genre)
    instrument = models.ForeignKey(Instrument)
    title = models.TextField(max_length=200, blank=True)

    def __str__(self):
        return self.title

Upvotes: 1

Related Questions