This article assumes you've done a tutorial in Django, are comfortable using function based views and want to start using class based views in your project. I'll be discussing function based views and their equivalent class based views when working with forms. This includes creating, updating, and deleting objects, intializing forms, and manipulating data after a form's data is found to be valid.
We're continuing with our toys application from Function based views and their class based view equivalents in Django (Pt 1)
Create
Add the below paths to the toys application urls.py file:
# urls.py
urlpatterns += [
path('fbv-toys/new/', views.create_toy,
name='fbv-toy-create'),
path('cbv-toys/new/', views.ToyCreateView.as_view(),
name='cbv-toy-create'),
]
NOTE
Put all the paths in this article BEFORE path('fbv-toys/<str:username>/',...) from the previous article.
Create a forms.py file in the toys application. Note we'll be using a ModelForm:
# forms.py
from django import forms
from .models import Toy
class ToyForm(forms.ModelForm):
class Meta:
model = Toy
fields = ('name', 'owner', 'is_birthday_present')
Make the views that plan to use that form:
# views.py
# function based view
from django.shortcuts import redirect
from .forms import ToyForm
def create_toy(request):
if request.method == 'POST':
form = ToyForm(request.POST)
if form.is_valid():
form.save()
return redirect('fbv-toy-list')
else:
form = ToyForm()
return render(request, 'toys/toy_form.html' , {'form': form})
# class based view
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView
class ToyCreateView(CreateView):
model = Toy
form_class = ToyForm
success_url = reverse_lazy('cbv-toy-list') # you can also simply put '/cbv-toys/' but its better practice to reference the url name
template_name = 'toys/toy_form.html'
# alternate class based view
class ToyCreateView(CreateView):
model = Toy
fields = ('name', 'owner', 'is_birthday_present')
success_url = reverse_lazy('cbv-toy-list')
template_name = 'toys/toy_form.html'
# another alternate class based view
class ToyCreateView(CreateView):
model = Toy
form_class = ToyForm
template_name = 'toys/toy_form.html'
def get_success_url(self):
return reverse('cbv-toy-list')
For the class based view, you don't have to specify a form_class, and you can simply use the fields field to specify the fields you want.
For our class based view, notice we are specifying our success_url and using reverse_lazy. We can also simply use the url path, ie. '/cbv-toys/'. Alternatively we can override the get_success_url method if we plan to do anything more fancy. i.e. grabbing the pk in the url to use in the redirect url.
After successful creation, the class based view defaults to calling the get_absolute_url method defined in your model, so if you do not want to include a success_url in your view, then update your model to include the below. This will redirect us to the detail page of our newly created object.
# models.py
from django.urls import reverse
class Toy(models.Model):
name = models.CharField(max_length=200)
owner = models.ForeignKey(User, on_delete=models.SET_NULL,
null=True, related_name='toys')
is_birthday_present = models.BooleanField(default=False)
def __str__(self):
return f'{self.name}'
def get_absolute_url(self):
return reverse('cbv-toy-detail', kwargs={'toy_pk': self.pk})
Lastly, the class based view defaults to looking up <model_name>_form.html, i.e. in our case toy_form.html, so you don't have to specify the template_name unless you plan to name your file differently.
Create toy_form.html and place it in the 'toys/templates/toys/' folder:
# in 'toys/templates/toys/'
# toy_form.html
<form action="" method="post">
# make sure to include the csrf_token here
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
NOTE
Remember to include <form> and </form> and the <input> button with your {{form}}.
Update
Add more paths to your urls.py file:
# urls.py
urlpatterns += [
path('fbv-toys/<int:toy_pk>/edit/', views.update_toy,
name='fbv-toy-update'),
path('cbv-toys/<int:toy_pk>/edit/', views.ToyUpdateView.as_view(),
name='cbv-toy-update'),
]
We'll use the same form we used before, i.e. ToyForm.
Add these views:
# views.py
# function based view
def update_toy(request, toy_pk):
toy = get_object_or_404(Toy, pk=toy_pk)
if request.method == 'POST':
form = ToyForm(request.POST, instance=toy)
if form.is_valid():
form.save()
return redirect('fbv-toy-list')
else:
form = ToyForm(instance=toy)
return render(request, 'toys/toy_form.html', {'form': form})
# class based view
from django.views.generic.edit import UpdateView
class ToyUpdateView(UpdateView):
model = Toy
form_class = ToyForm
success_url = reverse_lazy('cbv-toy-list')
pk_url_kwarg = 'toy_pk'
template_name = 'toys/toy_form.html'
As with DetailView, for CreateView the default pk_url_kwarg is 'pk' and you'll have to set path to ('cbv-toys/<int:pk>/edit/'....) if you don't want to specify. In our case, we specified 'toy_pk' and so we set path to ('cbv-toys/<int:toy_pk>/edit/'....).
Like above, you can define form_class, or specify fields in fields. You can specifying the success_url using reverse_lazy or use url path, or override the get_success_url. Also like above, after successful creation, the class based view defaults to calling the get_absolute_url method defined in your model, and it defaults to looking up <model_name>_form.html.
Delete
Set up the paths:
# urls.py
urlpatterns += [
path('fbv-toys/<int:toy_pk>/delete/', views.delete_toy,
name='fbv-toy-delete'),
path('cbv-toys/<int:toy_pk>/delete/', views.ToyDeleteView.as_view(),
name='cbv-toy-delete'),
]
No forms are needed since we are deleting an item.
Update the views:
# views
# function based view
def delete_toy(request, toy_pk):
toy = get_object_or_404(Toy, pk=toy_pk)
if request.method == 'POST':
toy.delete()
return redirect('cbv-toy-list')
return render(request, 'toys/toy_confirm_delete.html')
# class based view
from django.views.generic.edit import DeleteView
class ToyDeleteView(DeleteView):
model = Toy
pk_url_kwarg = 'toy_pk'
success_url = reverse_lazy('cbv-toy-list')
template_name = 'toys/toy_confirm_delete.html'
The default pk_url_kwarg is 'pk' and you'll have to set path to ('cbv-toys/<int:pk>/delete/'....) if you don't want to specify.
The default template is <model_name>_confirm_delete.html so you don't have to specify.
It is required to specify the success_url (or override the get_success_url) because you will get an error if there isn't something there.
You'll need to make an html file to confirm the deletion:
# in 'toys/templates/toys/'
# toy_confirm_delete.html
<form method="POST">
# make sure to include the csrf_token here
Are you sure you want to delete this item?
<input type="submit" value="Yes" />
</form>
NOTE
Remember to include <form> and </form> and the <input> button with your {{form}}.
Initial values
Let's make all of the newly created toys legos unless the input is changed.
Add your urls:
# urls.py
urlpatterns += [
path('fbv-toys/new-lego/', views.create_lego_toy,
name='fbv-toy-create-lego'),
path('cbv-toys/new-lego/', views.LegoToyCreateView.as_view(),
name='cbv-toy-create-lego'),
]
Add to the toys application views.py file:
# views.py
# function based view
def create_lego_toy(request):
if request.method == 'POST':
form = ToyForm(request.POST)
if form.is_valid():
form.save()
return redirect('fbv-toy-list')
else:
initial = {
'name': 'lego'
}
form = ToyForm(initial=initial)
return render(request, 'toys/toy_form.html', {'form': form})
# class based view
class LegoToyCreateView(CreateView):
model = Toy
form_class = ToyForm
initial = {'name': 'lego'}
success_url = reverse_lazy('cbv-toy-list')
# alternate class based view
class LegoToyCreateView(CreateView):
model = Toy
form_class = ToyForm
success_url = reverse_lazy('cbv-toy-list')
def get_initial(self):
return {'name': 'lego'}
Making changes after validation
Let's make all newly created toys marked as birthday presents once submitted.
Update your urls.py file:
urlpatterns += [
path('fbv-toys/new-birthday/', views.create_birthday_toy,
name='fbv-toy-create-birthday'),
path('cbv-toys/new-birthday/', views.BirthdayToyCreateView.as_view(),
name='cbc-toy-create-birtday')
]
Then views.py:
# views.py
# function based view
def create_birthday_toy(request):
if request.method == 'POST':
form = ToyForm(request.POST)
if form.is_valid():
toy = form.save(commit=False)
toy.is_birthday_present = True
toy.save()
return redirect('fbv-toy-list')
else:
form = ToyForm()
return render(request, 'toys/toy_form.html', {'form': form})
# class based view
class BirthdayToyCreateView(CreateView):
model = Toy
form_class = ToyForm
success_url = reverse_lazy('cbv-toy-list')
def form_valid(self, form):
form.instance.is_birthday_present = True
return super().form_valid(form)