Daniel Roy Greenfeld

Daniel Roy Greenfeld

About | Books

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:

  1. Installed dependencies =========================

This version requires the following package to be pip installed into your virtualenv.

This also needs to be added to your list of INSTALLED_APPS:

  1. 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
  1. 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.
        If the form is valid, redirect to the supplied URL.

        # 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.

        # 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)  
  1. 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('',

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