Matthew Daly
Matthew Daly

Reputation: 9476

Inline formset for Django UpdateView

I have the following forms in my Django application:

class SurveyAreaForm(ModelForm):
    class Media(object):
        js = formset_media_js

    class Meta:
        model = SurveyArea
        exclude = ['survey', ]
        widgets = {
            'action_by': CustomDateInput(),
        }


AreaFormSet = inlineformset_factory(Survey, SurveyArea, form=SurveyAreaForm)

class SurveyForm(ModelForm):
    class Meta:
        model = Survey
        exclude = ['tech', 'operator', ]
        widgets = {
            'date': CustomDateInput(),
            'client': FilteredJQMRadioSelectWithAdd(),
            'assessorSignature': SignatureInput(attrs={'id': 'assessorSignature'}),
        }

    def __init__(self, *args, **kwargs):
        super(SurveyForm, self).__init__(*args, **kwargs)
        self.fields['client'].empty_label = None

I'm using https://pypi.python.org/pypi/django-formset-js/0.3.0 to provide the JavaScript to add additional forms.

Each Survey object can have one or more SurveyAreas associated with it, and I want to make them editable using the same form. However, I'm running into an issue with rendering the initial data. Here's my view:

class SurveyUpdateView(SurveyValidateMixin, UpdateViewWithTech):
    model = Survey
    form_class = SurveyForm
    success_url="/forms/survey/updated/"
    template_name="my_django_app/survey_update.html"

    def get(self, request, *args, **kwargs):
        """
        Handles GET requests and instantiates blank version of the form
        and its inline formsets.
        """
        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)

        # Get areas
        areas = SurveyArea.objects.filter(survey=self.object).order_by('name').values()

        # Render form
        area_form = AreaFormSet(initial=areas)
        return self.render_to_response(
            self.get_context_data(form=form,
                                area_form = area_form))

    def post(self, request, *args, **kwargs):
        """
        Handles POST requests, instantiating a form instance and its inline
        formsets with the passed POST variables and them checking them for
        validity.
        """
        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        area_form = AreaFormSet(self.request.POST)
        if (form.is_valid() and area_form.is_valid()):
            return self.form_valid(form, area_form)
        else:
            return self.form_invalid(form, area_form)

I also have a SurveyCreateView, which works fine, and the validation is shared between these views using the SurveyValidateMixin. The SurveyUpdateView also inherits from UpdateViewWithTech, which basically just limits the queryset by user and automatically sets a field representing the user.

The problem I'm having is in rendering the initial data. In the get() method of SurveyUpdateView, I'm fetching all of the areas currently associated with that survey, and using PDB I've been able to confirm that at the point where I fetch the areas relating to this survey (in the variable areas), the data appears to be correct. Here's how it looks with 8 items:

[{'description': u'A', 'photo': u'', 'action_required': u'A', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 21L, 'action_taken': True, 'name': u'A'}, {'description': u'A', 'photo': u'', 'action_required': u'A', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 19L, 'action_taken': True, 'name': u'A'}, {'description': u'A', 'photo': u'', 'action_required': u'A', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 29L, 'action_taken': True, 'name': u'A'}, {'description': u'A', 'photo': u'', 'action_required': u'A', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 30L, 'action_taken': True, 'name': u'A'}, {'description': u'B', 'photo': u'', 'action_required': u'B', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 22L, 'action_taken': True, 'name': u'B'}, {'description': u'B', 'photo': u'', 'action_required': u'B', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 20L, 'action_taken': True, 'name': u'B'}, {'description': u'B', 'photo': u'', 'action_required': u'B', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 31L, 'action_taken': True, 'name': u'B'}, {'description': u'C', 'photo': u'', 'action_required': u'C', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 23L, 'action_taken': True, 'name': u'C'}, {'description': u'X', 'photo': u'', 'action_required': u'X', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 24L, 'action_taken': True, 'name': u'X'}, {'description': u'Y', 'photo': u'', 'action_required': u'Y', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 25L, 'action_taken': True, 'name': u'Y'}, {'description': u'Z', 'photo': u'', 'action_required': u'Z', 'action_by': datetime.date(2013, 11, 14), 'survey_id': 12L, u'id': 26L, 'action_taken': True, 'name': u'Z'}]

However, passing that data through as the value of initial when instantiating AreaFormSet doesn't render the data correctly. If in the definition of AreaFormSet, I set the value of extra, I get that number of areas rendered (if undefined, it shows three, which I believe is the default value of extra). The behaviour I want to see is for each existing area to be rendered in its own form.

Again using PDB, if I dump the value of area_form after it's set with area_form.as_table(), I only get the number of forms set in extra, so the issue does appear to be with passing through the initial data.

Am I passing through the value of initial correctly? I understood that the value of initial should be a list of dictionaries, and it looks correct to me, but I'm not getting the correct number of areas rendering.

Upvotes: 2

Views: 4771

Answers (2)

@Adam Starrh is right, although he didn't give any explanation.

I see you are trying to get the related items so you can populate your formset properly. But it turns out that django will do that for you, so you only need to pass the main object itself as an instance for the formset() like so:

    # Render form
    # put the object instance in the formset

    area_form = AreaFormSet(instance=self.object) 

I also faced this problem, and i even tried using a for loop like so:

        items = Model.objects.filter(order_details=self.object)
        def get_inline_items():
            for item in items:
                return item

then i passed the filtered objects as instances like so:

area_form = AreaFormSet(instance = get_inline_items()) 

But Django gave and error that says something like this "Must be 'ParentModel' instance".

I hope this serves as a good explanation for posterity

Upvotes: 0

neck45
neck45

Reputation: 89

Have you tried

area_form = AreaFormSet(instance=self.object)

instead of

area_form = AreaFormSet(initial=areas)

?

Upvotes: 1

Related Questions