首页 文章

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

提问于
浏览
199

说我的 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 ,每个都有 RatesClients 的范围 . 每个 Client 都应该有一个基础 Rate ,它是从它的父级 Company's Rates 中选择的,而不是另一个 Company's Rates .

在创建用于添加 Client 的表单时,我想删除 Company 选项(因为已经通过 Company 页面上的"Add Client"按钮选择了该选项)并将 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界面之外工作 .

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

7 回答

  • 15

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

    这在视图中明确完成 . 没有黑客攻击 .

  • 215

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

  • 2

    这很简单,适用于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'或'porcupine'中(仅作为说明) .

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

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

    筛选管理站点上所有foreignkey字段的选项:

    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)
    
  • 122

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

  • 0

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

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

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

  • 40

    所以,我真的试图理解这一点,但似乎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)
    

    这在设置Admin for Company时会产生问题,因为它具有 Contract 和位置的内联,并且根据您当前正在编辑的公司未正确过滤Contract的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管理员时,这被认为是基本功能!事实上,它总是自动的,如果你真的不想要它就必须被禁用!

  • 4

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

相关问题