Filter ManyToMany box in Django Admin

2019-01-16 01:26发布

I have a object with a many-to-many relation with another object.
In the Django Admin this results in a very long list in a multiple select box.

I'd like to filter the ManyToMany relation so I only fetch Categories that is available in the City that the Customer have selected.

Is this possible? Will I have to create a widget for it? And if so - how do I copy the behavior from the standard ManyToMany field to it, since I would like the filter_horizontal function as well.

These are my simplified models:

class City(models.Model):
    name = models.CharField(max_length=200)


class Category(models.Model):
    name = models.CharField(max_length=200)
    available_in = models.ManyToManyField(City)


class Customer(models.Model):
    name = models.CharField(max_length=200)
    city = models.ForeignKey(City)
    categories = models.ManyToManyField(Category)

7条回答
淡お忘
2楼-- · 2019-01-16 02:01

I think this is what you're looking for:

http://blog.philippmetzler.com/?p=52

we use django-smart-selects:

http://github.com/digi604/django-smart-selects

Philipp

查看更多
孤傲高冷的网名
3楼-- · 2019-01-16 02:03

Ok, this is my solution using above classes. I added a bunch more filters to filter it correctly, but I wanted to make the code readable here.

This is exactly what I was looking for, and I found my solution here: http://www.slideshare.net/lincolnloop/customizing-the-django-admin#stats-bottom (slide 50)

Add the following to my admin.py:

class CustomerForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs):
        super(CustomerForm, self).__init__(*args, **kwargs)
        wtf = Category.objects.filter(pk=self.instance.cat_id);
        w = self.fields['categories'].widget
        choices = []
        for choice in wtf:
            choices.append((choice.id, choice.name))
        w.choices = choices


class CustomerAdmin(admin.ModelAdmin):
    list_per_page = 100
    ordering = ['submit_date',] # didnt have this one in the example, sorry
    search_fields = ['name', 'city',]
    filter_horizontal = ('categories',)
    form = CustomerForm

This filters the "categories" list without removing any functionality! (ie: i can still have my beloved filter_horizontal :))

The ModelForms is very powerful, I'm a bit surprised it's not covered more in the documentation/book.

查看更多
冷血范
4楼-- · 2019-01-16 02:03

Like Ryan says, there has to be some javascript to dynamically change the options based on what the user selects. The posted solution works if city is saved and the admin form is reloaded, thats when the filter works, but think of a situation where a user wants to edit an object and then changes the city drop down but the options in category wont refresh.

查看更多
闹够了就滚
5楼-- · 2019-01-16 02:06

Since you're selecting the customer's city and categories in the same form, you would need some javascript to dynamically whittle down the categories selector to just the categories available in the city selected.

查看更多
乱世女痞
6楼-- · 2019-01-16 02:07
Category.objects.filter(available_in=cityobject)

That should do it. The view should have the city that the user selected, either in the request or as a parameter to that view function.

查看更多
SAY GOODBYE
7楼-- · 2019-01-16 02:09

As far as i can understand you, is that you basically want to filter the shown choices according to some criteria (category according to city).

You can do exactly that by using limit_choices_to attribute of models.ManyToManyField. So changing your model definition as...

class Customer(models.Model):
    name = models.CharField(max_length=200)
    city = models.ForeignKey(City)
    categories = models.ManyToManyField(Category, limit_choices_to = {'available_in': cityId})

This should work, as limit_choices_to, is available for this very purpose.

But one things to note, limit_choices_to has no effect when used on a ManyToManyField with a custom intermediate table. Hope this helps.

查看更多
登录 后发表回答