django:如何限制formset中的字段选择?

时间:2022-02-12 02:33:43

I'm having problems limiting the selectable choices in a formset. I have the following models: Employees, Department, Project, Projecttype, Membership, and Role. An employee can add/remove the roles that they play for a given departments project in the formset, the form should limit the selectable projects to only those belonging to the department that the employee belongs to.

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

MODELS:

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",),)

VIEW:

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

Right now an EU can select a role to play for any departments project. It's acting like this:

现在,欧盟可以为任何部门项目选择一个角色。它的行为如下:

Project Options:

Projects.objects.all()

I want to limit the projects with something like this: LIMIT PROJECT CHOCIES TO:

我想用这样的东西来限制项目:LIMIT PROJECT CHOCIES TO:

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

2 个解决方案

#1


7  

This Stack Overflow question is fairly similar. I like the approach of Matthew's answer, where you build the form dynamically in a function that has access to the employee via closure. In your case, you want something like:

此Stack Overflow问题非常相似。我喜欢Matthew的答案,你可以在一个通过闭包访问员工的函数中动态构建表单。在你的情况下,你想要的东西:

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

#2


3  

EDIT

Darn. All that typing because I missed one part of the code ;). As @Alasdair mentions in the comments, you've excluded department from the form, so you can limit this with Django. I'm going to leave my original answer, though, just in case it might help someone else.

该死。所有打字因为我错过了代码的一部分;)。正如@Alasdair在评论中提到的那样,你已经从表单中排除了部门,所以你可以用Django来限制它。不过,我会留下原来的答案,以防万一它可以帮助别人。

For your circumstances, all you need is:

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

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)

And, then:

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

Original Answer (for posterity)

原始答案(后代)

You can't limit this in Django, because the value for department is changeable, and thus the list of projects can vary depending on which particular department is selected at the moment. In order to validate the form, you'll have to feed all possible projects that could be allowed to Django, so your only option is AJAX.

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

Create a view that will return a JSON response consisting of projects for a particular department fed into the view. Something along the lines of:

创建一个视图,该视图将返回一个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')

Then, create a bit of JavaScript to fetch this view whenever the department select box is changed:

然后,每当更改部门选择框时,创建一些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);

#1


7  

This Stack Overflow question is fairly similar. I like the approach of Matthew's answer, where you build the form dynamically in a function that has access to the employee via closure. In your case, you want something like:

此Stack Overflow问题非常相似。我喜欢Matthew的答案,你可以在一个通过闭包访问员工的函数中动态构建表单。在你的情况下,你想要的东西:

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

#2


3  

EDIT

Darn. All that typing because I missed one part of the code ;). As @Alasdair mentions in the comments, you've excluded department from the form, so you can limit this with Django. I'm going to leave my original answer, though, just in case it might help someone else.

该死。所有打字因为我错过了代码的一部分;)。正如@Alasdair在评论中提到的那样,你已经从表单中排除了部门,所以你可以用Django来限制它。不过,我会留下原来的答案,以防万一它可以帮助别人。

For your circumstances, all you need is:

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

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)

And, then:

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

Original Answer (for posterity)

原始答案(后代)

You can't limit this in Django, because the value for department is changeable, and thus the list of projects can vary depending on which particular department is selected at the moment. In order to validate the form, you'll have to feed all possible projects that could be allowed to Django, so your only option is AJAX.

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

Create a view that will return a JSON response consisting of projects for a particular department fed into the view. Something along the lines of:

创建一个视图,该视图将返回一个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')

Then, create a bit of JavaScript to fetch this view whenever the department select box is changed:

然后,每当更改部门选择框时,创建一些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);