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 += ( '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
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 ====================
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', ), )
First off, you'll notice I didn't include the
pages/newsletter_signed_up.html because for this case it's too
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. ;-)