Question

I am struggling with understanding BaseModelFormSet and modelformset_factory in my app. I think I get the concept but am having a problem implementing it. The below code works fine but I think it is overly complicated because of my lack of understanding.

This would be OK but I'm running into problems when I want to modify the email_list queryset based on the current form's userid (not the user that is logged in). Basically, there are two models that this code effects. The first is the Family Model (and FamilyBaseFormSet) and is fine. The second is the Family Member model and this is the one that contains the userid for each family member. If that user id is part of the is_staff then I want the email_list query to be different for them (a different filter). When I modify it the whole site hangs.

So, here are my questions: 1. Is there an easier way to write this code? Maybe remove the FamilyBaseFormSet and FamilyMemberBaseFormSet? You can see that the only reason I have them is to modify the form.fields 2. How do I determine if the current form (current family member) user id is a staff member? And, if so, how do I set the query appropriately?


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)
Was it helpful?

Solution

Your code is not only a lot more complicated than it has to be, it also has some very significant security holes. By the fields in it, I take it FamilyMember is your custom user model.

Let's say I'm a regular family member who can edit my family. I get a form that has a lot of hidden inputs, including is_staff and is_superuser. Any nitwit with a webpage debug toolbar can change the value of these hidden input fields to True. Now I change the value of these fields to True, send in the form, and tada: I'm a superuser who can fuck up your complete site.

A HiddenInput widget is not a replacement for actual server-side validation and security. The only valid use-case for a hidden input field, is a default value that a user generally shouldn't change in that specific form, but that doesn't pose any security treats when it is changed, either because the user is in some way allowed to change it, or because the server overrides the change or denies the request.

What you need to do is use a custom form in your formset, that only includes the fields you want to include. For your FamilyMember model, it can be the following 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'}),
            ...
        }

This will ensure that a user can only edit the fields they're supposed to edit (the ones you explicitly added to the form), and will make your formset class a whole lot less complicated. You can pass the form to use to your formset factory using the form argument:

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

As for you second question, I'm not entirely sure what you want. By current family member, do you mean the logged in user? If that's the case, this will suffice:

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

If that's not what you meant, where exactly are you using this email_list_description and on what user's privileges should it be based?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top