1

I'm trying to follow the answer given by @qris in a previous question (django crispy forms: Nesting a formset within a form) but I get a KeyError at formset = context[self.formset_name_in_context] in the render function. I don't quite understand what's going on. Where is the context coming from? How can I fix this?

In forms.py, I create a formset for my AuthorForm:

AuthorFormSet = formset_factory(AuthorForm)
author_formset = AuthorFormSet()

and then I use author_formset in the Layout of my other form:

self.helper.layout = Layout(
    Div(
        Div('place', 'sample', css_class='col-xs-6'),
        Div('subject_type', 'data_title', css_class='col-xs-6'),
        css_class='row'
    ),
    MultiField(
        "Authors",
        Formset('author_formset'),
    )
)

My latest attempt at in views.py:

class register(TemplateView):

    form_class = RegistrationForm()
    facility_form = FacilityAndContactForm()

    formsets = {
        'author_formset': AuthorFormSet,
    }

    template_name = "databank/depositors.html"

    def __init__(self, data, files, *args, **kwargs):
        super(register, self).__init__(data, files, *args, **kwargs)
        if 'initial' in kwargs:
            # Formsets treat this parameter completely differently: they
            # expect an array of values instead of just one. So remove it
            # because it will break formsets.
            kwargs.pop('initial')

        for name, formset_class in self.formsets.iteritems():
            # doesn't exist yet, so we can't call its queryset() method
            queryset = formset_class.model._default_manager.filter(
                **{formset_class.fk.name: self.instance})
            extra = 0 if queryset.exists() else 1

            formset = self.construct_formset(formset_class, data, files, *args, prefix=name, extra=extra, **kwargs)
            setattr(self, name, formset)

            helper = getattr(formset.form(), 'helper', None)
            if helper:
                formset.helper = helper

    def construct_formset(self, formset_class, data, files, prefix, extra,
            *args, **kwargs):
        return formset_class(data, files, *args, prefix=prefix, extra=extra,
            **kwargs)

    def get(self, request, *args, **kwargs):
        form_class = self.form_class
        return render(request, self.template_name, {'form_class': form_class,
            'facility_form': self.facility_form, 'author_formset':
             self.formset})

    def post(self, request):
        form_class = RegistrationForm(request.POST, request.FILES)
        facility_form = FacilityAndContactForm(request.POST)

        author_formset=AuthorFormSet(request.POST)
        formsets = {
            'author_formset': author_formset,
        }

        if form_class.is_valid():
            registration = create_registration(form_class)
            dataset_name = registration.dataset.dataset_name
            # more logic here...

        return render(request, 'databank/register.html',
            {'registration_form': form_class, 'facility_form': facility_form,
            'author_formset': author_formset})
Community
  • 1
  • 1
steph
  • 701
  • 2
  • 10
  • 30
  • @qris where/how do I initially declare the layout object to pass to the new Formset field? In your example implementation, where/how did you first definte 'eduction'? – steph Mar 27 '15 at 15:58

1 Answers1

1

You have to put author_formset into the context yourself :) It can't get there any other way.

This is how I generalised it myself, having a dict of auto-constructed sub-formsets on my Form:

class MyForm(ModelForm):
    formsets = {
        'author_formset': AuthorFormSet,
    }

    def __init__(self, data, files, *args, **kwargs):
        super(MyView, self).__init__(data, files, *args, **kwargs)
        if 'initial' in kwargs:
            # Formsets treat this parameter completely differently: they
            # expect an array of values instead of just one. So remove it
            # because it will break formsets.
            kwargs.pop('initial')

        for name, formset_class in self.formsets.iteritems():
            # doesn't exist yet, so we can't call its queryset() method
            queryset = formset_class.model._default_manager.filter(
                **{formset_class.fk.name: self.instance})
            extra = 0 if queryset.exists() else 1

            formset = self.construct_formset(formset_class, data, files, *args,
                prefix=name, extra=extra, **kwargs)
            setattr(self, name, formset)

            helper = getattr(formset.form(), 'helper', None)
            if helper:
                formset.helper = helper

    def construct_formset(self, formset_class, data, files, prefix, extra,
            *args, **kwargs):
        return formset_class(data, files, *args, prefix=prefix, extra=extra,
            **kwargs)
qris
  • 7,900
  • 3
  • 44
  • 47
  • thank you so much for your help!! @qris I have never used Class Based Views before so, honestly, I'm still very confused. (I'm reading up on them now, though, trying to understand). Is there a way to do this using function-based views? Or, if I post more of what I currently have/am trying, could you help me with that? – steph Mar 30 '15 at 01:32
  • I added my latest view attempt but I'm getting errors about not passing the correct parameters to init `__init__() takes at least 3 arguments (1 given)` – steph Mar 30 '15 at 03:17
  • Sorry, my mistake, this code was from a Form, not a View. Add it to your Form instead, and then construct it as normal in your view. I've updated my answer above. – qris Mar 30 '15 at 10:26
  • Thank you for your help @qris Can't tell you how much I appreciate it. Unfortunately, I'm in over my head here. I'll keep trying but I think I need more help so I'm now trying to find someone who might be willing to screen share and help me that way (if you're willing, let me know! but I'll find some way). – steph Mar 30 '15 at 18:02