无法理解modelformset_factory和ModelBaseFormset

时间:2014-05-13 13:39:09

标签: django django-models django-views

我正在努力理解我的应用中的BaseModelFormSet和modelformset_factory。我想我得到了这个概念但是实现它时遇到了问题。下面的代码工作正常但我认为由于我缺乏理解而过于复杂。

这可以,但是当我想根据当前表单的userid(而不是登录的用户)修改email_list查询集时,我遇到了问题。基本上,这个代码有两个模型。第一个是Family Model(和FamilyBaseFormSet),很好。第二个是Family Member模型,它是包含每个家庭成员的userid的模型。如果该用户标识是is_staff的一部分,那么我希望email_list查询对它们不同(不同的过滤器)。当我修改它时,整个站点都会挂起。

所以,这是我的问题: 1.有没有更简单的方法来编写此代码?也许删除FamilyBaseFormSet和FamilyMemberBaseFormSet?你可以看到我拥有它们的唯一原因是修改form.fields 2.如何确定当前表单(当前系列成员)用户ID是否为员工?如果是这样,我该如何正确设置查询?


class FamilyBaseFormSet(BaseModelFormSet):
    def add_fields(self, form, index):
    super(FamilyBaseFormSet, self).add_fields(form, index)
    form.fields['state'].widget.attrs['class'] = 'input-mini'
        form.fields['zip_code'].widget.attrs['class'] = 'input-small'
    form.fields['emergency_notes'].widget.attrs['rows'] = '2'
    form.fields['notes'].widget = forms.HiddenInput()
    form.fields['is_active'].widget = forms.HiddenInput()
    form.fields['family_benefits'].widget = forms.HiddenInput()

class FamilyMemberBaseFormSet(BaseModelFormSet):
    def add_fields(self, form, index):
    super(FamilyMemberBaseFormSet, self).add_fields(form, index)
        form.fields['email_list'].queryset = EmailList.objects.filter(is_active=True)
    form.fields['first_name'].widget.attrs['class'] = 'input-small'
    form.fields['first_name'].required = True
    form.fields['first_name'].error_messages = {'required': 'Please enter a first name'}
    form.fields['middle_name'].widget.attrs['class'] = 'input-mini'
    form.fields['last_name'].widget.attrs['class'] = 'input-small'
    form.fields['last_name'].required = True
    form.fields['last_name'].error_messages = {'required': 'Please enter a last name'}
    form.fields['state'].widget.attrs['class'] = 'input-mini'
    form.fields['zip_code'].widget.attrs['class'] = 'input-small'
    form.fields['gender'].widget = forms.HiddenInput()
    form.fields['family'].widget = forms.HiddenInput()
    form.fields['username'].widget = forms.HiddenInput()
    form.fields['password'].widget = forms.HiddenInput()
    form.fields['last_login'].widget = forms.HiddenInput()
    form.fields['date_joined'].widget = forms.HiddenInput()
    form.fields['family_member_role'].widget = forms.HiddenInput()
    form.fields['notes'].widget = forms.HiddenInput()
    form.fields['is_superuser'].widget = forms.HiddenInput()
    form.fields['is_active'].widget = forms.HiddenInput()
    form.fields['is_staff'].widget = forms.HiddenInput()

def manage_family_member(request):

    FamilyInlineFormSet = modelformset_factory(Family, extra=0, formset=FamilyBaseFormSet)
    FamilyMemberInlineFormSet = modelformset_factory(FamilyMember,
    extra=0, formset=FamilyMemberBaseFormSet)
    email_list_description = EmailList.objects.filter(is_active=True)
    if request.method == "POST":
    family_formset = FamilyInlineFormSet(request.POST,  request.FILES,
        queryset=Family.objects.filter(id=request.user.family.id), prefix='f')
    family_member_formset = FamilyMemberInlineFormSet(request.POST, request.FILES,
        queryset=FamilyMember.objects.filter(family=request.user.family.id), prefix='fm')
    if family_formset.is_valid() and family_member_formset.is_valid():
        family_formset.save()
        family_member_formset.save()
        return redirect('/school/thanks/')
    else:
    family_formset = FamilyInlineFormSet(queryset=Family.objects.filter(id=request.user.family.id), prefix='f')
    family_member_formset = FamilyMemberInlineFormSet(queryset=FamilyMember.objects.filter(family=request.user.family.id), prefix='fm')

    context = RequestContext(request,{
    'email_list_description': email_list_description,
    'family_formset': family_formset,
    'family_member_formset': family_member_formset,
    })
    return render_to_response("school/family/manage_family_members.html", context)

1 个答案:

答案 0 :(得分:2)

您的代码不仅比以前复杂得多,而且还有一些非常重要的安全漏洞。根据其中的字段,我认为FamilyMember是您的自定义用户模型。

让我们说我是一个可以编辑我家人的普通家庭成员。我得到一个包含大量隐藏输入的表单,包括is_staffis_superuser。任何带有网页调试工具栏的nitwit都可以将这些隐藏输入字段的值更改为True。现在我将这些字段的值更改为True,在表单中发送,并且tada:我是一个超级用户,可以操作您的完整网站。

HiddenInput窗口小部件是实际服务器端验证和安全性的替代品。隐藏输入字段的唯一有效用例是用户通常不应该以该特定形式更改的默认值,但是在更改时不会构成任何安全处理,因为用户以某种方式允许更改它,或者因为服务器会覆盖更改或拒绝请求。

您需要做的是在formset中使用自定义表单,该表单仅包含您要包含的字段。对于您的FamilyMember模型,可以是以下ModelForm

class FamilyMemberForm(forms.ModelForm):
    first_name = forms.CharField(max_length=50, required=True, 
                                 widget=forms.TextInput(attrs={'class': 'input-small'}),
                                 error_messages={'required': 'Please enter a first name'})
    ...

    class Meta:
        model = FamilyMember
        fields = ['first_name', 'middle_name', 'last_name', 'state', 'zip_code']
        widgets = {
            'middle_name': forms.TextInput(attrs={'class': 'input-mini'}),
            ...
        }

这将确保用户只能编辑他们应该编辑的字段(您明确添加到表单中的字段),并且会使您的formset类变得更加复杂。您可以使用form参数将表单传递给formset工厂:

family_member_formset = FamilyMemberInlineFormSet(request.POST, request.FILES, 
        queryset=FamilyMember.objects.filter(family=request.user.family.id), prefix='fm',
        form=FamilyMemberForm)

关于你的第二个问题,我不完全确定你想要什么。目前的家庭成员,您的意思是登录用户?如果是这样的话,这就足够了:

if request.user.is_staff:
    email_list_description = <queryset for staff members>
else:
    email_list_description = <queryset for non-staff members>

如果这不是您的意思,您在究竟是在哪里使用此email_list_description以及它应该基于哪些用户的权限?