2009-08-04 25 views
33

Ho un oggetto con una relazione molti-a-molti con un altro oggetto.
Nell'amministratore di Django questo risulta in un elenco molto lungo in una casella di selezione multipla.Filtra ManyToMany box in Django Admin

Mi piacerebbe filtrare la relazione ManyToMany in modo da recuperare solo le categorie disponibili nella città che il cliente ha selezionato.

È possibile? Dovrò creare un widget per questo? E se è così - come faccio a copiare il comportamento dal campo ManyToMany standard ad esso, dal momento che mi piacerebbe anche la funzione filter_horizontal.

Questi sono i miei modelli semplificati:

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) 

risposta

36

Ok, questa è la mia soluzione utilizzando sopra le classi. Ho aggiunto un mucchio di altri filtri per filtrarlo correttamente, ma volevo rendere il codice leggibile qui.

Questo è esattamente quello che stavo cercando, e ho trovato la mia soluzione qui: http://www.slideshare.net/lincolnloop/customizing-the-django-admin#stats-bottom (slide 50)

Aggiungere il seguente alla mia 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 

Questo filtra le "categorie "elenco senza rimuovere alcuna funzionalità! (es: posso ancora avere il mio amato filter_horizontal :))

Il ModelForms è molto potente, sono un po 'sorpreso che non è coperto più nella documentazione/libro.

+0

Ho notato che dopo aver aggiunto questo codice in un progetto, ho che la casella delle opzioni selezionate (che si trova sotto "Scelte categorie" nel tuo esempio) è vuota anche dopo aver selezionato un'opzione dal campo "Categorie disponibili". Mi sono perso qualcosa nell'attuazione di questo? – Silfheed

+0

Ulteriore riduzione utilizzando la comprensione di lista: self.fields ['categories']. Widget.choices = [(choice.id, choice.name) per la scelta in wtf] –

+2

cosa è 'cat_id'? – Sevenearths

0
Category.objects.filter(available_in=cityobject) 

Che dovrebbe farlo. La vista dovrebbe avere la città selezionata dall'utente, nella richiesta o come parametro per quella funzione di visualizzazione.

+0

Ma sto parlando dell'amministratore di django, stai dicendo che dovrei duplicare la visualizzazione standard e aggiungere quanto sopra? – schmilblick

+0

Ah, ho totalmente perso l'intera parte "Django Admin" del titolo della tua domanda. Continuo a pensare che questo sia l'approccio corretto, anche se non sono esattamente sicuro di dove lo si possa inserire, o se è addirittura possibile. – AlbertoPL

1

Dato che si seleziona la città e le categorie del cliente nello stesso formato, è necessario un po 'di javascript per ridimensionare dinamicamente il selettore di categorie solo per le categorie disponibili nella città selezionata.

+0

Non mi sento in ansia di ripetere su decine di migliaia di elementi DOM con javascript e il confronto con un altro elenco enorme. Direi che Javascript non è assolutamente la strada da percorrere, questo deve essere fatto di nuovo quando si selezionano le categorie dal database. – schmilblick

12

Per quanto posso capire, è che fondamentalmente si desidera filtrare le scelte visualizzate in base ad alcuni criteri (categoria in base alla città).

È possibile eseguire esattamente questo utilizzando l'attributo limit_choices_to di models.ManyToManyField. Quindi, cambiare la vostra definizione modello come ...

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

Questo dovrebbe funzionare, come limit_choices_to, è disponibile per questo scopo.

Ma una cosa da notare, limit_choices_to non ha alcun effetto se utilizzato su un ManyToManyField con una tabella intermedia personalizzata. Spero che questo ti aiuti.

+0

Sembra che potrebbe funzionare! Comunque ... mi ha fatto capire che devo ri-modellare i miei modelli :) Sto leggendo nei documenti che l'amministratore non si preoccupa del limit_choices_to pure, che cosa ne pensi? – schmilblick

+0

Sto cercando di fare la stessa cosa nel modo in cui descrivi @sim, ma ottengo un errore di 'ValueError in/admin/foo/bar /: letterale non valido per int() con base 10: 'city''. C'è qualcosa che mi manca con come implementare questo metodo di filtraggio? – nhinkle

+0

@nhinkle Quella "città" nel valore dovrebbe significare Id dell'oggetto della città a cui si desidera limitare le categorie. Le mie scuse. Modificherò la risposta per essere più chiaro. – simplyharsh

0

Come dice Ryan, ci deve essere qualche javascript per modificare dinamicamente le opzioni in base a ciò che l'utente seleziona. La soluzione pubblicata funziona se la città viene salvata e il modulo di amministrazione viene ricaricato, questo è quando il filtro funziona, ma pensa a una situazione in cui un utente desidera modificare un oggetto e quindi modifica il menu a discesa della città, ma le opzioni nella categoria non si aggiornano.

4

Un altro modo è con formfield_for_manytomany in Admin Django.

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) 

Considerando che "le macchine" sono il campo ManyToMany.

Verificare this link per ulteriori informazioni.