如何在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有一系列的RatesClients 。 每个Client应该有一个从母公司的Rate中select的基本Rate ,而不是另一个Company's Rates

在创build添加Client的表单时,我想删除Company选项(因为已经通过Company页面上的“添加客户端”buttonselect了该选项),并限制该CompanyRate选项。

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 ,我不清楚,如果这将工作以外的pipe理界面无论如何。

谢谢。 (这似乎是一个非常基本的要求,但如果我应该重新devise一些我可以接受的build议。)

ForeignKey由django.forms.ModelChoiceField表示,它是一个ChoiceField,其select是模型QuerySet。 请参阅ModelChoiceField的参考。

所以,提供一个QuerySet到字段的queryset属性。 取决于你的表单是如何build立的。 如果你build立一个明确的表单,你将会有直接命名的字段。

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

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

这在视图中是明确的。 没有黑客周围。

除了S.Lott的回答以及在评论中提到的成为ModelForm.__init__ ,可以通过重写ModelForm.__init__函数来添加查询集filter。 (这可以很容易地适用于常规forms)它可以帮助重复使用,并保持视图function整洁。

 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}) 

如果你有很多模型需要通用的filter(通常我声明一个抽象的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) 

更简单的方式来做到这一点(例如创build用户可以访问的前端pipe理界面)是对ModelAdmin进行子类化,然后改变下面的方法。 最终的结果是一个用户界面,只显示与他们相关的内容,同时允许你(超级用户)看到一切。

我已经覆盖了四种方法,前两种方法使用户无法删除任何内容,并且还从pipe理站点删除了删除button。

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

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

通过这种方式,您可以提供一个易于pipe理的前台pipe理站点,该站点允许用户混淆自己的对象,而且您不必记得input上面介绍的特定的ModelAdminfilter。

 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) 

删除'删除'button:

  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 

过滤可在pipe理网站上查看的对象:

  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 

过滤pipe理网站上所有外键字段的选项:

  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) 

, 在这里阅读我的post

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

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

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

所以,我真的试图理解这一点,但似乎Django仍然没有使这个非常简单。 我并不是那么愚蠢,但我只是看不到任何(有点)简单的解决scheme。

我发现,为了这样的事情必须重写pipe理视图通常很丑陋,我发现每个例子都不能完全应用于pipe理视图。

对于我所制作的模型来说,这是一个普遍的情况,我觉得这个问题没有明显的解决办法。

我有这些类:

 # 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) 

这在设置Admin for Company时会产生问题,因为它已经为合同和位置内联了,而根据您当前正在编辑的公司,合同的位置m2m选项未被正确过滤。

总之,我需要一些pipe理选项来做这样的事情:

 # 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上。 (把它放在内联上更有意义,但是很难将基础契约称为“自我”)。

有没有人知道这个急需的捷径? 当我为这种事情做PHPpipe理员时,这被认为是基本的function! 事实上,它总是自动的,如果你真的不想要,它必须被禁用!

更公开的方法是在Admin类中调用get_form。 它也适用于非数据库字段。 例如,在这里我有一个名为'_terminal_list'的字段,可以在特殊情况下用于从get_list(request)中select几个terminal项,然后基于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