2009-06-04 10 views
37

Ho un modello per gli ordini in un'applicazione webshop, con una chiave primaria a incremento automatico e una chiave esterna a se stessa, poiché gli ordini possono essere suddivisi in più ordini, ma la relazione con l'ordine originale deve essere mantenuta.Django: accede all'istanza del modello da ModelAdmin?

class Order(models.Model): 
    ordernumber = models.AutoField(primary_key=True) 
    parent_order = models.ForeignKey('self', null=True, blank=True, related_name='child_orders') 
    # .. other fields not relevant here 

Ho registrato una classe OrderAdmin per l'amministratore del sito. Per la visualizzazione dettagliata, ho incluso parent_order nell'attributo fieldsets. Ovviamente, per impostazione predefinita questo elenca tutti gli ordini in una casella di selezione, ma questo non è il comportamento desiderato. Invece, per gli ordini che non hanno un ordine genitore (cioè non sono stati divisi da un altro ordine: parent_order è NULL/Nessuno), non devono essere visualizzati ordini. Per gli ordini che sono stati divisi, questo dovrebbe visualizzare solo l'ordine genitore singolo.

C'è un nuovo metodo ModelAdmin disponibile, formfield_for_foreignkey, che sembra perfetto per questo, poiché il set di query può essere filtrato al suo interno. Immagina di osservare la vista dettagliata dell'ordine n. 11234, che è stato diviso dall'ordine n. 11208. Il codice è inferiore

def formfield_for_foreignkey(self, db_field, request, **kwargs): 
    if db_field.name == 'parent_order': 
     # kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=11234) 
     return db_field.formfield(**kwargs) 
    return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) 

la fila commentato funziona quando eseguito in una shell Python, restituendo un queryset singolo elemento contenente ordine # 11208 per # 11234 e tutti gli altri ordini che possono essere stati divisi da esso.

Naturalmente, non è possibile codificare il numero dell'ordine. Abbiamo bisogno di un riferimento al campo ordernumber dell'istanza dell'ordine di cui stiamo guardando la pagina dei dettagli. In questo modo:

Non ho trovato alcun modo di lavoro per sostituire ????? con un riferimento all'istanza dell'Ordine "corrente", e ho scavato piuttosto in profondità. self all'interno di formfield_for_foreignkey fa riferimento all'istanza ModelAdmin e sebbene abbia un attributo model, non è l'istanza del modello di ordine (è un riferimento a ModelBase; self.model() restituisce un'istanza, ma il suo numero di ordine è Nessuno).

Una soluzione potrebbe essere quella di estrarre il numero di ordine da request.path (/ admin/orders/order/11234 /), ma questo è davvero brutto. Mi piacerebbe davvero che ci fosse un modo migliore.

risposta

54

Penso che potrebbe essere necessario avvicinarsi a questo in un modo leggermente diverso - modificando il ModelForm, piuttosto che la classe di amministrazione. Qualcosa del genere:

class OrderForm(forms.ModelForm): 

    def __init__(self, *args, **kwargs): 
     super(OrderForm, self).__init__(*args, **kwargs) 
     self.fields['parent_order'].queryset = Order.objects.filter(
      child_orders__ordernumber__exact=self.instance.pk) 

class OrderAdmin(admin.ModelAdmin): 
    form = OrderForm 
+1

Funziona! Grazie mille!Sono completamente nuovo a tutto questo business ModelForm/ModelAdmin e stavo cercando nel posto sbagliato. –

+0

Mi rendo conto che questo post è vecchio, ma questo ha funzionato anche come soluzione decente per me per get_readonly_fields su un InlineModelAdmin poiché il parametro obj passato ad esso è attualmente l'oggetto genitore, non l'oggetto dell'istanza in linea. A tutti gli effetti, questo ha reso il mio oggetto readonly permettendomi di restituire un solo oggetto nel mio foreignkey. – dgraves

+0

Assicurati di chiamare prima super .__ init__. Ciò definisce l'ego. – yellottyellott

5

Ho modellato la mia classe inline in questo modo. È un po 'brutto su come ottiene l'id del modulo padre per filtrare i dati in linea, ma funziona. Filtra le unità per società dal modulo padre.

Il concetto originale è spiegato qui http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-django-admin

class CompanyOccupationInline(admin.TabularInline): 

    model = Occupation 
    # max_num = 1 
    extra = 0 
    can_delete = False 
    formset = RequiredInlineFormSet 

    def formfield_for_dbfield(self, field, **kwargs): 

     if field.name == 'unit': 
      parent_company = self.get_object(kwargs['request'], Company) 
      units = Unit.objects.filter(company=parent_company) 
      return forms.ModelChoiceField(queryset=units) 
     return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs) 

    def get_object(self, request, model): 
     object_id = request.META['PATH_INFO'].strip('/').split('/')[-1] 
     try: 
      object_id = int(object_id) 
     except ValueError: 
      return None 
     return model.objects.get(pk=object_id) 
+5

Un modo un po 'più pulito per avvicinarsi a questo sarebbe quella di sfruttare il resolver URL: 'object_id = risolvono (request.path) .args [0]' – philipk

3

La risposta di cui sopra da Erwin Giulio ha lavorato per me, se ho scoperto che il nome conflitti "get_object" con una funzione di Django così chiamano la funzione "my_get_object".

class CompanyOccupationInline(admin.TabularInline): 

    model = Occupation 
    # max_num = 1 
    extra = 0 
    can_delete = False 
    formset = RequiredInlineFormSet 

    def formfield_for_dbfield(self, field, **kwargs): 

     if field.name == 'unit': 
      parent_company = self.my_get_object(kwargs['request'], Company) 
      units = Unit.objects.filter(company=parent_company) 
      return forms.ModelChoiceField(queryset=units) 
     return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs) 

    def my_get_object(self, request, model): 
     object_id = request.META['PATH_INFO'].strip('/').split('/')[-1] 
     try: 
      object_id = int(object_id) 
     except ValueError: 
      return None 
     return model.objects.get(pk=object_id) 

mi ha detto di non "rispondere" per le risposte degli altri, ma io non sono autorizzato a 'rispondere' ancora, e ho cercato per questo per un po 'così spero che questo sarà utile per gli altri . Non mi è nemmeno permesso di andare avanti ancora o lo farei totalmente!

+0

Anche se sarebbe meglio lasciare queste informazioni come un commento, mi capisci che non hai abbastanza reputazione per questo - conoscere 'get_object' causa comunque una collisione. – BlackVegetable

Problemi correlati