In late 2015 I found myself working with Python, Django and py.test. I was trying to apply some practices that I had been applying for a very long time with different tools, but Django resisted, so here’s the survival kit I had while I was struggling not to compare Django with more modern frameworks. I’m sure a more experienced Django engineer would have found more elegant solutions but these actually did the trick for me.
1. HTML arrays in POST requests
Coming from a background that HTML arrays (or Hashes) are posted in forms can cause some pain in Django. If for example you’re coming from a Ruby on Rails or PHP background, you may have found easy to access HTML arrays by simply checking the request
object in Rails or $_POST
array in PHP. So, for example if you need to post the following form:
<input type="text" name="person[name]" value="John">
In Rails for example, by accessing request[:person][:name]
you would get the expected result, while in Django this is not the case. In order to do that, the following snippet is what you need
import re
def get_dict_array(post, key):
"""
Get an entry from an HTML array eg:
<input type="text" name="person[name]" value="John">
Usage:
get_dict_array(request.POST, "person")
"""
result = {}
if post:
patt = re.compile('^([a-zA-Z_]\w+)\[([a-zA-Z_\-0-9][\w\-]*)\]$')
for post_name, value in post.items():
value = post[post_name]
match = patt.match(post_name)
if not match or not value:
continue
name = match.group(1)
if name == key:
k = match.group(2)
result.update({k:value})
return result
2. Error message overloading for Unique composite keys
Let’s say in your database, there is a table with a composite key, for example in a table of users’ Portfolio Items, the fields user
and url
should be unique together. In case you need to customise the error message for when a user enters an item which already exists, then you’re in for a surprise: You need to overload the model’s unique_error_message
method. Sounds dangerous? Probably because it is…
def unique_error_message(self, model_class, unique_check):
if model_class == type(self) and unique_check == ('user', 'url'):
return _("There already is a portfolio item with the specific url")
else:
return super(MyModel, self).unique_error_message(model_class, unique_check)
3. Not able to chain scopes (use QuerySet instead of ModelManager and objects = QuerySet.as_manager
)
If you have created a model in django and you want to set a few scopes for it, you might need to use a ModelManager
, right?
Well, sort of… You see, chaining ModelManager
objects can be painful, so if for example you need to have
Users.objects.active().social_user().all()
you might get a few not-so-clear error.
The most solid approach I’ve found, is the following:
- Declare a
QuerySet
instead ofModelManager
- In your model, you can set
objects = QuerySet.as_manager()
to use your custom objects manager.
Example:
from django.db import models
class AccountQuerySet(models.QuerySet):
# ... other scopes here ...
def active():
"""Filter accounts by active status"""
return self.filter(is_active=True)
class Account(models.Model):
# ... fields go here ...
objects = AccountQuerySet.as_manager()
4. Converting QueryDict
to plain dict
Casting a QueryDict
to a plain dict
might sound like a trivial thing, but you need to be aware of the following caveat: QueryDict.dict()
and dict(QueryDict...)
return different things, as shown in the output below
In [3]: QueryDict("utm_source=email_campaign").dict()
Out[3]: {u'utm_source': u'email_campaign'}
In [4]: dict(QueryDict("utm_source=email_campaign"))
Out[4]: {u'utm_source': [u'email_campaign']}
5. Package seems missing even though you have just installed
You have just installed a package, for example boto
and you want to run a command, for example fab
.
If you receive the following error:
ImportError: No module named boto
all you have to do is:
export PYTHONPATH=/usr/local/lib/python2.7/site-packages
or in order to make the change permanent, you need to add this line to your shell’s rc
file like .bashrc
or .zshrc
.
6. Uninstall all python packages
Want to start fresh or for some reason remove every python package in your system? There you go:
pip freeze | xargs pip uninstall -y