如何限制表单集中的字段选择?

Posted

技术标签:

【中文标题】如何限制表单集中的字段选择?【英文标题】:How to limit field choices in formset? 【发布时间】:2012-04-02 08:10:33 【问题描述】:

我在限制表单集中的可选选项时遇到问题。我有以下模型:员工、部门、项目、项目类型、成员资格和角色。员工可以在表单集中添加/删除他们为给定部门项目所扮演的角色,表单应将可选项目限制为仅属于员工所属部门的项目。

型号:

class Department(models.Model):
    name = models.CharField(max_length=20)
    def __unicode__(self):
    return self.name

class Employee(models.Model):
    fname = models.CharField(max_length=15)
    department = models.ForeignKey(Department)
    def __unicode__(self):
        return self.fname

class Projecttype(models.Model):
    name = models.CharField(max_length=20)
    def __unicode__(self):
        return self.name

class Project(models.Model):
    projecttype = models.ForeignKey(Projecttype)
    department = models.ForeignKey(Department)
    members = models.ManyToManyField(Employee, through='Membership')
    def __unicode__(self):
       return "%s > %s" % (self.department, self.projecttype)

class Role(models.Model):
    name = models.CharField(max_length=20)
    def __unicode__(self):
       return self.name

class Membership(models.Model):
    project = models.ForeignKey(Project, null=True)
    department = models.ForeignKey(Department)
    employee = models.ForeignKey(Employee)
    role = models.ManyToManyField(Role, blank=True, null=True)
    class Meta:
        unique_together = (("project", "employee",),)

查看:

def employee_edit(request, employee_id):
    i = get_object_or_404(Employee, pk=employee_id)
    MembershipFormSet = modelformset_factory(Membership, exclude=('department', 'employee'),)
    f = MembershipFormSet(queryset=Membership.objects.filter(employee=i),)
    return render_to_response('gcs/edit.html', 'item': i, 'formset': f, , context_instance=RequestContext(request))

目前,欧盟可以为任何部门的项目选择一个角色。它的行为是这样的:

项目选项:

Projects.objects.all()

我想通过以下方式限制项目:LIMIT PROJECT CHOCIES TO:

Projects.objects.filter(department=i.department)

【问题讨论】:

This stack overflow question 非常相似。有两种方法有效。 1) 在其__init__ 方法中创建一个将employee 作为参数的表单并使用curry 函数。 2)在视图函数中构建表单类。如果您不需要在其他地方重用表单,我发现第二种方法更容易。 python 新手,您是否像在 model.py 中那样在视图中构造表单类? 我已将我的评论扩展为下面的答案。 【参考方案1】:

这个Stack Overflow question 非常相似。我喜欢马修回答的方法,您可以在一个可以通过闭包访问员工的函数中动态构建表单。在你的情况下,你想要这样的东西:

from django.http import HttpResponseRedirect

def make_membership_form(employee):
    """
    Returns a Membership form for the given employee, 
    restricting the Project choices to those in the 
    employee's department. 
    """
    class MembershipForm(forms.ModelForm):
        project = forms.ModelChoiceField(queryset=Projects.objects.filter(department=employee.department))
        class Meta:
            model = Membership
            excludes = ('department', 'employee',)
    return MembershipForm

def employee_edit(request, employee_id):
    employee = get_object_or_404(Employee, pk=employee_id)
    # generate a membership form for the given employee
    MembershipForm = make_membership_form(employee)
    MembershipFormSet = modelformset_factory(Membership, form=MembershipForm)

    if request.method == "POST":
        formset = MembershipFormSet(request.POST, queryset=Membership.objects.filter(employee=employee))
        if formset.is_valid():
            instances = formset.save(commit=False)
                for member in instances:
                    member.employee = employee
                    member.department = employee.department
                    member.save()
            formset.save_m2m()
            # redirect after successful update
            return HttpResponseRedirect("")
    else:
        formset = MembershipFormSet(queryset=Membership.objects.filter(employee=employee),)
    return render_to_response('testdb/edit.html', 'item': employee, 'formset': formset, , context_instance=RequestContext(request))

【讨论】:

类 MembershipForm 应该在视图中还是在模型中? 表单并不真正属于models.py。在较大的 Django 应用程序中,您有时会有一个 forms.py 模块。在这种情况下,您可以根据需要将make_membership_form 函数放在views.py 中。 我修复了employee_edit 视图的缩进。确保它在您的代码中是正确的。 非常感谢您,我不得不修改您的代码。但一切正常!我已经更新了我的问题以反映对我有用的内容。再次感谢您! 很高兴你能成功!我已经更新了我的答案以包括您所做的修改。请注意更新成功后的重定向 - 如果用户在成功更新后点击刷新,它会阻止用户重新提交请求。【参考方案2】:

编辑

该死。所有这些打字都是因为我错过了一部分代码;)。正如 @Alasdair 在 cmets 中提到的那样,您已将 department 从表单中排除,因此您可以使用 Django 来限制它。不过,我将保留我原来的答案,以防万一它可能对其他人有所帮助。

根据您的情况,您只需要:

class MembershipForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(MembershipForm, self).__init__(*args, **kwargs)
        self.fields['project'].queryset = self.fields['project'].queryset.filter(department_id=self.instance.department_id)

然后:

MembershipFormSet = modelformset_factory(Membership, form=MembershipForm, exclude=('department', 'employee'),)

原始答案(供后代使用)

您不能在 Django 中对此进行限制,因为部门的值是可变的,因此项目列表可能会根据当前选择的特定部门而有所不同。为了验证表单,您必须向 Django 提供所有可能允许的项目,因此您唯一的选择是 AJAX。

创建一个视图,该视图将返回一个 JSON 响应,其中包含馈入视图的特定部门的项目。大致如下:

from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import get_list_or_404
from django.utils import simplejson

def ajax_department_projects(request):
    department_id = request.GET.get('department_id')
    if department_id is None:
        return HttpResponseBadRequest()

    project_qs = Project.objects.select_related('department', 'project_type')
    projects = get_list_or_404(project_qs, department__id=department_id)
    data = []
    for p in projects:
        data.append(
            'id': p.id,
            'name': unicode(p),
        )

    return HttpResponse(simplejson.dumps(data), mimetype='application/json')

然后,创建一些 javascript 来在更改部门选择框时获取此视图:

(function($)
    $(document).ready(function()
        var $department = $('#id_department');
        var $project = $('#id_project');

        function updateProjectChoices()
            var selected = $department.val();
            if (selected) 
                $.getJSON('/path/to/ajax/view/', department_id: selected, function(data, jqXHR)
                    var options = [];
                    for (var i=0; i<data.length; i++) 
                        output = '<option value="'+data[i].id+'"';
                        if ($project.val() == data[i].id) 
                            output += ' selected="selected"';
                        
                        output += '>'+data[i].name+'</option>';
                        options.push(output);
                    
                    $project.html(options.join(''));
                );
            
        

        updateProjectChoices();
        $project.change(updateProjectChoices);
    );
)(django.jQuery);

【讨论】:

department 不可更改 - 请注意 exclude=('department', 'employee'),) 天哪,谢谢,我已经在这个问题上停留了一周。我见过类似的答案,但永远无法完全理解发生了什么。你能告诉我这个编码向导是什么意思吗?参考文献? 另外,我的插图还有一个额外的空白表格,用于创建新的会员条目。但是这个新表单中的项目字段不包含任何选项...为什么我的选项现在不见了?

以上是关于如何限制表单集中的字段选择?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle Apex:如何将表单中的数字字段限制为仅允许数字?

Django:如何更改内联表单集中的字段小部件

最初在 django 内联表单集中设置不同的外键值

如何从 ZF2 中子实体的字段集中调用单个表单元素

Django Newbie - 使用多字段表单,如何消除查询集中的空字段

如何使 Ninja Forms 3.1 中的字段唯一?