Django Hidden Fields

Quick shortcut for including hidden fields in your forms:

forms.py

class SomeForm(forms.Form):
hidden = forms.CharField(required=False, max_length=50, widget=forms.HiddenInput())

view raw
forms.py
hosted with ❤ by GitHub

template.html

{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}

view raw
template.html
hosted with ❤ by GitHub

Now you don’t have to worry about each and every hidden field.

Override Django Form.is_valid()

For this project I won’t be using Django’s built-in user authentication application. It’s a bit restrictive for my taste (but getting better in Django 1.5 from what I can tell).

One common use case is to validate the user’s username and password when logging in. Rather than place this logic within the view, it’s cleaner to override the default is_valid method with some additional logic that checks the password.

Here’s an example form with a username and password field. The username field allows either a username or email address, and I’m using Django’s built-in password hasher.

forms/signin.py

from django import forms
from app.models import User
from django.contrib.auth.hashers import check_password
from django.db.models import Q
class SignInForm(forms.Form):
username = forms.CharField(max_length=User._meta.get_field('email').max_length)
password = forms.CharField(min_length=6, max_length=16, widget=forms.PasswordInput())
go = forms.CharField(required=False, max_length=50, widget=forms.HiddenInput())
def is_valid(self):
# run the parent validation first
valid = super(SignInForm, self).is_valid()
# we're done now if not valid
if not valid:
return valid
# so far so good, get this user based on the username or email
try:
user = User.objects.get(
Q(username=self.cleaned_data['username']) | Q(email=self.cleaned_data['username'])
)
# no user with this username or email address
except User.DoesNotExist:
self._errors['no_user'] = 'User does not exist'
return False
# verify the passwords match
if not check_password(self.cleaned_data['password'], user.password):
self._errors['invalid_password'] = 'Password is invalid'
return False
# all good
return True

view raw
signin.py
hosted with ❤ by GitHub

You can then simply use form.is_valid() in your view:

views/signin.py

if form.is_valid():
# … logic to login the user …
# redirect to a protected page
return HttpResponseRedirect(reverse('account'))

view raw
signin.py
hosted with ❤ by GitHub

Checking if a GET Variable Exists in Django

http://stackoverflow.com/questions/2422055/how-to-check-if-request-get-var-is-none

First, check if the request.GET dict contains a parameter named q:

if request.method == 'GET' and 'q' in request.GET:

view raw
basics.py
hosted with ❤ by GitHub

Now ensure the variable has a value:

q = request.GET['q']
if q:
# Do processing here

view raw
basics.py
hosted with ❤ by GitHub

Creating a Custom Django Server on RightScale

UPDATE: RightScale has released version 13.2 of their server templates. I have updated the scripts to reflect the various changes and bug fixes in the new templates.

RightScale has released a beta server template for Django which makes it incredibly easy to get a server up and running on EC2. One drawback with this server template is that it is configured to work with a load balancer which isn’t always necessary.

To create a custom server template that doesn’t require a load balancer, start with the Base ServerTemplate for Linux (current version is 13.2) (be sure to use the Chef-based template rather the RightScript-based version). Then add the necessary scripts so that your boot and operational configs look like the following:

Now you’ve got a server that runs Apache, mod_wsgi and Django 1.4. It will also pull your code down from GIT or SVN and will install any PIP packages from your requirements.txt file (I prefer to install this stuff as part of the initial server config as shown below, or via a controlled server upgrade rather than automatically via requirements.txt).

But what about missing or outdated software? The following is a sample script that installs and upgrades a whole bunch of stuff. It also reconfigs Apache and runs compress. This was added as the last script in the template’s boot scripts. (Note – this script assumes you’re using the Ubuntu RightImage)

#!/bin/bash -e
#
# Test for a reboot, if this is a reboot just skip this script.
#
if test "$RS_REBOOT" = "true" ; then
echo "Skip software update on reboot."
logger -t RightScale "Software update, skipped on a reboot."
exit 0
fi
#
# install and upgrade a bunch of stuff
#
if [ $RS_DISTRO = ubuntu ]; then
# fix sftp issue
perl -p -i -e "s/\/usr\/libexec/\/usr\/lib/g" /etc/ssh/sshd_config
service ssh restart
# upgrade pip
yes | pip install –upgrade pip
# after updating pip, need to relink it so do_update_code works
ln -s /usr/local/bin/pip /usr/bin/pip
# install this for pip
apt-get install -y libyaml-dev
# install these for compressor
apt-get install -y default-jre
apt-get install -y npm
npm install -g less
npm install -g coffee-script
gem install sass
# link these so compress can find them
ln -s /usr/local/bin/lessc /usr/bin/lessc
ln -s /usr/local/bin/sass /usr/bin/sass
ln -s /usr/local/bin/coffee /usr/bin/coffee
# install Django beta
yes | pip uninstall Django
wget https://www.djangoproject.com/download/1.5b2/tarball/
mv index.html Django-1.5b2.tar.gz
tar xzvf Django-1.5b2.tar.gz
cd Django-1.5b2
python setup.py install
cd ../
rm -rf Django-1.5b2
rm Django-1.5b2.tar.gz
# install pip packages
yes | pip install –upgrade distribute
yes | pip install –upgrade MySQL-python
yes | pip install django-compressor
yes | pip install django-appconf
yes | pip install versiontools
yes | pip install –upgrade simplejson
yes | pip install "BeautifulSoup<4.0"
STATIC_DEPS=true
yes | sudo pip install lxml
yes | pip install odict
yes | pip install ply
yes | pip install html5lib
yes | pip install httplib2
yes | pip install slimit
yes | pip install boto
yes | pip install oauth2
yes | pip install python-openid
yes | pip install PyYAML
yes | pip install pytz
yes | pip install py-bcrypt
yes | pip install yolk
# cleanup simplejson (fixes yolk)
rm /usr/lib/python2.7/dist-packages/simplejson-2.3.2.egg-info
# remove generated wsgi.py in site root
rm /home/webapps/$APPLICATION/wsgi.py
# update apache config
perl -p -i -e "s/$APPLICATION\/wsgi.py/$APPLICATION\/conf\/wsgi.py/g" /etc/apache2/sites-available/http-80-localhost.vhost.conf
# copy and compress assets
python /home/webapps/$APPLICATION/manage.py collectstatic –noinput -i css -i js
python /home/webapps/$APPLICATION/manage.py compress
# restart apache
service apache2 restart
fi
logger -t RightScale "Software update completed."

view raw
install.sh
hosted with ❤ by GitHub

Django Messages Framework

The previous post made use of the Django messages framework to display a one-time message after an HTTP redirect.

Using the messages framework is simple. First, as explained in the docs, adjust your settings file:

  • ‘django.contrib.messages’ is in INSTALLED_APPS
  • MIDDLEWARE_CLASSES contains ‘django.contrib.sessions.middleware.SessionMiddleware’ and ‘django.contrib.messages.middleware.MessageMiddleware’
  • TEMPLATE_CONTEXT_PROCESSORS contains ‘django.contrib.messages.context_processors.messages’

You can now use the framework in a view:

from django.contrib import messages
# within a view method
messages.add_message(request, messages.INFO, 'This message will appear after the redirect!')

view raw
view.py
hosted with ❤ by GitHub

And display it within a template:

{% if messages %}
<div class="notice">
{% for message in messages %}
{{ message }}
{% endfor %}
</div>
{% endif %}

view raw
template.html
hosted with ❤ by GitHub

Sending Django Emails via AWS SES

Boto is the de facto standard for working with the AWS API from Python. Unfortunately their SES implementation only sends text or html emails, not both. You need to drop down to their send_raw_email method to send a multi-part email.

Using this Gist as a guideline, here’s an implementation of sending a multi-part email via a library class:

amazonses.py

from email.utils import COMMASPACE
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from boto.ses import SESConnection
from django.conf import settings
class SESMessage(object):
def __init__(self, source, to_addresses, subject, **kw):
self.ses = SESConnection(settings.AWS_ACCESS_KEY, settings.AWS_SECRET_KEY)
self._source = source
self._to_addresses = to_addresses
self._cc_addresses = None
self._bcc_addresses = None
self.subject = subject
self.text = None
self.html = None
self.attachments = []
def send(self):
if not self.ses:
raise Exception, 'No connection found'
if (self.text and not self.html and not self.attachments) or\
(self.html and not self.text and not self.attachments):
return self.ses.send_email(self._source, self.subject,
self.text or self.html,
self._to_addresses, self._cc_addresses,
self._bcc_addresses,
format='text' if self.text else 'html')
else:
if not self.attachments:
message = MIMEMultipart('alternative')
message['Subject'] = self.subject
message['From'] = self._source
if isinstance(self._to_addresses, (list, tuple)):
message['To'] = COMMASPACE.join(self._to_addresses)
else:
message['To'] = self._to_addresses
message.attach(MIMEText(self.text, 'plain'))
message.attach(MIMEText(self.html, 'html'))
else:
raise NotImplementedError, 'Attachments are not currently supported.'
return self.ses.send_raw_email(message.as_string(), source=self._source,
destinations=self._to_addresses)

view raw
amazonses.py
hosted with ❤ by GitHub

You’ll also need to add your AWS access key and secret to your settings file:

settings.py

# Amazon AWS
AWS_ACCESS_KEY = 'AKIA……..'
AWS_SECRET_KEY = 'w71v……..'

view raw
settings.py
hosted with ❤ by GitHub

You can then use the class like this:

from app.lib.amazonses import SESMessage
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# grab the form data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
body = form.cleaned_data['body']
recipient = 'recipient@site.com'
# render the template with the submitted data
rendered = render_to_string('email/contact.html', {'name': name, 'email': email, 'subject': subject, 'body': body})
# create the message and send the email
# the from address must be a verified sender in SES
msg = SESMessage('valid_sending_address@site.com', recipient, subject)
msg.text = rendered
msg.html = rendered + ' html'
msg.send()
messages.add_message(request, messages.INFO, 'Your contact enquiry was successfully sent. Thank you!')
return HttpResponseRedirect(reverse('contact'))
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})

view raw
lib.py
hosted with ❤ by GitHub

Multiple Files for Django Views

Separating your views into multiple files works much the same way as models except for one difference – importing the views in __init__.py isn’t necessary unless you want to type a little less.

Without importing them in __init__, you’ll need to do this:

from app.views.blog import Blog

view raw
import.py
hosted with ❤ by GitHub

But if you add them, you can do the following instead:

# import one view
from app.views import Blog
# import a bunch
from app.views import *

view raw
import.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