Django GetOrCreateView
Today I decided to use the Django class based view (CBV) CreateView, but I wanted to avoid duplications and submit to the view from the front page of a site. The reason was I needed a simple newsletter signup form. This is what I cooked up and should work for Django 1.3, 1.4, and the forthcoming 1.5 release. Here is what I did:
- Installed dependencies =========================
This version requires the following package to be pip installed into your virtualenv.
- django-extensions so we can have easy timestamps on models.
This also needs to be added to your list of INSTALLED_APPS
:
INSTALLED_APPS += (
'django_extensions',
)
- Defined the model ====================
The model is really simple, and inherits from TimeStampedModel so we know when people signed up:
from django.db import models
from django_extensions.db.models import TimeStampedModel
class NewsLetterSignup(TimeStampedModel):
email = models.EmailField("Email")
def __unicode__(self):
return self.email
- Wrote the view =================
Here's the somewhat challenging part that forced me to dive into Django's source code. Even with the documentation work we've done over the past few months, it's clear we've got a long way to go.
Because of that source code diving, for this blog post I really did my
best to document why I did things in the
NewsLetterSignupView.form_valid()
method.
from django.http import HttpResponseRedirect
from django.views.generic import CreateView
from .models import NewsLetterSignup
class NewsLetterSignupView(CreateView):
""" Signs up users to a newsletter """
model = NewsLetterSignup
success_url = '/newsletter-signed-up/' # replace with reverse
def form_valid(self, form):
"""
If the form is valid, save the associated model.
(django.views.generic.edit.ModelFormMixin)
If the form is valid, redirect to the supplied URL.
(django.views.generic.edit.FormMixin)
"""
# Get the email from the form.cleaned_data dictionary
email = form.cleaned_data.get("email", "")
# Get or create the signup. We don't need to do anything with the
# model instance or created boolean so we don't set them.
NewsLetterSignup.objects.get_or_create(email=email)
# Don't use super() to inherit as it will do a form.save()
# You could call the FormMixin's form_valid() method but I think
# using a HttpResponseRedirect() much more explicit.
return HttpResponseRedirect(self.success_url)
- Wired it together ====================
In urls.py:
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
from .views import NewsLetterSignupView
urlpatterns = patterns('',
url(regex=r'^newsletter-signed-up/$',
view=TemplateView.as_view(
template_name="pages/newsletter_signed_up.html"
),
name='newsletter_signedup',
),
url(regex=r'^newsletter-signup/$',
view=NewsLetterSignupView.as_view(),
name='news_letter_signup',
),
)
Closing thoughts
First off, you'll notice I didn't include the
pages/newsletter_signed_up.html
because for this case it's too
trivial.
Second, this is one of those very clear cases where a functional view would have been so much easier compared to the effort I spent writing this as a class based view. The line count would have been about the same, but the mental bandwidth involved in figuring this would have been a fraction of the effort I spent.
Third, this is probably better served with an implementation of
django.views.generic.FormView
. Oh well...
Fourth, I want to see a configurable version of this in the next release of django-braces. ;-)
Tags: python django howto class-based-views