मैं एक Django ModelForm में ForeignKey विकल्पों को कैसे फ़िल्टर करूं?


227

कहो मैं अपने में निम्नलिखित है models.py:

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)

यानी एक से अधिक कर रहे हैं 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और मैं स्पष्ट नहीं हूं कि क्या यह वैसे भी व्यवस्थापक इंटरफ़ेस के बाहर काम करेगा।

धन्यवाद। (यह एक बहुत ही बुनियादी अनुरोध की तरह लगता है, लेकिन अगर मुझे सुझाव के लिए खुले हुए कुछ को फिर से डिज़ाइन करना चाहिए।)


संकेत के लिए "limit_choices_to" के लिए धन्यवाद। यह आपके प्रश्न को हल नहीं करता है, लेकिन मेरा :-) डॉक्स: docs.djangoproject.com/en/dev/ref/models/fields/…
guettli

जवाबों:


243

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

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

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

यदि आप डिफ़ॉल्ट मॉडलफ़ॉर्म ऑब्जेक्ट लेते हैं, form.fields["rate"].queryset = ...

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


ठीक है, यह आशाजनक लगता है। मैं संबंधित फ़ील्ड ऑब्जेक्ट को कैसे एक्सेस करूं? form.company.QuerySet = Rate.objects.filter (company_id = the_company.id)? या एक शब्दकोश के माध्यम से?
टॉम

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

8
क्या फार्म की __init__विधि में फ़ील्ड की क्वेरी सेट करना बेहतर नहीं होगा ?
लक्ष्मण प्रसाद

1
@ अंतिम टिप्पणी सही नहीं है (या मेरी साइट को काम नहीं करना चाहिए :)। आप अपने ओवरराइड पद्धति में सुपर (...)। Init__ कॉल का उपयोग करके सत्यापन डेटा को पॉप्युलेट कर सकते हैं। यदि आप इनमें से कई क्वेरी को बदल रहे हैं, तो यह init विधि को ओवरराइड करके उन्हें पैकेज करने के लिए बहुत अधिक सुरुचिपूर्ण है ।
माइकल

3
@ स्लेट चीयर्स, मैंने एक उत्तर जोड़ा है क्योंकि यह समझाने के लिए 600 से अधिक वर्ण लेगा। भले ही यह प्रश्न पुराना है, यह एक उच्च Google स्कोर प्राप्त कर रहा है।
माइकल

135

एस.लॉट के जवाब के अलावा और जैसा कि टिप्पणियों में उल्लेख किया जा रहा है, इसके कारण 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 ब्लॉग सामग्री को पुनर्स्थापित कर रहा हूं जिसमें से कई अच्छे हैं।


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

6
मुझे यह उत्तर बेहतर लगता है, मुझे लगता है कि व्यू क्लास के बजाय फॉर्म क्लास में फॉर्म इनिशियलाइज़ेशन लॉजिक को इनकैप्सुलेट करना क्लीनर है। चीयर्स!
सममितीय

44

यह सरल है, और 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
    ....

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

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)

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

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

तीसरा ओवरराइड किसी भी क्वेरी को फ़िल्टर करता है जिसमें (उदाहरण के लिए 'उपयोगकर्ता' या 'साही' (केवल एक उदाहरण के रूप में) का संदर्भ होता है।

अंतिम ओवरराइड मॉडल में किसी भी विदेशी फ़ील्ड को फ़िल्टर करने के लिए उपलब्ध विकल्प को मूल क्वेरीसेट के समान फ़िल्टर करता है।

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

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)

'हटाएं' बटन हटाएं:

    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 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 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

यदि आप Django
1.4+

16

एक सामान्य दृश्य के साथ ऐसा करने के लिए, CreateView की तरह ...

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'))

उस का सबसे महत्वपूर्ण हिस्सा ...

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

, मेरी पोस्ट यहाँ पढ़ें


4

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

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

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


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 पर रखा गया था, या यदि यह अनुबंधइनलाइन पर रखा गया था। (इनलाइन पर इसे रखने से अधिक समझ में आता है, लेकिन यह आधार अनुबंध को 'स्वयं' के रूप में संदर्भित करना कठिन बनाता है।)

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


0

एक अधिक सार्वजनिक तरीका एडमिन क्लासेस में get_form को कॉल करके है। यह गैर-डेटाबेस फ़ील्ड के लिए भी काम करता है। उदाहरण के लिए, मेरे पास फॉर्म पर '_terminal_list' नामक एक फ़ील्ड है जिसे विशेष मामलों में get_list (अनुरोध) से कई टर्मिनल आइटम चुनने के लिए उपयोग किया जा सकता है, फिर request.user के आधार पर फ़िल्टरिंग करें:

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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.