Saving within a post_save signal in Django

One of the more useful features of the Django framework is it’s extensive signaling capabilities. The ORM throws off a handful of signals every time a model is initialized, modified, saved, or deleted. They include:

  • pre_init
  • post_init
  • pre_save
  • post_save
  • pre_delete
  • post_delete
  • m2m_changed
  • class_prepared

I tend to use the post_save signal fairly often as a good way to get around overriding the default save method on models. Recently though I ran into an issue where I was hitting the “maximum recursion depth exceeded” error when I was saving the current model from within the post_save signal. If you think about it, that makes a lot of sense. You save once, then save again in the signal and then it triggers the signal again. BOOM, infinite loop.

To get around the saving within a post_save signal problem, you just need to disconnect the post_save signal before you call save. After save, you can re-connect it.

from django.db.models import signals
signals.post_save.disconnect(some_method, sender=SomeModel)
some_instance.save()
signals.post_save.connect(some_method, sender=SomeModel)

Programmatically Adding Users to Groups in Django

I recently needed to add a large number of users to a permission group in Django. I had a hard time finding a way to do this in the documentation, so I thought I’d share my solution.

from django.contrib.auth.models import Group, User
 
g = Group.objects.get(name='My Group Name')
users = User.objects.all()
for u in users:
    g.user_set.add(u)

Easy as pie. I originally attempted to do this from the perspective of a user, but as it turns out, doing it from the group perspective is much easier.

Copying Django Model Objects

Django’s ORM is top notch. It provides facilities to do almost anything you can think of with a database, and if it doesn’t, it still lets you execute arbitrary SQL to your hearts content. I’ve been developing Django for close to 2 years now, and still discover facets of it that I never knew existed. For instance, I had a need to duplicate a row in a table, but give it a different primary key. After a quick Google search, I discovered that Django allows you to do the following to copy instantiated model objects.

my_model = MyModel.objects.get(pk=4)
my_model.id = None
my_model.save()

There are a few caveats with doing things this way.

  • Unique Constraints – If you have any unique constraints on the model, the save will not pass validation and fail.
  • ManyToMany Fields – If you need new copies of ManyToMany field values, you’ll need to handle this yourself.

That being said, in many cases duplicating a model instance is as easy as changing it’s ID and saving.

Update All PIP Packages

Revisiting an old Python + Django project made me realize that I needed to upgrade it’s PIP packages. Unfortunately, PIP doesn’t provide a way out of the box to update all of your installed packages at once. To update all of the PIP packages at once, use the following script.

import pip
from subprocess import call
 
for dist in pip.get_installed_distributions():
    call("pip install --upgrade " + dist.project_name, shell=True)

For more detail, check out this question on Stack Overflow.

28 Questions

Over the past few years I’ve tried my hand at launching a couple of different start ups. Over this time period, I’ve collected a set of questions from many different sources (HN, YCombinator, TechStars, etc) that I ask myself before proceeding. They help be decided if this is a real or manufactured problem, if it can be profitable, and if people will buy what I’m selling. My latest project idea is the only idea I’ve had that passes all of these questions to my satisfaction. How do yours hold up?

  1. What are you going to build?
  2. What is the actual problem?
  3. How will you sell your product/service?
  4. What are some potential obstacles?
  5. What are some existing options that solve this problem? How are you different?
  6. Who needs what you’re making?
  7. How do you know that they need it?
  8. How is the problem being solved now? Is it being solved now?
  9. Why isn’t this being done your way already?
  10. How will customers find out about you?
  11. What resistance will they have to trying your product?
  12. What are some key things about your project that outsiders don’t understand?
  13. Who will your first paying customer be?
  14. How might you expand if your initial idea succeeds?
  15. Why did you choose to work on this idea?
  16. Six months from now, what will be you biggest problem?
  17. What are the hard parts of this idea?
  18. Who would you hire/add to your team?
  19. What is the next step in product evolution?
  20. How does your product work?
  21. How big is the opportunity? [market]
  22. How do you know customers need what you’re making?
  23. What domain expertise do you have? Why should you be the one to do this?
  24. What part of your project will you build first? (could be business connections, hardware, software, etc)
  25. How much money could you make per year?
  26. How will you make money?
  27. What have you built in the past?
  28. How would you spend $5,000, how would you use it?

Installing Local Flavor with Django 1.5

I just started a new project using the latest release candidate of Django 1.5. One thing that I needed was the local flavor add-on so I could get a list of US states. This functionality used to be rolled into the main codebase, but it was rightfully removed in Django 1.5. To install the US local flavor package, just use PIP.

pip install https://github.com/django/django-localflavor-us/zipball/master

Now you should be able to import anything you need from the package in the usual manner, except from the new package instead of the old one.

from django_localflavor_us.us_states import STATE_CHOICES

For more information, check out the How To Migrate section of the local flavor docs on the Django documentation site.

Extending Django’s Model.save() Method

I’m currently working on a project where I have a model named ‘Instructor’ with a few fields:

  • first_name – CharField
  • last_name – CharField
  • departments – Many-toMany

I found out at some point that I’d really like a field called “full_name”, which is “first_name” and “last_name” concatenated together. I didn’t want to have to fill the field in though, I wanted it to happen automatically. To accomplish this, I extended Django’s Model.save() method after I added the ‘full_name’ column to my model.

class Instructor(models.Model):
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150)
    departments = models.ManyToManyField(Department)
    full_name = models.CharField(max_length=180, blank=True)
 
    def save( self, *args, **kw ):
        self.full_name = '{0} {1}'.format( self.first_name, self.last_name )
        super( Instructor, self ).save( *args, **kw )

And that’s it! Now every time I create a new instructor with ‘first_name’ and ‘last_name’, it automatically populates the ‘full_name’ field for me.

Deploying Django with Fabric

Fabric is a Python library and command-line tool for streamlining the use of SSH to deployment and sys-admin tasks. In the Django community Fabric has been picked up as the de facto standard for deploying apps, which is a place that it deserves. One thing I noticed though was that there wasn’t a lot of good examples for getting it to place nice with VirtualEnv.

Getting Started

The first thing you need is to make sure that you have Fabric installed.

pip install fabric

Once that’s done, open a file called fabfile.py and add the following imports:

from __future__ import with_statement
from fabric.api import *

Role Defs

Fabric has two functions that get used very frequently: local & run. The local() command will execute whatever you pass into in your local environment. The run() command will execute whatever you put into it in a remote environment defined in your roledefs.

A roledef defines a what a server is. For instance, if you have a nice fleshed out dev-staging-live type of environment:

evn.roledefs = {
	'dev' : ['dev.re-cycledair.com'],
	'staging' : ['staging.re-cycledair.com'],
	'live' : ['re-cycledair.com']
}

Once your roledefs are defined, you can run code on those specific servers by using the @roles decorator.

@roles("dev")
def ls_on_dev:
	run("ls") # Runs 'ls' on dev.re-cycledair.com
 
@roles("staging")
def ls_on_staging:
	run("ls") # Runs 'ls' on staging.re-cycledair.com

At this point, you want to run one of your new Fabric functions, from the directory where your fabfile is run:

fab ls_on_staging

Deployment

Now that you’ve got a handle on the trivial examples, lets make things a little more useful. We’re going to deploy to our ‘staging’ server.

@roles("staging")
def deploy_staging(tag=False):
	code_dir = '/path/to/remote/directory'
	with cd(code_dir):
		run("git fetch")
		if tag:
			run("git checkout %s" % tag)
		else:
			run("git checkout master")
			run("git pull origin master")
		with prefix('source /path/to/virtual/environment/bin/activate'):
			run("pip install -r requirements.txt")
			run("python manage.py syncdb")
			run("python manage.py migrate")
			run("python manage.py collectstatic --noinput")
			run("touch path/to/wsgi.py"

All this code is pretty self-explanatory. The @roles decorator has run execute in the context of “staging”. The run commands are just regular commands you would execute at the terminal if you were manually deploying. The only potential “gotch-ya” is the with prefix call. That basically says “execute the following statements in the context of my virtual environment”. To make everthing go:

fab deploy_staging

will deploy the head of the master branch and

fab deploy_staging:tag=1.0.0

will deploy the 1.0.0 tag.

Done

If all goes well, you can now deploy your app in seconds! If it takes longer then that, you might need to set up your ssh keys you so can login to your server without getting prompted for usernames and password.