代码之家  ›  专栏  ›  技术社区  ›  Tom Gruff

如何在Django模型表单中筛选ForeignKey选项?

  •  201
  • Tom Gruff  · 技术社区  · 17 年前

    假设我在我的 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 选项(因为已经通过屏幕上的“添加客户端”按钮选择了该选项) 公司 对此的选择

    我如何在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 我不清楚这是否会在管理界面之外起作用。

    谢谢(这似乎是一个非常基本的要求,但如果我需要重新设计一些东西,我愿意接受建议。)

    7 回复  |  直到 17 年前
        1
  •  256
  •   S.Lott    16 年前

    ForeignKey由django.forms.ModelChoiceField表示,它是一个ChoiceField,其选项是模型查询集。有关详细信息,请参阅参考资料 ModelChoiceField .

    因此,为字段的 queryset 属性取决于您的表单是如何构建的。如果您构建一个显式表单,您将有直接命名的字段。

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

    form.fields["rate"].queryset = ...

        2
  •  145
  •   Tom Gruff    15 年前

    除了S.Lott的答案和becomingGuru在评论中提到的,还可以通过覆盖 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')
    

        3
  •  48
  •   neil.millikin    10 年前

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

    您不需要在form类中指定,但可以直接在ModelAdmin中指定,因为Django已经在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)
    

    实现这一点的一种更巧妙的方法(例如,在创建用户可以访问的前端管理界面时)是将ModelAdmin子类化,然后更改下面的方法。最终的结果是用户界面只显示与他们相关的内容,同时允许您(超级用户)查看所有内容。

    第三个覆盖过滤任何包含引用的查询(在示例“user”或“horcupine”(如图所示)。

    最后一个覆盖过滤模型中的任何foreignkey字段,以过滤与基本queryset相同的可用选项。

    通过这种方式,您可以提供一个易于管理的面向前端的管理站点,允许用户处理他们自己的对象,并且您不必记得键入我们上面提到的特定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
    

    筛选管理网站上所有foreignkey字段的选项:

        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)
    
        4
  •  25
  •   BoltClock    14 年前

    要使用常规视图(如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)
    

    , read my post here

        5
  •  4
  •   Wtower Campi    10 年前

    如果尚未创建表单并希望更改查询集,则可以执行以下操作:

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

    这在使用通用视图时非常有用!

        6
  •  2
  •   Tim    16 年前

    所以,我真的试着去理解这一点,但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)
    

    这在为公司设置管理员时会产生问题,因为它同时具有合同和位置的内联线,并且合同的位置m2m选项未根据您当前编辑的公司进行正确筛选。

    简而言之,我需要一些管理选项来执行以下操作:

    # 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上,还是放在收缩行上。(将其放在内联上更有意义,但这样很难将基本契约引用为“self”。)

        7
  •  1
  •   FloatingKiwi    9 年前

    更公开的方法是在管理类中调用get_表单。它也适用于非数据库字段。例如,我在表单上有一个名为“_terminal_list”的字段,可在特殊情况下用于从get_list(请求)中选择几个终端项,然后根据请求进行过滤。用户:

    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
    
        8
  •  -1
  •   jorge4larcon    5 年前

    根据Django文档,您可以使用 __init__

    https://docs.djangoproject.com/en/3.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey

    class CountryAdminForm(forms.ModelForm):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.fields['capital'].queryset = self.instance.cities.all()
    
    class CountryAdmin(admin.ModelAdmin):
        form = CountryAdminForm