# When to use CreateView?
Django provides several class based generic views to accomplish common tasks. One among them is CreateView.
CreateView should be used when you need a form on the page and need to do a db insertion on submission of a valid form.
# CreateView is better than vanilla View
We will first write a vanilla view by subclassing View, and then modify the view to subclass CreateView instead of View.
CreateView is better than vanilla View in following ways:
- Avoid boilerplate code
- Succinct and more maintainable code.
# Vanilla View
We want to create a page with a book creation form.
# books/models.py
class Book(models.Model):
title = models.CharField(max_length=100)
isbn = models.CharField(max_length=100, unique=True)
is_published = models.BooleanField(default=True)
def __str__(self):
return self.title
# books/forms.py
class BookCreateForm(forms.ModelForm):
class Meta:
model = Book
Vanilla view looks like:
# books/views.py
class BookCreateView(CreateView):
def get(self, request, *args, **kwargs):
context = {'form': BookCreateForm()}
return render(request, 'books/book-create.html', context)
def post(self, request, *args, **kwargs):
form = BookCreateForm(request.POST)
if form.is_valid():
book = form.save()
book.save()
return HttpResponseRedirect(reverse_lazy('books:detail', args=[book.id]))
return render(request, 'books/book-create.html', {'form': form})
Template code looks like:
<!--books/templates/books/book-create.html-->
<form action="." method="POST">
{% csrf_token %}
<table>
</table>
<button type="submit">SUBMIT</button>
</form>
With proper urlpattern, you should be able to see the book creation form.
from django.urls import path
from . import views
app_name = 'books'
urlpatterns = [
path('create/', views.BookCreateView.as_view(), name='create'),
path('<int:pk>/', views.BookDetailView.as_view(), name='detail'),
]
# Using CreateView
Vanilla view has a lot of boilerplate code.
Any object creation view will have a get() implementation for creating context and rendering the response. Similarly object creation view will have a post() implementation to do .save()
. CreateView, which is a generic class based view, can avoid this boilerplate code.
class BookCreateView(CreateView):
template_name = 'books/book-create.html'
form_class = BookCreateForm
This change also needs that a get_absolute_url()
be defined on the object which is being created. So we need to provide a get_absolute_url()
on model Book.
class Book(models.Model):
# More code
def get_absolute_url(self):
return reverse('books:detail', args=[self.id])
Refresh the page and you should still be able to achieve everything that was possible with vanilla view.
As you would have noticed, using a CreateView helped us avoid boilerplate get() and post() implementation. The code looks much more succinct as it only has few class attributes and there isn’t any function implementation.
# Adding initial data to CreateView
Assume we want to populate form’s title
field with some initial data.
Modify BookCreateView to look like:
class BookCreateView(CreateView):
template_name = 'books/book-create.html'
form_class = BookCreateForm
def get_initial(self, *args, **kwargs):
initial = super(BookCreateView, self).get_initial(**kwargs)
initial['title'] = 'My Title'
return initial
This code has better separation of concern. There is a separate method for dealing with initial data.
Had we used a vanilla view, initial data code would have been part of get()
.
# Adding form kwargs to CreateView
Let’s add a user
field to Book to track the user who creates a Book.
class Book(models.Model):
title = models.CharField(max_length=100)
isbn = models.CharField(max_length=100, unique=True)
is_published = models.BooleanField(default=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, **NULL_AND_BLANK)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('books:detail', args=[self.id])
Assume you don’t want to allow a user to create two books with same title. The title should be unique per user.
This validation needs writing a clean_title()
method which would look like:
class BookCreateForm(forms.ModelForm):
class Meta:
model = Book
exclude = ('user',)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(BookCreateForm, self).__init__(*args, **kwargs)
def clean_title(self):
title = self.cleaned_data['title']
if Book.objects.filter(user=self.user, title=title).exists():
raise forms.ValidationError("You have already written a book with same title.")
return title
This needs that a user
be supplied from view during form creation. This is where CreateView.get_form_kwargs() come into picture. Modify the view to look like:
class BookCreateView(CreateView):
template_name = 'books/book-create.html'
form_class = BookCreateForm
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def get_initial(self, *args, **kwargs):
initial = super(BookCreateView, self).get_initial(**kwargs)
initial['title'] = 'My Title'
return initial
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(BookCreateView, self).get_form_kwargs(*args, **kwargs)
kwargs['user'] = self.request.user
return kwargs
After this any logged in user wouldn’t be able to create two Books with same title.
# Our other posts on generic class views
Thank you for reading the Agiliq blog. This article was written by Akshar on Jan 7, 2019 in django .
You can subscribe ⚛ to our blog.
We love building amazing apps for web and mobile for our clients. If you are looking for development help, contact us today ✉.
Would you like to download 10+ free Django and Python books? Get them here
