2008-11-15 108 views
188

说在我models.py我有以下几点:如何在Django ModelForm中过滤ForeignKey选项?

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应该有一个从其父母Company's Rates中选择的基地Rate,而不是另一个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界面之外工作。

谢谢。 (这似乎是一个非常基本的请求,但如果我应该重新设计一些我愿意接受的建议。)

回答

200

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 = ...

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

+0

好,听起来有希望。我如何访问相关的Field对象? form.company.QuerySet = Rate.objects.filter(company_id = the_company.id)?或通过字典? – Tom 2008-11-15 02:43:15

+1

好的,谢谢扩展例子,但我似乎必须使用form.fields [“rate”]。queryset以避免“'ClientForm'对象没有'rate'属性,我错过了什么? (你的例子应该是form.rate.queryset也是一致的。) – Tom 2008-11-15 03:36:09

+0

非常好,谢谢澄清。为了将来参考,当您通过评论编辑答案时可能值得注意,因为编辑不会显示在我的用户页面的答复选项卡中。 – Tom 2008-11-16 03:53:30

115

除了S.Lott的回答和作为成为导师在评论中提到的,它可能通过重写ModelForm.__init__函数来添加queryset筛选器。 (这可以很容易地适用于常规形式),它可以帮助重复使用,并保持视图功能整洁。

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

这可重用有用的说,如果你有需要的很多车型在常用的过滤器(通常我宣布一个抽象的形式类)。例如。

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

2

所以,我真的试图理解这一点,但似乎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。 (将它放置在内联更有意义,但它使得很难将基本契约称为“自己”)。

有没有人知道这个急需的快捷方式?当我为这类事情做PHP管理员时,这被认为是基本功能!事实上,它总是自动的,如果你真的不想要它,必须禁用!

3

如果您尚未创建表格,要更改查询集,你可以这样做:

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

这是非常有用的,当你使用的是通用的看法!

15

要使用通用视图做到这一点,像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) 

read my post here

37

这很简单,并与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) 
0

更公开的方法是通过调用Admin类中的get_form。它也适用于非数据库字段。例如在这里,我已经然后过滤基于request.user被叫可以在特殊情况下可以用于从get_list(请求)选择几个终端的项目的形式上“_terminal_list”,字段:

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 
相关问题