In one of our projects, we maintain a fairly large central view to display a dashboard of user information. Recently, we've begun adding the ability for users to edit parts of that information inline on the dashboard. Due to the nature of the data being disparate, we need to have the ability to build out multiple different forms, and at the same time maintain some sort of structure and organization within the application itself.
I'll go over how we implemented a solution to this problem with a simplified example.
To accomplish this task we start out creating a Django form for each formset/data that we want the user to be able to edit. We want to keep as much business logic relating to the form inside here as possible which allows us to keep complex business logic out of our views.
class SomeForm(forms.Form): first_name = forms.CharField() last_name = forms.CharField() alternate_email_1 = forms.EmailField(required=False) alternate_email_2 = forms.EmailField(required=False) def __init__(self, *args, **kwargs): """ Dynamic form manipulation. """ def save(self): """ Call save actions here """
The real challenge with adding multiple forms is how to handle the processing in an elegant way. I didn't find a truely good way to do this, but this solution accomplished our goals.
To accomplish this, we first create a new view that will only handle a single form submission. We will repeat this for any addtional forms that we need to process.
@login_required def handle_some_form(request): if request.method == 'POST': SOME_SUCCESS = 'Success message' SOME_FAIL = 'Fail message' try: del request.session['some_form'] except KeyError: pass form = SomeForm(request.POST, prefix='some') if form.is_valid(): try: form.save() messages.success(request, SOME_SUCCESS) except CustomException: messages.error(request, SOME_FAIL) else: request.session['some_form'] = request.POST return redirect('home')
# Urls.py url(r'^forms/someform/$', views.handle_some_form, name='handle_form'),
The first thing we do after confirming that the request is POST, is clear a session variable. This session variable can be set later if the form is not valid.
If the form validates, we save (or do some other form action) and create a success message and redirect the user back to our main view, simple enough.
The difficult part was coming up with a way to handle form validation since we need to load the main view, but with data sent to the second view.
When a form doesn't validate in the second view, we set a session variable with the posted data and then redirect the user back to the main view with this session variable set.
Then in our main view:
try: form_post = request.session['some_form'] form = SomeForm(form_post) form.is_valid() context['some_form'] = some_form del request.session['some_form'] except KeyError: pass
We check for the existence of the session variable and attempt again to validate the form. When it fails we are left with the invalid form which now has its
errors dict filled out, so we can present that back to the user. We set the form context, delete the session variable so that it doesn't collide with the next request and display the failed form back to the user.
If I was able to do this over again I think I'd probably look at making the main view class based which might give us a bit more of an elegant way to handle these types of forms. We could also build out some form view mixins that would allow us to quickly extend and build out form handling views.