Django Dev, Test, and Prod Environments Revisited
Posted by on March 10, 2009 in the Django category.
Back in November, I posted a detailed entry about the environments I used to develop, test, and deploy my Django applications. Since then, I've made a lot of changes to my configuration that I feel have helped boost my productivity and effectiveness. I wanted to talk a little bit about these changes here, and invite readers to share ideas below in the comments about how they do things differently.
This article was written to build upon the original entry and highlight the changes. You may want to read that entry first to better understand what I refer to here.
I still use Ubuntu exclusively on my desktop, laptop, and servers. I've never been more convinced that the era of the Microsoft monopoly is coming to a close, and I never cease to be amazed by the work that the Ubuntu developer community puts out. I also still use the latest version of Eclipse to develop with, using Aptana, PyDev, and Subclipse. Another plugin that I've incorporated, however, is MercurialEclipse.
Version Control
Yes, I've been won over by the dark side of distributed version control systems. There are several reasons that I've decided to switch from Subversion to Mercurial for my internal source control. One of the biggest reasons is speed. I was already noticing sluggishness in my big all-in-one SVN repo. I started researching alternatives, and found a lot of anecdotal evidence suggesting that distributed systems were much faster. This article by Robert Fendt came out after I switched, but it further reinforces that point. SVN lags behind the three major DVCS systems in most operations.
Another advantage that I wanted was the ability to make multiple "personal" commits to your local copy of the repository before pushing major changesets to the central repository (which itself is really just a convention in DVCS's).
Mercurial was the obvious choice for me because it was based on Python, the language I am hopelessly biased towards. (Edit: See comments below - Bazaar is also based on Python.) Creating a new repository in Mercurial takes all of a couple seconds. This turned out to be a major benefit to me. As I mentioned in my previous environment entry, I was organizing all of my projects under one SVN repo. With Mercurial, it was very easy to create separate repositories for each project which are automatically picked up by the Web interface.
I no longer use the branches, tags, and trunk convention because I've found it's easier for me without them. When I need to test something, I push my development repo to the my central server. I then pull from the testing server, update, and test. When I'm ready to deploy, I push any changes back to the central server and then use Fabric to pull the changes to the production server and update. I'll talk more on Fabric later in this post.
Settings
I've tried a couple of different ways to manage settings between the development, testing, and production environments. I've finally settled on a way using Python's config file parser that makes it completely automatic and invisible to me during regular use. First, I filled in my settings.py file with all of my production settings. Then, I created a directory in my home folder on each of my environments called .adoleo. Within the config file, I created a category called environment and a setting called type. For my development machines, I set the type to dev. For testing, I set it to test. For production, I set it to prod. That way, I can read the config files with Python and automatically override the production settings with appropriate settings for my other environments. Here's an example:
The top of my config file contains my production settings like any other Django project would.
DEBUG = False
TEMPLATE_DEBUG = DEBUG
ADMINS = (
('Brandon Konkle', 'brandon.konkle@adoleo.com'),
)
MANAGERS = ADMINS
DATABASE_ENGINE = 'my_db_engine'
DATABASE_NAME = 'my_db_name'
DATABASE_USER = 'my_db_user'
DATABASE_PASSWORD = 'my_db_password'
DATABASE_HOST = ''
DATABASE_PORT = ''
And continuing on as usual until I get down to the bottom of the settings file:
# Import config to determine environment, and then override prod settings
import os
import sys
import ConfigParser
config_loc = os.path.expanduser('~') + '/.adoleo/config.file'
env_type = False
if os.path.exists(config_loc):
config = ConfigParser.SafeConfigParser()
config.read(config_loc)
try:
env_type = config.get('environment', 'type')
except:
pass
if env_type == 'dev':
DEBUG = True
DATABASE_ENGINE = 'my_dev_db_engine'
DATABASE_NAME = 'my_dev_db_name'
DATABASE_USER = 'my_dev_db_user'
DATABASE_PASSWORD = 'my_dev_db_password'
MEDIA_ROOT = '/my/dev/media/root'
TEMPLATE_DIRS = (
'/my/dev/templates',
)
elif env_type == 'test':
DATABASE_USER = 'my_test_db_user'
DATABASE_PASSWORD = 'my_test_db_password'
MEDIA_ROOT = '/my/test/media/root/'
TEMPLATE_DIRS = (
'/my/test/templates',
)
This way, if the config file contains a dev or test indicator, I override the needed settings with values for that particular environment. If the value is prod, or there is a problem reading the config file, it defaults to the production settings.
Development Database Storage
For awhile I ran a full-scale PostgreSQL server on each of my development machines. As I mentioned in my previous post, I had to run pg_dumps every time I needed to switch back and forth between one of the development machines. Also, if I needed to wipe and reinstall a dev machine, I had to recreate the PostgreSQL environment completely. Fixtures are one way to address this problem, but I ran into some issues that made them a bit tricky.
My solution was to use SQLite for development. I don't know why I didn't think of it earlier - it's so easy and simple! I go ahead and commit my local database file to the Mercurial repository, and then if I need to switch machines I can do a simple hg pull && hg up on the new machine and can instantly use the current dev database.
Deploying to Testing and Production with Fabric
I've recently started using the excellent Fabric tool to deploy my projects to testing and production. Working with Mercurial was a challenge at first until I discovered the -R option, which designates the repository to work with. In my fabfile.py script I first set a few details based on the environment:
repo_user = 'mercurialuser'
repo_pass = 'mercurialpassword' # Could also use getpass()
repo_url = 'central.mercurialserver.com/reponame'
def test():
config.fab_hosts = ['testing.server.com']
repo_loc = '/mercurial/test/repo/location'
def production():
config.fab_hosts = ['production.server.com']
repo_loc = '/mercurial/prod/repo/location'
Then I use commands like this to deploy:
def deploy():
run('hg -R %s pull http://%s:%s@%s' % (repo_loc, repo_user,
repo_pass, repo_url))
run('hg -R %s up' % repo_loc)
That's it for now. What tricks do you use for managing your Django environments?
Comments are closed.
Comments have been closed for this post.
Other posts:
« Ajax Django Comments With jQuery | Installing Adobe Air on Ubuntu Jaunty 64-bit »
Categories
By Month
- November, 2009
- October, 2009
- August, 2009
- April, 2009
- March, 2009
- February, 2009
- December, 2008
- November, 2008
- October, 2008
- September, 2008
By Year
Atom Feeds
Latest Comments
-
-posted by hokOffillaCal on Easily Working With Pinax on Multiple Machines, 1 day ago
-
-posted by test on DRY Ajax Comments, 1 week, 6 days ago
-
-posted by test on DRY Ajax Comments, 1 week, 6 days ago
-
-posted by srn on DRY Ajax Comments, 1 month, 1 week ago
-
-posted by Brandon Konkle on DRY Ajax Comments, 4 months ago
12 comments so far:
I like now to use a custom development.ini as in turbogears and pylons.
for instance, you can configure per-application settings as such:
[myapp:mymodule] database_name = "blah" database_user = "quux" debug = true
i know this is not the configuration scheme proposed by many django peoples but this scheme seems very scalable and convenient. :)
what i don't like seeing or having to manage is a huge settings.py file with if/elif/else blocks that are typically used for scripting a particular logic...
my 2 cents!
peace!
-erob
Posted by erob 1 year ago.
Great article! It's always interesting to me to see how others in the community are doing things. Thanks for sharing.
Where I work, we've started using the server's hostname as the primary factor for determining which environment we should be running in (dev, test, prod). It works well for our setup because our servers follow a specific naming convention, but I can see how that would become very ineffective in larger environments with no set naming convention.
Sadly, I am still set on SVN... More out of habit and comfort than anything else. I've dabbled with other VCSs before, but most of them feel clunky to me just because I am used to being able to do things quickly (ie knowing the commands) with SVN. One day I'll bite the bullet and switch to git or something more fun.
One thing that I have picked up over my time with Django by way of "efficient" settings is the use of relative paths. With many of my own projects, I develop on several different machines. Since one such machine is Windows-based, I can't very well have a mirrored directory structure that my settings expect to see. Also, I have considerably more limited control over the directory structure on my host, so relative paths are extremely convenient. Therefore, I do something like this at the top of my
settings.py::When I need to refer to a file on the server, I do something like this::
...which takes care of translating forward slashes and back slashes depending on which operating system you're using. That is one "improvement" in particular which I truly enjoy.
Thanks again!
Posted by codekoala 1 year ago.
I can definitely see what you're saying, Erob. My method does increase the size and complexity of the settings.py file somewhat. I previously used separate dev_settings.py and test_settings.py files, but I personally found it easier to just handle it at the bottom of the settings file. One thing I love about Django is it's flexibility - there are so many different ways to do things!
By the way - I plan to fix the Markup support for the comments this week, so things should start to look much better down here at the bottom of the page. :-)
Posted by Brandon Konkle 1 year ago.
"Mercurial was the obvious choice for me because it was based on Python"
Ever tried Bazaar? It's also based on Python, but is primary developed by Canonical and is currently part of the GNU project.
It also supported by Launchpad which is the most collaborative tool I've seen.
Posted by Daniel Nyström 1 year ago.
@codekoala:
yeah, socket.gethostname() is really good for this.. now just trying to push this to your collegues might not be that easy... ;)
@Brandon:
i agree, django is very flexible. especially in regards of using alternative configuration schemes. besides that, nice article and web site ;)
cheers, -erob
Posted by erob 1 year ago.
@codekoala: I can definitely see how that would help simplify paths. If I remember correctly, Pinax does something like that to add it's plugin set to the Pythonpath.
@Daniel: I'll admit, you caught me off guard there. I knew Bazaar was a Canonical project, but I didn't realize it was Python based. In looking at the benchmarks at Robert Fendt's site, however, it looks like Mercurial and Git outperform Bazaar in a lot of ways. I'll definitely join in your admiration of Launchpad, though - it's a great project management tool.
@erob: Thank you very much for the compliments! Thanks for joining in the conversation!
Posted by Brandon Konkle 1 year ago.
@erob: as a matter of fact, we have a customized default
settings.pytemplate that an in-house "auto-create/auto-deploy" script uses to configure our sites. This customizedsettings.pyscript has thesocket.gethostname()call in it, so my coworkers would have to go out of their way to change it... most don't bother :)@Daniel or anyone else: I realize this probably isn't the proper place to start such a discussion, but just briefly, what are some of the pros/cons of bazaar vs mercurial (vs git vs *)? Obviously, your response is likely to contain some sort of bias, but I'd appreciate it if the bias could be limited as much as possible... I have a difficult time finding reviews of these systems that are based solely on facts instead of pure opinion (this is another reason I'm still with SVN). Thanks!
Posted by codekoala 1 year ago.
There, I cleaned the up the Markdown rendering and added a quick cheat-sheet at the top of the comment form. Comments should be a lot more usable now. Coming soon: the return of Previewing.
Posted by Brandon Konkle 1 year ago.
@codekoala I don't know if you've seen the wikipedia comparison, but it is quite interesting. Not all that technical, but a good overview of features. http://en.wikipedia.org/wiki/Comparison_of_revision_contr...>
Great post this, btw!
Posted by Stii 11 months, 2 weeks ago.
I use Harness as a wrapper for my Django settings. Hard to say which way is "better". Harness does generally the same but also hides away the boilerplate code and addresses the issue of absolute paths to templates, SQLite database, etc.
Posted by Andy 10 months, 4 weeks ago.
@Andy: Thanks! I hadn't heard about Harness before. It looks like a cleaner solution, so I'll definitely give it a try.
Posted by Brandon Konkle 10 months, 4 weeks ago.
What I do is, I do my development locally, commit, and then push it out to my private bitbucket repo.
From there I pull it down to my test environment and the cycle continues till I finally pull the changeset down to my production.
Posted by C Moran 7 months, 2 weeks ago.