1

I'm trying to put multiple account management forms on the one page with a TemplateView as follows:

class AccountManagement(TemplateView):
    """ Generic view to display the account management template """

    template_name = 'accountmanagement.html'

    def get_context_data(self, **kwargs):
        context = super(AccountManagement, self).get_context_data(**kwargs)
        context['user'] = self.request.user
        # pass unbound form instances to context 
        # if there aren't bound instances already there
        if context.get('usercreate_form') is None:
            context['usercreate_form'] = UserCreateForm()
        if context.get('password_form') is None:
            context['password_form'] = PasswordChangeForm()
        return context

I'm handling UserCreation with a FormView (because this example is simplified; I also need some non-model data, and CreateView needs a ModelForm). This view processes the POST request, and is supposed to redirect to the TemplateView with a success message, or pass the invalid bound form back to the context so that the template can render the errors. Trouble is, it doesn't do the part in bold italics (obviously HttpResponseRedirect doesn't pass the context). Why? How can I get the bound form back into the TemplateView context here so that the form.errors will be available and the user doesn't have to retype the data?

class UserCreate(FormView):
    """
    Generic view to create a User.
    """
    form_class = UserCreateForm
    http_method_names = ['post',]
    success_url = reverse_lazy('accountmanagement')
    failure_url = reverse_lazy('accountmanagement')

    def form_valid(self, form):
        #password1 == password2 as per UserCreateForm.clean()
        try:
            new_user = User.objects.create_user(
                username=form.cleaned_data['email'],
                first_name=form.cleaned_data['first_name'],
                last_name=form.cleaned_data['last_name'],
                email=form.cleaned_data['email'],
                password=form.cleaned_data['password1']
            )
            new_user.save()
            messages.success(self.request, new_user.username + str(_(": successfully saved.") ))
            return HttpResponseRedirect(self.success_url)
        except IntegrityError:
            #duplicate username
            messages.error(self.request, _("Duplicate email address."))
            return HttpResponseRedirect(self.failure_url, {'usercreate_form': form})

    def form_invalid(self, form):
        messages.error(self.request, _("Unable to create user."))
        return HttpResponseRedirect(self.failure_url, {'usercreate_form': form})

template:

<form method="post" action="{% url 'usercreate' %}">{% csrf_token %}
{{ usercreate_form.errors }}
{{ usercreate_form.as_p }}
<button type="submit">{% trans 'Save' %}</button>

Final related question: is the right way to put several related forms on the one page a TemplateView? And then process non-model forms using a POST-only FormView with a redirect back to the TemplateView? Or should I do this a different way?

Community
  • 1
  • 1
Escher
  • 5,418
  • 12
  • 54
  • 101
  • I'm not really sure what you're trying to do here. Checking for duplicate integrity errors should be part of the form validation process itself. And why are you redirecting on validation failure? – Daniel Roseman Sep 16 '16 at 14:24
  • You're right Daniel; I should move that logic to the form. As for redirecting on validation failure: I'm trying to render the TemplateView again, but this time with the form bound and passed in the context so the errors can be displayed `{{ usercreate_form.errors }}` and the user doesn't have to retype most of the form data. – Escher Sep 16 '16 at 14:45
  • But you can't possibly do that with a redirect, and I don't understand why you want to. A redirect is just an instruction to the browser to request another URL. By definition it can't know anything about the Django context. Your form_invalid should just redisplay the forms *in the current request*; which is what the default implementation does, you should call super and return that (or not override it at all). – Daniel Roseman Sep 16 '16 at 14:50
  • Oh, I see. I was just trying to avoid double POST absentmindedly. OK, now I can pass the bound form in the context by just doing `def form_invalid(self, form): context = self.get_context_data(); context['usercreate_form'] = form; return render(self.request, self.template_name, context)`. Thanks so much for injecting some sanity there Daniel. – Escher Sep 16 '16 at 15:19

0 Answers0