2014-05-12 13 views
11

Come si limitano le opzioni mostrate per i campi ForeignKey nell'amministratore di Django quando vengono visualizzati utilizzando l'opzione raw_id_fields?Come limitare le scelte di ForeignKey per Django raw_id_field

Quando il rendering viene eseguito come casella di selezione, è semplice impostare define a custom ModelForm per impostare il valore del queryset di quel campo con le opzioni desiderate. Tuttavia, questo set di query sembra essere completamente ignorato quando viene eseguito il rendering utilizzando raw_id_fields. Genera un collegamento al modello di quella ForeignKey, consentendo di selezionare qualsiasi record da quel modello tramite una finestra popup. È ancora possibile filtrare questi valori personalizzando l'URL, ma non riesco a trovare un modo per farlo tramite un ModelAdmin.

risposta

6

Io uso simile all'approccio FSp nel mio progetto Django 1.8/Python 3.4:

from django.contrib import admin 
from django.contrib.admin import widgets 
from django.contrib.admin.sites import site 
from django import forms 

class BlogRawIdWidget(widgets.ForeignKeyRawIdWidget): 
    def url_parameters(self): 
     res = super().url_parameters() 
     res['type__exact'] = 'PROJ' 
     return res 

class ProjectAdminForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 
     self.fields['blog'].queryset = Blog.objects.filter(type='PROJ') 
     self.fields['blog'].widget = BlogRawIdWidget(rel=Project._meta.get_field('blog').rel, admin_site=site) 

    class Meta: 
     # Django 1.8 convenience: 
     fields = '__all__' 
     model = Project 

class ProjectAdmin(admin.ModelAdmin): 
    form = ProjectAdminForm 
    raw_id_fields = ('blog',) 

per selezionare solo blog.type == 'PROJ' come stranieri chiave Project.blog in django.admin. Perché gli utenti finali possono e vogliono selezionare qualsiasi cosa, sfortunatamente.

+0

Mi piace questa risposta se il tipo è statico, sempre "PROJ". Ma come lo faresti se volessi che fosse dinamico, basato su un sito selezionato, per esempio. Nel modulo principale, si seleziona un sito. Quindi dovresti scegliere per raw_id_field in forma in linea di essere limitato a quel sito. Ma il problema che ho è, il modulo viene istanziato prima che l'utente abbia la possibilità di selezionare un sito – straykiwi

3

Il metodo seguente funziona per me, ma è un set di query che interessa ogni amministratore che deve utilizzare il modello Cliente. Ma se hai un altro amministratore, ad es. Fattura che richiede un queryset diverso, potresti voler sperimentare un po 'con il proxy del modello.

Modello

class Customer(models.Model): 
    name = models.CharField(max_length=100) 
    is_active = models.BooleanField() 

class Order(models.Model): 
    cust = models.ForeignKey(Customer) 

Admin

class CustomerAdmin(admin.ModelAdmin):   
    def queryset(self, request): 
     qs = super(CustomerAdmin, self).queryset(request)   
     return qs.filter(is_active=1) 

class OrderAdmin():  
    raw_id_fields = ('cust',)  
+0

Grazie, questo è in realtà abbastanza semplice e intelligente. Inoltre, è possibile controllare i parametri GET nella richiesta e regolare il filtro se si tratta di un popup, ecc. – Cerin

4

trovo la soluzione di data (la personalizzazione del ModelAdmin queryset) un po 'troppo restrittiva, per i progetti realistici.

Quello che faccio, di solito è il seguente:

  • creare un filtro personalizzato nel mio ModelAdmin (ad esempio sottoclasse admin.SimpleListFilter, vedo il doc)
  • creare il mio sottoclasse di widget di ForeignKeyRawIdWidget come segue:

    nota che l'unica cosa che il widget personalizzato fa è "preselezionare" il filtro che, a sua volta, è responsibl e per "restrizione" del queryset

  • uso il widget personalizzato:

    class MyForm(forms.ModelForm): 
    
        myfield = forms.ModelChoiceField(queryset=MyModel.objects.all(), 
         ... 
         widget=CustomRawIdWidget(
          MyRelationModel._meta.get_field('myfield').rel, 
          admin.site)) 
    

Un punto debole di questo approccio è che il filtro selezionato dal widget non impedisce di selezionare un altro esempio da quel modello. Se lo si desidera, si ignora il metodo ModelAdmin.save_model(...) (vedere doc) per verificare che le istanze correlate siano solo quelle consentite.

Trovo questo approccio un po 'più complesso, ma molto più flessibile della limitazione del set di query per l'intero ModelAdmin.

2

Se è necessario filtrare la comparsa list_view raw_id sulla base di esempio modello è possibile utilizzare nell'esempio qui sotto:

1.Scrivi personalizzato widget di

class RawIdWidget(widgets.ForeignKeyRawIdWidget): 

    def url_parameters(self): 
     res = super(RawIdWidget, self).url_parameters() 
     object = self.attrs.get('object', None) 
     if object: 
      # Filter variants by product_id 
      res['product_id'] = object.variant.product_id 
     return res 

2. esempio Passo sulla forma init

class ModelForm(forms.ModelForm): 

    def __init__(self, *args, **kwargs): 
     super(ModelForm, self).__init__(*args, **kwargs) 
     obj = kwargs.get('instance', None) 
     if obj and obj.pk is not None: 
      self.fields['variant'].widget = RawIdWidget(
       rel=obj._meta.get_field('variant').rel, 
       admin_site=admin.site, 
       # Pass the object to attrs 
       attrs={'object': obj} 
      ) 
2

ho creato una soluzione genetica per risolvere il problema dei parametri personalizzati per passare la finestra di pop-up. Hai solo bisogno di copiare questo codice sul vostro progetto:

from django.contrib.admin import widgets 

class GenericRawIdWidget(widgets.ForeignKeyRawIdWidget): 
    url_params = [] 

    def __init__(self, rel, admin_site, attrs=None, \ 
     using=None, url_params=[]): 
     super(GenericRawIdWidget, self).__init__(
      rel, admin_site, attrs=attrs, using=using) 
     self.url_params = url_params 

    def url_parameters(self): 
     """ 
     activate one or more filters by default 
     """ 
     res = super(GenericRawIdWidget, self).url_parameters() 
     res.update(**self.url_params) 

     return res 

Quindi, è possibile utilizzare in questo modo:

field.widget = GenericRawIdWidget(YOURMODEL._meta.get_field('YOUR_RELATION').rel, 
      admin.site, url_params={"<YOURMODEL>__id__exact":  object_id}) 

ho usato in questo modo:

class ANSRuleInline(admin.TabularInline): 
    model = ANSRule 
    form = ANSRuleInlineForm 
    extra = 1 
    raw_id_fields = ('parent',) 

    def __init__(self, *args, **kwargs): 
     super (ANSRuleInline,self).__init__(*args,**kwargs) 

    def formfield_for_dbfield(self, db_field, **kwargs): 
     formfield = super(ANSRuleInline, self).formfield_for_dbfield(db_field, **kwargs) 
     request = kwargs.get("request", None) 
     object_id = self.get_object(request) 

     if db_field.name == 'parent': 
      formfield.widget = GenericRawIdWidget(ANSRule._meta.get_field('parent').rel, 
       admin.site, url_params={"pathology__id__exact": object_id}) 

     return formfield 

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

quando uso GenericRawIdWidget, ho passato un dettato a url_params, che verrà utilizzato nell'URL.

Problemi correlati