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

2024-12-24 08:55:00
admin
原创
135
摘要:问题描述:假设我的中有以下内容models.py:class Company(models.Model): name = ... class Rate(models.Model): company = models.ForeignKey(Company) name = ... class...

问题描述:

假设我的中有以下内容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`ClientsClientRateCompany's RatesCompany'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,而且我也不清楚这是否可以在管理界面之外工作。

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


解决方案 1:

ForeignKey 由 django.forms.ModelChoiceField 表示,它是一个 ChoiceField,其选择是一个模型查询集。请参阅ModelChoiceField的参考。

因此,为字段的queryset属性提供一个查询集:

form.fields["rate"].queryset = Rate.objects.filter(company_id=the_company.id)

这是在视图中明确完成的。无需进行任何改动。

解决方案 2:

除了 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 博客材料,其中有很多优秀的材料。

解决方案 3:

这很简单,并且适用于 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 子类化,然后更改下面的方法。最终结果是用户界面仅向用户显示与他们相关的内容,同时允许您(超级用户)查看所有内容。

我重写了四种方法,前两种方法使用户无法删除任何内容,并且还从管理站点中删除了删除按钮。

第三个覆盖会过滤任何包含引用的查询(在示例中为“用户”或“豪猪”(仅作为说明)。

最后的覆盖会过滤模型中的任何外键字段,以过滤与基本查询集相同的可用选项。

通过这种方式,您可以呈现一个易于管理的前端管理站点,允许用户处理自己的对象,而您不必记住输入我们上面讨论的特定 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)

解决方案 4:

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

,请在此处阅读我的帖子

解决方案 5:

如果您尚未创建表单并想要更改查询集,您可以执行以下操作:

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

当您使用通用视图时这非常有用!

解决方案 6:

所以,我真的试图理解这一点,但似乎 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 上,还是放在 ContractInline 上。(将其放在内联上更有意义,但很难将基础 Contract 引用为“自身”。)

有谁知道有这么简单、急需的快捷方式吗?当我为这类事情创建 PHP 管理员时,这被认为是基本功能!事实上,它一直是自动的,如果你真的不想要它,就必须禁用它!

解决方案 7:

一种更公开的方式是在 Admin 类中调用 get_form。它也适用于非数据库字段。例如,这里我在表单上有一个名为“_terminal_list”的字段,可以在特殊情况下用于从 get_list(request) 中选择几个终端项,然后根据 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

解决方案 8:

在运行时(例如在 CreateView 中)限制 ModelForm 的 ForeignKey 字段的选择的一个好方法是通过在视图中进行覆盖limit_choices_to来设置。base_fields['field_name']`get_form_class()`

例如,当创建客户端时,将费率选择限制为 URL 中所标识的公司费率:

class ClientCreateView(LoginRequired, CreateView):
    model = Client
    fields = '__all__'
    
    def get_form_class(self):
        modelform = super().get_form_class()
        modelform.base_fields['rate'].limit_choices_to = {'company': self.kwargs['company']}
        return modelform

解决方案 9:

根据 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
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用