nyergler / nested-formset

Nest Django formsets for multi-level editing.
BSD 3-Clause "New" or "Revised" License
87 stars 21 forks source link

how to edit the highest level entity? #4

Closed henning closed 10 years ago

henning commented 10 years ago

Submitting it here as its the only way I see to contact you...

I find the implementation interesting and helpful, just one thing I couldn't yet figure out: In the documentation and the example you show how to set up an update view for the Block model, but it actually is only for editing objects related to the block, not the block itself.

Would it work to make the block's properties edtiable in the same view, too? If so, how?

Thanks in advance, Henning

nyergler commented 10 years ago

If you want to edit the base object at the same time as you edit the related objects, that's pretty straight-forward. You can just use a separate form for that object in your view. You'll have to process them independently (ie, check that both forms' is_valid() methods return True), but it's more straight-forward than trying to craft a single object that does it all.

If you want to create the base object (Block) at the same time as you create the related objects, that's more involved. You'd need an unsaved Block to use as the object when instantiating the nested formset on GET, and then you'd substitute the saved one when instantiating the nested formset on POST. It's a little tricky.

Because the nested formset is based on Django's inline formsets, editing the base object is not part of its built-in feature set.

henning commented 10 years ago

Thanks for your reply and help!

I only needed to edit it, not create. I was mostly overwhelmed on how to get the additional form processed from a class based view that extended the UpdateView - it looked pretty complex, and a lot of fiddling the extra form and it's handling into the context etc, so I ended up to implement the view the oldschool function style, which worked fine.

nyergler commented 10 years ago

Yeah, doing that with a class-based view is a little tricky. The CBVs are great because they hide a bunch of boilerplate, but they also hide the control flow where you'd insert the second form.

When I've done this in the past, I've overridden get_form_class to return a tuple of forms, and then handle instantiation manually (I think). We also wrote something at Eventbrite called rebar (http://github.com/eventbrite/rebar) that lets you group forms and formsets together into one logical group. It's also a little complicated, but may be helpful.

henning commented 10 years ago

I'll look into these ideas when I come back to the task and check if that helps making it nicer as it is with my current solution - thanks ;)

Gwildor commented 9 years ago

I know I'm late to the party, but I'm just leaving this here for others who are needing this as well. I needed both of these things: a basic form of a grandparent instance, and working inlines on a create view.

It was easier than anticipated (thanks to the pointers in this topic and on some other pages), and in the end I ended up with this:

class NestedFormsetsMixin(generic.FormView):
    def get_form_class(self):
        return nestedformset_factory(
            models.GrandParent,
            models.Parent,
            nested_formset=inlineformset_factory(
                models.Parent,
                models.Child,
                extra=1
            ),
            extra=1
        )

    def get_form(self, form_class):
        form_kwargs = self.get_form_kwargs()

        base_form = modelform_factory(models.GrandParent)(**form_kwargs)
        return base_form, form_class(**form_kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)

        for f in form:
            if not f.is_valid():
                return self.form_invalid(form)

        return self.form_valid(form)

    def form_valid(self, form):
        self.object = form[0].save()

        for f in form[1:]:
            f.instance = self.object
            f.save()

        return HttpResponseRedirect(self.get_success_url())

You can use this for both Django's CreateView and UpdateView. Do note, however, that for the create view, you need to implement def get_object(self, queryset=None): return None. This is because I had to fully override the post handler for validation of all the forms. In theory (haven't tested this, but made it in a way that it should be possible), you can have multiple (and optionally nested) formsets or forms this way as well: just return more formsets or forms in the get_form method.