假设我的 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
都应该有一个从其父 Company's Rates
而不是另一个 Company's Rates
中选择的基础 Rate
。
创建用于添加 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
,而且我不清楚这是否可以在管理界面之外工作。
谢谢。 (这似乎是一个非常基本的要求,但如果我应该重新设计一些东西,我愿意接受建议。)
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 的回答和评论中提到的 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 博客材料,其中有很多不错的。
这很简单,适用于 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)
要使用通用视图执行此操作,例如 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)
如果您尚未创建表单并想要更改查询集,您可以执行以下操作:
formmodel.base_fields['myfield'].queryset = MyModel.objects.filter(...)
当您使用通用视图时,这非常有用!
所以,我真的试图理解这一点,但似乎 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 引用为“self”。)
有没有人知道像这条急需的捷径一样简单的事情?当我为这类事情做 PHP 管理员时,这被认为是基本功能!事实上,它总是自动的,如果你真的不想要它就必须禁用它!
更公开的方式是在 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
在运行时(例如在 CreateView 中)限制 ModelForm 的 ForeignKey 字段的选择的一种好方法是通过覆盖视图中的 get_form_class()
为 base_fields['field_name']
设置 limit_choices_to
。
例如,在创建客户端时,将 Rate 选项限制为 URL 中标识的 Company 选项:
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
根据 Django 文档,您可以使用模型表单的 __init__
方法将过滤器应用于默认查询集。
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
不定期副业成功案例分享
__init__
方法中设置字段的查询集不是更好吗?