当前位置:  开发笔记 > 编程语言 > 正文

如何在Django ModelForm中过滤ForeignKey选项?

如何解决《如何在DjangoModelForm中过滤ForeignKey选项?》经验,为你挑选了4个好方法。

说我的我有以下内容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,每个都有RatesClients.每个都Client应该有一个Rate从它的父级Company's Rates而不是另一个基础中选择的基础Company's Rates.

在创建用于添加a的表单时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,我不清楚是否这将在Admin界面之外工作.

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



1> S.Lott..:

ForeignKey由django.forms.ModelChoiceField表示,它是ChoiceField,其选择是模型QuerySet.请参阅ModelChoiceField的参考.

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

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

如果采用默认的ModelForm对象, form.fields["rate"].queryset = ...

这在视图中明确完成.没有黑客攻击.


在表单的`__init__`方法中设置字段的查询集不是更好吗?
@Slott欢呼,我添加了一个答案,因为需要超过600个字符来解释.即使这个问题很老,它的谷歌得分也很高.

2> michael..:

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

如果您在许多模型上需要通用过滤器(通常我声明一个抽象的Form类),这对重用来说很有用.例如

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博客材料,其中有许多好的博客材料.


我更喜欢这个答案,我认为将表单初始化逻辑封装在表单类中而不是在view方法中更清晰.干杯!

3> neil.milliki..:

这很简单,适用于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中指定它,因为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'或'porcupine'(仅作为说明).

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

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

要使用通用视图执行此操作,例如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)

,在这里阅读我的帖子

推荐阅读
sx-March23
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有