如何在Django ModelForm中过滤ForeignKey选项?
假设我在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
,每家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
,我不清楚这是否可以在Admin界面之外工作。
谢谢。 (这似乎是一个非常基本的要求,但如果我应该重新设计一些我可以接受的建议。)
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 = ...
这是在视图中明确完成的。 没有黑客周围。
除了S.Lott的回答以及在评论中提到成为ModelForm.__init__
,还可以通过重写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的博客资料,其中有许多好的博客资料。
这很简单,适用于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'或'豪猪'中(如图所示)。
最后一个覆盖过滤模型中的任何外键字段,以过滤与基本查询集相同的可用选项。
通过这种方式,您可以提供一个易于管理的前台管理站点,该站点允许用户混淆他们自己的对象,而且您不必记住键入上面介绍的特定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)
链接地址: http://www.djcxy.com/p/38647.html
上一篇: How do I filter ForeignKey choices in a Django ModelForm?