2008-11-15 15 views
188

कहें कि मेरे पास models.py में निम्न है:मैं Django ModelForm में ForeignKey विकल्पों को कैसे फ़िल्टर करूं?

class Company(models.Model): 
    name = ... 

class Rate(models.Model): 
    company = models.ForeignKey(Company) 
    name = ... 

class Client(models.Model): 
    name = ... 
    company = models.ForeignKey(Company) 
    base_rate = models.ForeignKey(Rate) 

I.e. कई Companies हैं, प्रत्येक में Rates और Clients की एक श्रृंखला है। प्रत्येक Client में आधार Rate होना चाहिए जो उसके मूल Company's Rates से चुना गया है, न कि कोई अन्य Company's Rates

Client जोड़ने के लिए एक फॉर्म बनाते समय, मैं Company विकल्पों को हटाना चाहता हूं (जैसा कि पहले से ही Company पृष्ठ पर "क्लाइंट जोड़ें" बटन के माध्यम से चुना गया है) और Rate विकल्पों को Company पर भी सीमित करें ।

मैं Django 1.0 में इसके बारे में कैसे जा सकता हूं?

मेरी वर्तमान forms.py फ़ाइल इस समय बस बॉयलरप्लेट है:

from models import * 
from django.forms import ModelForm 

class ClientForm(ModelForm): 
    class Meta: 
     model = Client 

और views.py भी मूलभूत है:

from django.shortcuts import render_to_response, get_object_or_404 
from models import * 
from forms import * 

def addclient(request, company_id): 
    the_company = get_object_or_404(Company, id=company_id) 

    if request.POST: 
     form = ClientForm(request.POST) 
     if form.is_valid(): 
      form.save() 
      return HttpResponseRedirect(the_company.get_clients_url()) 
    else: 
     form = ClientForm() 

    return render_to_response('addclient.html', {'form': form, 'the_company':the_company}) 

Django 0.96 में मैं कुछ ऐसा करने से इसे हैक करने में सक्षम था टेम्पलेट को प्रस्तुत करने से पहले निम्नलिखित:

manipulator.fields[0].choices = [(r.id,r.name) for r in Rate.objects.filter(company_id=the_company.id)] 

ForeignKey.limit_choices_to आशाजनक प्रतीत होता है लेकिन मुझे नहीं पता कि the_company.id में कैसे पास किया जाए और मैं स्पष्ट नहीं हूं कि यह व्यवस्थापक इंटरफ़ेस के बाहर काम करेगा या नहीं।

धन्यवाद। (यह एक बहुत ही बुनियादी अनुरोध की तरह लगता है लेकिन अगर मुझे कुछ नया स्वरूप देना चाहिए तो मैं सुझावों के लिए खुला हूं।)

उत्तर

200

विदेशीकी का प्रतिनिधित्व django.forms.ModelChoiceField द्वारा किया जाता है, जो चॉइसफ़िल्ल्ड है जिसका विकल्प मॉडल QuerySet है। ModelChoiceField के संदर्भ देखें।

तो, फ़ील्ड के queryset विशेषता के लिए एक क्वेरीसेट प्रदान करें। इस पर निर्भर करता है कि आपका फॉर्म कैसे बनाया गया है। यदि आप एक स्पष्ट रूप बनाते हैं, तो आपके पास सीधे नाम वाले फ़ील्ड होंगे।

form.rate.queryset = Rate.objects.filter(company_id=the_company.id) 

आप डिफ़ॉल्ट ModelForm वस्तु लेते हैं, form.fields["rate"].queryset = ...

यह दृश्य में स्पष्ट रूप से किया जाता है। चारों ओर कोई हैकिंग नहीं।

+0

ठीक है, कि लगता है: उदाहरण के लिए यहाँ के लिए मैं एक क्षेत्र, फार्म का है कि get_list (अनुरोध) से कई टर्मिनल आइटम चुनने के लिए विशेष मामलों में इस्तेमाल किया जा सकता पर '_terminal_list' कहा जाता है तो request.user के आधार पर फ़िल्टरिंग होनहार। मैं प्रासंगिक फ़ील्ड ऑब्जेक्ट का उपयोग कैसे करूं? form.company.QuerySet = Rate.objects.filter (company_id = the_company.id)? या एक शब्दकोश के माध्यम से? – Tom

+1

ठीक है, उदाहरण का विस्तार करने के लिए धन्यवाद, लेकिन मुझे फॉर्म.फील्ड ["दर"] का उपयोग करना प्रतीत होता है। "क्लाइंटफॉर्म" ऑब्जेक्ट से बचने के लिए क्वेरीसेट में कोई विशेषता 'दर' नहीं है, क्या मुझे कुछ याद आ रहा है? (और आपका उदाहरण form.rate.queryset भी होना चाहिए।) – Tom

+0

उत्कृष्ट, स्पष्टीकरण के लिए धन्यवाद। भविष्य के संदर्भ के लिए, जब आप टिप्पणी के माध्यम से अपना उत्तर संपादित करते हैं तो यह ध्यान देने योग्य हो सकता है क्योंकि संपादन मेरे उपयोगकर्ता पृष्ठ के प्रतिक्रिया टैब में दिखाई नहीं देता है। – Tom

115

एसएलॉट के उत्तर के अलावा और टिप्पणियों में उल्लिखित गुरु बनने के रूप में, ModelForm.__init__ फ़ंक्शन को ओवरराइड करके क्वेरीसेट फ़िल्टर जोड़ना संभव है। (यह नियमित रूप से नियमित रूप से लागू हो सकता है) यह पुन: उपयोग करने में मदद कर सकता है और दृश्य कार्य को साफ रखता है।

class ClientForm(forms.ModelForm): 
    def __init__(self,company,*args,**kwargs): 
     super (ClientForm,self).__init__(*args,**kwargs) # populates the post 
     self.fields['rate'].queryset = Rate.objects.filter(company=company) 
     self.fields['client'].queryset = Client.objects.filter(company=company) 

    class Meta: 
     model = Client 

def addclient(request, company_id): 
     the_company = get_object_or_404(Company, id=company_id) 

     if request.POST: 
      form = ClientForm(the_company,request.POST) #<-- Note the extra arg 
      if form.is_valid(): 
       form.save() 
       return HttpResponseRedirect(the_company.get_clients_url()) 
     else: 
      form = ClientForm(the_company) 

     return render_to_response('addclient.html', 
            {'form': form, 'the_company':the_company}) 

यह पुनः उपयोग के लिए उपयोगी हो सकता है का कहना है कि अगर आप कई मॉडलों पर की जरूरत आम फिल्टर है (आम तौर पर मैं एक सार फार्म वर्ग घोषित)। जैसे

class UberClientForm(ClientForm): 
    class Meta: 
     model = UberClient 

def view(request): 
    ... 
    form = UberClientForm(company) 
    ... 

#or even extend the existing custom init 
class PITAClient(ClientForm): 
    def __init__(company, *args, **args): 
     super (PITAClient,self).__init__(company,*args,**kwargs) 
     self.fields['support_staff'].queryset = User.objects.exclude(user='michael') 

इसके अलावा मैं सिर्फ Django ब्लॉग सामग्री को बहाल कर रहा हूं जिसमें से कई अच्छे लोग हैं।

+0

आपके पहले कोड स्निपेट में एक टाइपो है, आप तर्क और kwargs के बजाय __init __() में दो बार तर्क परिभाषित कर रहे हैं। – tpk

+0

चीयर्स, – michael

+5

अपडेट किया गया है, मुझे यह जवाब बेहतर लगता है, मुझे लगता है कि दृश्य विधि के बजाय फॉर्म क्लास में फॉर्म प्रारंभिक तर्क को समाहित करना क्लीनर है। चीयर्स! – Symmetric

2

तो, मैंने वास्तव में इसे समझने की कोशिश की है, लेकिन ऐसा लगता है कि Django अभी भी इसे बहुत सरल नहीं बनाता है। मैं सब गूंगा नहीं हूँ, लेकिन मैं बस कुछ (कुछ हद तक) सरल समाधान नहीं देख सकता।

मुझे यह आम तौर पर इस तरह की चीज़ के लिए व्यवस्थापक दृश्यों को ओवरराइड करने के लिए बहुत बदसूरत लगता है, और मुझे लगता है कि प्रत्येक उदाहरण कभी भी व्यवस्थापक दृश्यों पर पूरी तरह से लागू नहीं होता है।

इस मॉडल मैं मुझे लगता है कि यह भयावह है कि वहाँ इस के लिए कोई स्पष्ट समाधान बनाने के साथ इस तरह के एक आम परिस्थिति है ...

मुझे मिल गया है इन कक्षाओं:

# models.py 
class Company(models.Model): 
    # ... 
class Contract(models.Model): 
    company = models.ForeignKey(Company) 
    locations = models.ManyToManyField('Location') 
class Location(models.Model): 
    company = models.ForeignKey(Company) 

यह बनाता है कंपनी के लिए व्यवस्थापक स्थापित करते समय एक समस्या, क्योंकि इसमें अनुबंध और स्थान दोनों के लिए इनलाइन है, और स्थान के लिए अनुबंध के एम 2 एम विकल्प ठीक से उस कंपनी के अनुसार फ़िल्टर नहीं किए जाते हैं, जिसे आप वर्तमान में संपादित कर रहे हैं।

संक्षेप में, मैं कुछ व्यवस्थापक विकल्प की जरूरत है इस तरह कुछ करने के लिए होगा:

# admin.py 
class LocationInline(admin.TabularInline): 
    model = Location 
class ContractInline(admin.TabularInline): 
    model = Contract 
class CompanyAdmin(admin.ModelAdmin): 
    inlines = (ContractInline, LocationInline) 
    inline_filter = dict(Location__company='self') 

अंततः मुझे परवाह नहीं है अगर फ़िल्टर करने की प्रक्रिया आधार CompanyAdmin पर रखा गया था, या उस पर रखा गया था, तो ContractInline। (इनलाइन पर रखकर इसे और अधिक समझ में आता है, लेकिन बेस अनुबंध को 'स्वयं' के रूप में संदर्भित करना मुश्किल हो जाता है।)

क्या वहां कोई भी व्यक्ति है जो इस बुरी तरह से शॉर्टकट के रूप में सरल के रूप में कुछ जानता है? वापस जब मैंने इस तरह की चीज के लिए PHP व्यवस्थापक बनाये, तो इसे मूल कार्यक्षमता माना जाता था! वास्तव में, यह हमेशा स्वचालित था, और अगर आप वास्तव में इसे नहीं चाहते थे तो इसे अक्षम किया जाना था!

3

आप फार्म नहीं बनाया है और क्वेरीसमूह बदलना चाहते है, तो आप कर सकते हैं:

formmodel.base_fields['myfield'].queryset = MyModel.objects.filter(...) 

यह सुंदर उपयोगी होता है जब आप सामान्य विचारों का उपयोग कर रहे हैं!

15

class AddPhotoToProject(CreateView): 
    """ 
    a view where a user can associate a photo with a project 
    """ 
    model = Connection 
    form_class = CreateConnectionForm 


    def get_context_data(self, **kwargs): 
     context = super(AddPhotoToProject, self).get_context_data(**kwargs) 
     context['photo'] = self.kwargs['pk'] 
     context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user) 
     return context 
    def form_valid(self, form): 
     pobj = Photo.objects.get(pk=self.kwargs['pk']) 
     obj = form.save(commit=False) 
     obj.photo = pobj 
     obj.save() 

     return_json = {'success': True} 

     if self.request.is_ajax(): 

      final_response = json.dumps(return_json) 
      return HttpResponse(final_response) 

     else: 

      messages.success(self.request, 'photo was added to project!') 
      return HttpResponseRedirect(reverse('MyPhotos')) 

कि का सबसे महत्वपूर्ण हिस्सा CreateView जैसा एक आम दृश्य के साथ ऐसा करने के लिए ... ...

context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user) 

, read my post here

37

यह आसान है, और Django 1.4:

class ClientAdminForm(forms.ModelForm): 
    def __init__(self, *args, **kwargs): 
     super(ClientAdminForm, self).__init__(*args, **kwargs) 
     # access object through self.instance... 
     self.fields['base_rate'].queryset = Rate.objects.filter(company=self.instance.company) 

class ClientAdmin(admin.ModelAdmin): 
    form = ClientAdminForm 
    .... 
के साथ काम करता है

आप एक फार्म कक्षा में इस निर्दिष्ट करने की आवश्यकता नहीं है, लेकिन ModelAdmin में सीधे यह कर सकते हैं, जैंगो पहले से ही ModelAdmin पर इस में निर्मित विधि (डॉक्स से) भी शामिल है के रूप में:

ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)¶ 
'''The formfield_for_foreignkey method on a ModelAdmin allows you to 
    override the default formfield for a foreign keys field. For example, 
    to return a subset of objects for this foreign key field based on the 
    user:''' 

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

एक ऐसा करने के लिए भी शानदार तरीका (उदाहरण के लिए फ्रंट-एंड एडमिन इंटरफेस बनाने में जो उपयोगकर्ता पहुंच सकते हैं) मॉडलएडमिन को उप-वर्ग करना है और फिर नीचे दिए गए तरीकों को बदलना है। शुद्ध परिणाम एक उपयोगकर्ता इंटरफ़ेस है जो केवल उन्हें उन सामग्री से दिखाता है जो उनसे संबंधित हैं, जबकि आपको (एक सुपर-उपयोगकर्ता) सबकुछ देखने की इजाजत देता है।

मैंने चार विधियों को ओवरराइड कर दिया है, पहले दो उपयोगकर्ता को कुछ भी हटाने के लिए असंभव बनाते हैं, और यह व्यवस्थापक साइट से हटाए गए बटन को भी हटा देता है।

तीसरे को रद्द कर किसी भी प्रश्न है कि (उदाहरण में 'उपयोगकर्ता या सिर्फ एक उदाहरण के रूप में' साही '() का संदर्भ होता है फिल्टर।

पिछले ओवरराइड मॉडल फिल्टर करने के लिए किसी भी foreignkey क्षेत्र फिल्टर विकल्प मूल क्वेरीसेट के समान उपलब्ध हैं।

इस तरह, आप फ्रंट-फेस एडमिन साइट को प्रबंधित करने में आसान प्रदर्शन कर सकते हैं जो उपयोगकर्ताओं को अपनी ऑब्जेक्ट्स के साथ गड़बड़ करने की अनुमति देता है, और आपको टाइप करना याद रखना नहीं है विशिष्ट मॉडलएडमिन फ़िल्टर जो हमने ऊपर के बारे में बात की थी।

class FrontEndAdmin(models.ModelAdmin): 
    def __init__(self, model, admin_site): 
     self.model = model 
     self.opts = model._meta 
     self.admin_site = admin_site 
     super(FrontEndAdmin, self).__init__(model, admin_site) 

निकालें 'नष्ट' बटन: पर सभी foreignkey क्षेत्रों के लिए

def get_queryset(self, request): 
     if request.user.is_superuser: 
      try: 
       qs = self.model.objects.all() 
      except AttributeError: 
       qs = self.model._default_manager.get_queryset() 
      return qs 

     else: 
      try: 
       qs = self.model.objects.all() 
      except AttributeError: 
       qs = self.model._default_manager.get_queryset() 

      if hasattr(self.model, ‘user’): 
       return qs.filter(user=request.user) 
      if hasattr(self.model, ‘porcupine’): 
       return qs.filter(porcupine=request.user.porcupine) 
      else: 
       return qs 

फिल्टर विकल्प:

def get_actions(self, request): 
     actions = super(FrontEndAdmin, self).get_actions(request) 
     if 'delete_selected' in actions: 
      del actions['delete_selected'] 
     return actions 

रोकता अनुमति

def has_delete_permission(self, request, obj=None): 
     return False 

फिल्टर वस्तुओं है कि व्यवस्थापक साइट पर देखी जा सकती है हटाना व्यवस्थापक साइट:

def formfield_for_foreignkey(self, db_field, request, **kwargs): 
     if request.employee.is_superuser: 
      return super(FrontEndAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) 

     else: 
      if hasattr(db_field.rel.to, 'user'): 
       kwargs["queryset"] = db_field.rel.to.objects.filter(user=request.user) 
      if hasattr(db_field.rel.to, 'porcupine'): 
       kwargs["queryset"] = db_field.rel.to.objects.filter(porcupine=request.user.porcupine) 
      return super(ModelAdminFront, self).formfield_for_foreignkey(db_field, request, **kwargs) 
+1

और मुझे यह जोड़ना चाहिए कि यह रुचि के समान संदर्भ फ़ील्ड वाले एकाधिक मॉडलडमिन्स के लिए एक सामान्य कस्टम रूप के रूप में अच्छी तरह से काम करता है। – nemesisfixx

+0

यदि आप Django 1.4+ का उपयोग कर रहे हैं तो यह सबसे अच्छा जवाब है –

0

व्यवस्थापक कक्षाओं में get_form को कॉल करके एक और सार्वजनिक तरीका है। यह गैर-डेटाबेस फ़ील्ड के लिए भी काम करता है।

class ChangeKeyValueForm(forms.ModelForm): 
    _terminal_list = forms.ModelMultipleChoiceField( 
queryset=Terminal.objects.all()) 

    class Meta: 
     model = ChangeKeyValue 
     fields = ['_terminal_list', 'param_path', 'param_value', 'scheduled_time', ] 

class ChangeKeyValueAdmin(admin.ModelAdmin): 
    form = ChangeKeyValueForm 
    list_display = ('terminal','task_list', 'plugin','last_update_time') 
    list_per_page =16 

    def get_form(self, request, obj = None, **kwargs): 
     form = super(ChangeKeyValueAdmin, self).get_form(request, **kwargs) 
     qs, filterargs = Terminal.get_list(request) 
     form.base_fields['_terminal_list'].queryset = qs 
     return form 
संबंधित मुद्दे