33

Ich habe ein Objekt mit einer Viele-zu-viele-Beziehung mit einem anderen Objekt.
Im Django Admin führt dies zu einer sehr langen Liste in einer Mehrfachauswahlbox.Filter ManyToMany Box in Django Admin

Ich möchte die ManyToMany-Beziehung filtern, damit ich nur Kategorien abrufen, die in der Stadt verfügbar sind, die der Kunde ausgewählt hat.

Ist das möglich? Muss ich ein Widget dafür erstellen? Und wenn ja - wie kopiere ich das Verhalten aus dem Standard-ManyToMany-Feld, da ich auch die Funktion filter_horizontal möchte.

Das sind meine vereinfachte Modelle:

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) 

Antwort

36

Ok, das ist meine Lösung mit den oben genannten Klassen. Ich habe ein paar Filter hinzugefügt, um es richtig zu filtern, aber ich wollte den Code hier lesbar machen.

Das ist genau das, was ich suchte, und ich meine Lösung hier: http://www.slideshare.net/lincolnloop/customizing-the-django-admin#stats-bottom (Folie 50)

folgenden mein admin.py hinzufügen:

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 

Diese filtert die „Kategorien "Liste ohne jegliche Funktionalität zu entfernen! (zB: ich kann immer noch meine geliebte filter_horizontal :))

Die ModelForms ist sehr mächtig, ich bin ein wenig überrascht, es ist nicht mehr in der Dokumentation/Buch abgedeckt.

+0

Ich habe festgestellt, dass nach dem Hinzufügen dieses Codes zu einem Projekt ich habe, dass das ausgewählte Optionsfeld (wäre in Ihrem Beispiel unter "Ausgewählte Kategorien" gewählt) leer ist, auch nach Auswahl einer Option aus dem Feld "Verfügbare Kategorien". Habe ich bei der Umsetzung etwas übersehen? – Silfheed

+0

Weitere Reduktion mit Listenverständnis: self.fields ['categories']. Widget.choices = [(choice.id, choice.name) zur Auswahl in wtf] –

+2

was ist 'cat_id'? – Sevenearths

0
Category.objects.filter(available_in=cityobject) 

Das sollte es tun. Die Ansicht sollte die Stadt haben, die der Benutzer entweder in der Anforderung oder als Parameter für diese Ansichtsfunktion ausgewählt hat.

+0

Aber wenn ich über den Django-Administrator spreche, sagst du, ich sollte die Standardansicht duplizieren und die obigen hinzufügen? – schmilblick

+0

Ah, ich habe den ganzen "Django Admin" Teil deines Fragetitels total vermisst. Ich denke immer noch, dass dies die richtige Herangehensweise ist, obwohl ich nicht genau weiß, wo Sie es hinstellen würden, oder ob das überhaupt möglich ist. – AlbertoPL

1

Da Sie die Stadt und die Kategorien des Kunden in derselben Form auswählen, benötigen Sie Javascript, um den Kategorien-Selektor dynamisch auf die Kategorien zu reduzieren, die in der ausgewählten Stadt verfügbar sind.

+0

Ich fühle mich nicht daran interessiert, Zehntausende von DOM-Elementen mit Javascript zu iterieren und mit einer anderen riesigen Liste zu vergleichen. Ich würde sagen, Javascript ist definitiv nicht der Weg zu gehen, dies muss Back-End getan werden, wenn Sie die Kategorien aus der Datenbank auswählen. – schmilblick

12

Soweit ich Sie verstehen kann, ist, dass Sie im Grunde die angezeigten Auswahlmöglichkeiten nach bestimmten Kriterien (Kategorie nach Stadt) filtern möchten.

Sie können genau das tun, indem Sie limit_choices_to Attribut von models.ManyToManyField verwenden. So wie Ihr Modell Definition zu ändern ...

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

Dies sollte funktionieren, wie limit_choices_to, ist für diesen Zweck zur Verfügung.

Aber eine Sache zu beachten, hat limit_choices_to keinen Effekt, wenn auf einem ManyToManyField mit einer benutzerdefinierten Zwischentabelle verwendet. Hoffe das hilft.

+0

Das sieht so aus als könnte es funktionieren! Wie auch immer ... es machte mir klar, dass ich meine Modelle neu modellieren muss :) Ich lese in den Dokumenten, dass der Admin sich nicht um die limit_choices_to kümmert, was ist das? – schmilblick

+0

Ich versuche genau dasselbe zu tun, wie Sie @sim beschreiben, aber ich erhalte einen Fehler von 'ValueError um/admin/foo/bar /: ungültiges Literal für int() mit Basis 10: 'city''. Gibt es etwas, das mir bei der Implementierung dieser Filtermethode fehlt? – nhinkle

+0

@nhinkle Diese "Stadt" im Wert soll die ID des Stadtobjekts bedeuten, auf das Kategorien beschränkt werden sollen. Meine Entschuldigung. Ich werde die Antwort bearbeiten, um klarer zu sein. – simplyharsh

0

Wie Ryan sagt, muss es einige Javascript geben, um die Optionen basierend auf dem, was der Benutzer wählt, dynamisch zu ändern. Die gepostete Lösung funktioniert, wenn die Stadt gespeichert und das Admin-Formular neu geladen wird, dh wenn der Filter funktioniert, aber eine Situation, in der ein Benutzer ein Objekt bearbeiten möchte und dann das Stadt-Dropdown ändert, aber die Optionen in der Kategorie nicht aktualisieren.

4

Ein anderer Weg ist mit formfield_for_manytomany in Django Admin.

class MyModelAdmin(admin.ModelAdmin): 
    def formfield_for_manytomany(self, db_field, request, **kwargs): 
     if db_field.name == "cars": 
      kwargs["queryset"] = Car.objects.filter(owner=request.user) 
     return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs) 

Unter Berücksichtigung, dass "Autos" das ManyToMany-Feld sind.

Überprüfen Sie this link für weitere Informationen.