Customize Django’s unique_together Error Message

While you’re able to customize the error message for a unique field when defining your model…

class Context(models.Model):
name = models.CharField(error_messages={'unique': u'My custom message'})

view raw
model.py
hosted with ❤ by GitHub

…you can’t do the same for a unique_together constraint. There’s an open ticket but it hasn’t seen much attention. In the meantime, you can override the unique_error_message in your model to provide a custom error message:

def unique_error_message(self, model_class, unique_check):
if model_class == type(self) and unique_check == ('field1', 'field2'):
return 'Your custom error message.'
else:
return super(YourModel, self).unique_error_message(model_class, unique_check)

view raw
model.py
hosted with ❤ by GitHub

Saving Only Specific Fields in Django 1.5

By default instance.save() flushes all fields to the database. While this can be handy in some use cases, more often than not you only need to save a subset of fields. Folks have resorted to various workarounds to deal with this behavior, none of which are very pretty.

Luckily Django 1.5 remedies this situation with the new update_fields argument for save(). This not only gives you a minor performance boost, but can also help with certain race conditions.

One thing to note if you use deferred model loading:

“When saving a model fetched through deferred model loading (only() or defer()) only the fields loaded from the DB will get updated. In effect there is an automatic update_fields in this case. If you assign or change any deferred field value, the field will be added to the updated fields.”

Django ModelForm, FormView and the Request Object

It’s often necessary to get access to the request object from within a ModelForm. To do this, you’ll need to override a single method in both the ModelForm and FormView.

First the FormView:

class RegisterView(FormView):
template_name = 'register.html'
form_class = RegisterForm
success_url = reverse_lazy('home')
# add the request to the kwargs
def get_form_kwargs(self):
kwargs = super(RegisterView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs

view raw
views.py
hosted with ❤ by GitHub

And now the ModelForm:

class RegisterForm(forms.ModelForm):
username = forms.CharField(max_length=User._meta.get_field('username').max_length)
email = forms.CharField(max_length=User._meta.get_field('email').max_length, widget=HTML5EmailInput())
password = forms.CharField(min_length=6, max_length=16, widget=forms.PasswordInput())
# the request is now available, add it to the instance data
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(RegisterForm, self).__init__(*args, **kwargs)

view raw
forms.py
hosted with ❤ by GitHub

Symfony2 vs Django – Models

As promised in my first post, I’m going to be looking at the reasons I chose to go with Django rather than Symfony2. Aside from wanting to learn a new language, there were a few things that jumped out at me when looking into Django.

One of the biggest was the dramatic amount of code necessary to do the same thing in Symfony2. Let’s look at a typical model for a blog. Here’s what it looks like in Django (I’ve added a small utility function via a manager):

models/blog.py

from django.db import models
class BlogManager(models.Manager):
def get_tags(self):
# get just the tags
tags = self.values('tags')
# combine all tags into a single list
all_tags = []
for tag in tags:
all_tags += tag.values()[0].split(',')
# strip whitespace
stripped_tags = [tag.strip() for tag in all_tags]
return stripped_tags
class Blog(models.Model):
title = models.CharField(max_length=255)
slug = models.CharField(max_length=255)
author = models.CharField(max_length=100)
blog = models.TextField()
image = models.CharField(max_length=20)
tags = models.TextField()
created = models.DateTimeField('date created', auto_now_add=True)
updated = models.DateTimeField('last updated', auto_now_add=True, auto_now=True)
objects = BlogManager()
def __unicode__(self):
return self.title
class Meta:
app_label = 'app'

view raw
blog.py
hosted with ❤ by GitHub

Now here’s the same model in Symfony2 along with the utility function:

Entity/Blog.php

<?php
namespace NewCoInc\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use NewCoInc\AppBundle\Lib\FormattingLib;
/**
* Blog
*
* @ORM\Table(name="blog")
* @ORM\Entity(repositoryClass="NewCoInc\AppBundle\Entity\BlogRepository")
*/
class Blog
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="title", type="string", length=255, nullable=false)
*/
private $title;
/**
* @ORM\Column(type="string")
*/
private $slug;
/**
* @var string
*
* @ORM\Column(name="author", type="string", length=100, nullable=false)
*/
private $author;
/**
* @var string
*
* @ORM\Column(name="blog", type="text", nullable=false)
*/
private $blog;
/**
* @var string
*
* @ORM\Column(name="image", type="string", length=20, nullable=false)
*/
private $image;
/**
* @var string
*
* @ORM\Column(name="tags", type="text", nullable=false)
*/
private $tags;
/**
* @ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
*/
private $comments;
/**
* @var \DateTime
*
* @ORM\Column(name="created", type="datetime", nullable=false)
*/
private $created;
/**
* @var \DateTime
*
* @ORM\Column(name="updated", type="datetime", nullable=false)
*/
private $updated;
public function __construct()
{
$this->comments = new ArrayCollection();
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
$this->formatter = new FormattingLib();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* @param string $title
* @return Blog
*/
public function setTitle($title)
{
$this->title = $title;
$this->setSlug($this->title);
return $this;
}
/**
* Get title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set author
*
* @param string $author
* @return Blog
*/
public function setAuthor($author)
{
$this->author = $author;
return $this;
}
/**
* Get author
*
* @return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* Set blog
*
* @param string $blog
* @return Blog
*/
public function setBlog($blog)
{
$this->blog = $blog;
return $this;
}
/**
* Get blog
*
* @return string
*/
public function getBlog()
{
return $this->blog;
}
/**
* Set image
*
* @param string $image
* @return Blog
*/
public function setImage($image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* @return string
*/
public function getImage()
{
return $this->image;
}
/**
* Set tags
*
* @param string $tags
* @return Blog
*/
public function setTags($tags)
{
$this->tags = $tags;
return $this;
}
/**
* Get tags
*
* @return string
*/
public function getTags()
{
return $this->tags;
}
/**
* Set created
*
* @param \DateTime $created
* @return Blog
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* @return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Set updated
*
* @param \DateTime $updated
* @return Blog
*/
public function setUpdated($updated)
{
$this->updated = $updated;
return $this;
}
/**
* Get updated
*
* @return \DateTime
*/
public function getUpdated()
{
return $this->updated;
}
/**
* Add comments
*
* @param \NewCoInc\AppBundle\Entity\Comment $comments
* @return Blog
*/
public function addComment(\NewCoInc\AppBundle\Entity\Comment $comments)
{
$this->comments[] = $comments;
return $this;
}
/**
* Remove comments
*
* @param \NewCoInc\AppBundle\Entity\Comment $comments
*/
public function removeComment(\NewCoInc\AppBundle\Entity\Comment $comments)
{
$this->comments->removeElement($comments);
}
/**
* Get comments
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getComments()
{
return $this->comments;
}
/**
* Set slug
*
* @param string $slug
* @return Blog
*/
public function setSlug($slug)
{
$this->slug = $this->formatter->slugify($slug);
return $this;
}
/**
* Get slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
}

view raw
Blog.php
hosted with ❤ by GitHub

Entity/BlogRepository.php

<?php
namespace NewCoInc\AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class BlogRepository extends EntityRepository
{
public function getTags()
{
$blogTags = $this->createQueryBuilder('b')
->select('b.tags')
->getQuery()
->getResult();
$tags = array();
foreach ($blogTags as $blogTag)
{
$tags = array_merge(explode(",", $blogTag['tags']), $tags);
}
foreach ($tags as &$tag)
{
$tag = trim($tag);
}
return $tags;
}
}

view raw
BlogRepository.php
hosted with ❤ by GitHub

Wow, that’s a TON of code. I will mention that Symfony2 does offer some tools to help generate this code, which are very helpful. But it feels to me like they’re trying to overcome the shortcomings of the language.

Using Your Django Model’s max_length in Your ModelForm

Let’s say you have a field defined as the following in your model:

class User(models.Model):
first_name = models.CharField(max_length=40, null=True)

view raw
models.py
hosted with ❤ by GitHub

You can then use the max_length in your ModelForm like this:

class RegisterForm(forms.ModelForm):
first_name = forms.CharField(max_length=User._meta.get_field('first_name').max_length)

view raw
forms.py
hosted with ❤ by GitHub

Multiple Files for Django Models

One of the first things I noticed when I started with Django was that models and views were single files. Now I’ve read various opinions on how things should be handled (multiple modules vs. multiple files) but I’m of the opinion that clarity is always better. So even if you have a single model, I’d rather have the file be named something descriptive rather than the generic models.py.

Breaking things up into multiple files is quite straight-forward – delete models.py, create a models directory, add __init__.py and create your model classes. Here’s an example:

__init__.py

from blog import Blog
from blogcomment import BlogComment

view raw
__init__.py
hosted with ❤ by GitHub

Note: In order for syncdb to see your models and work correctly, you’ll need to import the models in __init__.py.

models/blog.py

from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=255)
slug = models.CharField(max_length=255)
author = models.CharField(max_length=100)
blog = models.TextField()
image = models.CharField(max_length=20)
tags = models.TextField()
created = models.DateTimeField('date created', auto_now_add=True)
updated = models.DateTimeField('last updated', auto_now_add=True, auto_now=True)
def __unicode__(self):
return self.title
class Meta:
app_label = 'app'

view raw
blog.py
hosted with ❤ by GitHub

Note: The Meta class is necessary for Django to associate the model correctly. Just set it to the name of your application. Otherwise syncdb won’t see your models or you’ll get a lovely error like the following when one of your models contains a foreign key:

CommandError: One or more models did not validate:
app.blogcomment: 'blog' has a relation with model <class 'app.models.blog.Blog'>, which has either not been installed or is abstract.

view raw
error.py
hosted with ❤ by GitHub

You can then import your models like this:

# import one model
from app.models import Blog
# import a bunch
from app.models import *

view raw
import.py
hosted with ❤ by GitHub