38 votes

Filtrer la boîte ManyToMany dans Django Admin

J'ai un objet avec une relation ManyToMany avec un autre objet.
Dans l'administration Django, cela se traduit par une liste très longue dans une boîte de sélection multiple.

J'aimerais filtrer la relation ManyToMany afin de ne récupérer que les catégories disponibles dans la ville que le client a sélectionnée.

Est-ce possible ? Devrai-je créer un widget pour cela ? Et si c'est le cas, comment puis-je copier le comportement du champ ManyToMany standard dans ce champ, puisque j'aimerais que l'option filter_horizontal également la fonction.

Ce sont mes modèles simplifiés :

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)

39voto

schmilblick Points 958

Ok, voici ma solution en utilisant les classes ci-dessus. J'ai ajouté un tas de filtres supplémentaires pour le filtrer correctement, mais je voulais rendre le code lisible ici.

C'est exactement ce que je cherchais, et j'ai trouvé ma solution ici : http://www.slideshare.net/lincolnloop/customizing-the-django-admin#stats-bottom (diapositive 50)

Ajoutez ce qui suit à mon fichier 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

Cela filtre la liste des "catégories" sans enlever aucune fonctionnalité ! (c'est-à-dire que je peux toujours avoir mon cher filter_horizontal :))

Le ModelForms est très puissant, je suis un peu surpris qu'il ne soit pas plus couvert dans la documentation/livre.

0 votes

J'ai remarqué qu'après avoir ajouté ce code à un de mes projets, la case des options sélectionnées (qui se trouverait sous "Catégories choisies" dans votre exemple) est vide même après avoir sélectionné une option dans le champ "Catégories disponibles". Ai-je manqué quelque chose dans l'implémentation de ce code ?

0 votes

Réduction supplémentaire en utilisant la compréhension de liste : self.fields['categories'].widget.choices = [(choice.id, choice.name) for choice in wtf]

0 votes

Comment rendre le classement des catégories en lecture seule. J'essaie de lire seulement_fields = ('users',) . Mais il est affiché sur une seule ligne séparée par une virgule. Je veux qu'il soit affiché dans un saut de ligne ...

18voto

simplyharsh Points 11663

D'après ce que je comprends, vous souhaitez filtrer les choix affichés en fonction de certains critères (catégorie selon la ville).

Vous pouvez faire exactement cela en utilisant limit_choices_to l'attribut de models.ManyToManyField . Donc, en changeant la définition de votre modèle comme...

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

Cela devrait fonctionner, car limit_choices_to est disponible à cette fin.

Mais il y a une chose à noter, limit_choices_to n'a aucun effet lorsqu'elle est utilisée sur un champ ManyToManyField avec une table intermédiaire personnalisée. J'espère que cela vous aidera.

0 votes

On dirait que ça pourrait marcher ! Cependant... cela m'a fait réaliser que je dois remodeler mes modèles :) Je lis dans la documentation que l'administrateur ne se préoccupe pas non plus de la limite_choices_to, qu'en pensez-vous ?

0 votes

J'essaie de faire exactement la même chose que celle décrite par @sim, mais j'obtiens l'erreur suivante ValueError at /admin/foo/bar/: invalid literal for int() with base 10: 'city' . Y a-t-il quelque chose qui m'échappe dans la mise en œuvre de cette méthode de filtrage ?

0 votes

Le terme "ville" dans la valeur est censé signifier l'identifiant de l'objet ville auquel vous voulez limiter les catégories. Je m'excuse. Je vais modifier la réponse pour qu'elle soit plus claire.

2voto

Googol Points 337

Je pense que c'est ce que vous cherchez :

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

nous utilisons django-smart-selects :

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

Philipp

1 votes

Pourriez-vous développer votre réponse à l'aide d'exemples ? Il s'agit pratiquement d'une réponse uniquement par URL. Pourquoi ce blog est-il ce qu'ils recherchent ? Pourquoi utilisez-vous Django-Smart-Selects ?

1voto

Ryan Fugger Points 176

Étant donné que vous sélectionnez la ville et les catégories du client dans le même formulaire, vous auriez besoin d'un javascript pour réduire dynamiquement le sélecteur de catégories aux seules catégories disponibles dans la ville sélectionnée.

0 votes

Je n'ai pas envie d'itérer sur des dizaines de milliers d'éléments DOM avec javascript et de les comparer à une autre liste énorme. Je dirais que Javascript n'est définitivement pas la voie à suivre, cela doit être fait en amont lors de la sélection des catégories dans la base de données.

-2voto

AlbertoPL Points 8644
Category.objects.filter(available_in=cityobject)

Cela devrait suffire. La vue doit contenir la ville que l'utilisateur a sélectionnée, soit dans la requête, soit comme paramètre de la fonction de vue.

0 votes

Mais je parle de l'administration de django, vous dites que je dois dupliquer la vue standard et ajouter ce qui précède ?

0 votes

Ah, j'ai complètement manqué la partie "Django Admin" dans le titre de votre question. Je pense toujours que c'est l'approche correcte, même si je ne sais pas exactement où vous le placeriez, ou si c'est même possible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X