I ran into a slightly tricky use case the other day wherein I needed to grab some data from an API, validate it against a Django ModelForm, and only show the form to the user if there are errors during the validation.
If the form is shown, the error messages from the validation should appear along with a pre-populated form (using the data from the API). Otherwise, the ModelForm should submit and be saved immediately, without any user interaction.
After digging through Django’s code it became clear that to accomplish this, I needed to override a couple methods of the generic class-based CreateView.
Here’s the code for the view…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.core.urlresolvers import reverse_lazy | |
from django.views.generic import CreateView | |
from app.forms import MyModelForm | |
from app.lib.api import API | |
class MyCreateView(CreateView): | |
template_name = 'form.html' | |
form_class = MyModelForm | |
success_url = reverse_lazy('home') | |
object = None | |
# allow data to be passed to this | |
# pulled from django/views/generic/edit.py/ModelFormMixin | |
def get_form_kwargs(self, data=None): | |
kwargs = super(MyCreateView, self).get_form_kwargs() | |
# if we have data, set the form's data so that we can auto-submit this form (without POSTing) | |
if data: | |
kwargs.update({'initial': data, 'data': data}) | |
return kwargs | |
def get(self, request, *args, **kwargs): | |
# grab the data from the API | |
data = API.get_data() | |
formdata = {'user_id': data['user_id'], | |
'email': data['email'], | |
'first_name': data['first_name'], | |
'last_name': data['last_name']} | |
form_class = self.get_form_class() | |
# must instantiate the class directly and include the data object rather than use get_form | |
# will set both the initial and instance data | |
#form = self.get_form(form_class) | |
form = form_class(**self.get_form_kwargs(formdata)) | |
# do we have a valid form submission | |
if form.is_valid(): | |
return self.form_valid(form) | |
else: | |
return self.form_invalid(form) | |
# once the user submits the form, validate the form and create the new user | |
def post(self, request, *args, **kwargs): | |
# setup the form | |
# we can use get_form this time as we no longer need to set the data property | |
form_class = self.get_form_class() | |
form = self.get_form(form_class) | |
# grab the data from the API | |
data = API.get_data() | |
form.initial = {'user_id': data['user_id'], | |
'email': data['email'], | |
'first_name': data['first_name'], | |
'last_name': data['last_name']} | |
if form.is_valid(): | |
return self.form_valid(form) | |
else: | |
return self.form_invalid(form) |
As you can see, the first thing I’m doing is overriding get_form_kwargs so that it can take a new argument – data. This allows me to pass in the data pulled from the API to the form. Since I want the form to be saved immediately, without any user interaction, I set both the initial and data properties to the passed in data.
The get method is now a simple exercise in pinging the API for data, placing it into a dictionary the form can use, and calling form.is_valid() to see if the data passes the validation.
If it doesn’t, the form is rendered (complete with validation errors and pre-populated data) and the post method handles when the form is submitted by the user.