Entries tagged with django rss

Installing django-threaded-multihost

I'm interested in serving multiple sites from a single django installation. The sites will be exactly the same, though with different db-driven content, so it seems unnecessary to have multiple django settings files and therefore distinct apache configurations. This is usually called "multihost." An early implementation of this idea is django-multihost. django-multihost is good, but it doesn't use the sites framework which can break some of the contrib applications.

Another implementation is django-threaded-multihost, which been factored out of Satchmo. It does respect and use the sites framework, but it has no install documentation. But since it was in Satchmo, there's code using it which can be used to write our own, horray.

First, get the package and do the basic install:

hg clone http://bitbucket.org/bkroeze/django-threaded-multihost/ sudo python setup.py install

Next, add the middleware. The satchmo install docs have this as the last entry in MIDDLEWARE_CLASSES before the satchmo-specific middleware.

MIDDLEWARE_CLASSES = ( ... "threaded_multihost.middleware.ThreadLocalMiddleware", )

django-threaded-multihost has a custom implementation of get_current() for the Site manager. This is the component which allows a single django installation to "easily" distinguish between multiple sites, by freeing the django instance from reliance upon the settings.SITE_ID setting. In order to use this, the threaded_multihost.multihost_patch must be imported somewhere in your installation. The easiest place is probably in an __init__ file for the primary application on your site. So do:

from threaded_multihost import multihost_patch

And that's it. Any further requests to Sites.objects.get_current() will return the appropriate site object depending upon the request URL or fallback on settings.SITE_ID.

except that keyedcache doesn't work. So install keyedcache from bitbucket. then set the settings CACHE_PREFIX = 'whatever' and one oother one.

django, django-tagging from debian repositories

The most recent python-django and python-django-tagging packages for debian are in the experimental repositories, but one probably doesn't want to pull all packages from experimental. APT pinning can be used to prioritize certain repositories for certain packages.

First, the repository should be added to /etc/apt/sources.list

deb http://ftp.us.debian.org/debian experimental main contrib non-free

Then in /etc/apt/preferences, each release needs to be given a priority, and certain packages should be pinned to certain releases. In particular, the django-related packages are pinned to experimental, and python-support is pinned to unstable (the django packages require a higher python-support version than is in stable).

Package: * Pin: release a=stable Pin-Priority: 900 Package: * Pin: release a=unstable Pin-Priority: 800 Package: python-support Pin: release a=unstable Pin-Priority: 1001 Package: python-django python-django-tagging Pin: release a=experimental Pin-Priority: 1001 Package: * Pin: release a=experimental Pin-Priority: 700

As noted in the previous entry, I've stopped using the debian repositories for django packages, but this is how it's done, for reference.

mod_wsgi and virtualenv for multiple django versions

I'm converting a mod_python environment to a mod_wsgi one. I was using the debian packages for both django-tagging and django itself. This has proved to be unacceptable both for upgrades and for keeping sites running. I don't want an apt-get dist-upgrade to break all my django sites (as it did yesterday).

One solution to this is to use virtualenv to keep separate python environments for each site, and then upgrade them individually as necessary. It wasn't clear how mod_python would work with virtualenv (I tried), so I moved to using mod_wsgi which is also reputed to be more efficient. To begin, install both virutalenv and mod_wsgi:

apt-get install python-virtualenv libapache2-mod-wsgi

Next, set up virtualenv. In the project directory (described previously), run:

virtualenv --no-site-packages --unzip-setuptools ENV

Next, grab django and whatever other packages you might want. I move these to a directory with the SVN revision after the package name.

cd ~/libs/ svn co http://code.djangoproject.com/svn/django/trunk/ django mv django django-10645 svn checkout http://django-tagging.googlecode.com/svn/trunk/ tagging mv tagging tagging-154

Symlink the packages in to the virtual environment.

cd ~/libs/django-10645 ln -s `pwd`/django /home/username/blog/ENV/lib/python2.5/site-packages/ cd ~/libs/tagging-154 ln -s `pwd`/tagging /home/username/blog/ENV/lib/python2.5/site-packages/

Create the wsgi config file. This goes in a separate directory from everything else so that the permissions that apache has to read and serve it don't affect anything else.

mkdir ~/blog/apache vi ~/blog/apache/blog.wsgi

The wsgi file should look like the following. Bolded are the parts which will be changed for each project.

ALLDIRS = ['/home/username/blog/ENV/lib/python2.5/site-packages/'] import os, sys, site prev_sys_path = list(sys.path) for directory in ALLDIRS: site.addsitedir(directory) new_sys_path = [] for item in list(sys.path): if item not in prev_sys_path: new_sys_path.append(item) sys.path.remove(item) sys.path[:0] = new_sys_path sys.path.append('/home/username/blog/projects/') os.environ['PYTHON_EGG_CACHE'] = '/home/username/.python-eggs' os.environ['DJANGO_SETTINGS_MODULE'] = 'blogsite.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler()

My version seems to differ slightly from other versions I've seen. In particular my "main site" app is in a subdirectory of projects, so I have appname.settings for the DJANGO_SETTINGS_MODULE. I'm not sure that my way has any advantage over any others, it's just the way it is.

Finally, set up the virtual host:

<VirtualHost XXX.XXX.XXX.XXX> ServerAdmin admin@example.com ServerName blog.example.com CustomLog /var/log/apache2/access.log combined Alias /adminmedia /home/username/blog/ENV/lib/python2.5/site-packages/django/contrib/admin/media Alias /media /home/username/blog/media <Directory /home/username/blog/apache/> Order deny,allow Allow from all </Directory> WSGIDaemonProcess blog.example.com user=www-data group=www-data threads=25 WSGIProcessGroup blog.example.com WSGIScriptAlias / /home/username/blog/apache/blog.wsgi </VirtualHost>

First, two aliases are set, one for the normal media (this is unchanged from a mod_python installation) and the other for the admin media. This should point to the virtualenv version of the admin media. Next, the Directory section allows apache access to the wsgi handler. And finally, wsgi is configured and pointed to the handler in the apache directory. Do a:

apache2ctl restart

And it's done!

apache2/mod_python configuration for django

The request for a working apache/mod_python configuration example comes up frequently on the django IRC channel. Usually a bad configuration is the underlying problem when admin media doesn't show up or when projects are not found on the python path. This is the model I use:

<VirtualHost xxx.xxx.xxx.xxx> ServerAdmin admin@example.com DocumentRoot /home/username/projectname/public_html/ ServerName example.com CustomLog /var/log/apache2/access.log combined Alias /adminmedia /usr/share/python-support/python-django/django/contrib/admin/media Alias /media /home/username/projectname/media <Location "/"> SetHandler python-program PythonHandler django.core.handlers.modpython PythonPath "['/home/username/projectname/projects/'] + sys.path" SetEnv DJANGO_SETTINGS_MODULE mysite.settings PythonDebug On </Location> <Location "/media"> SetHandler None </Location> <Location "/adminmedia"> SetHandler None </Location> </VirtualHost>

This is for a directory layout like this:

/home/username/projectname/ <- your project directory /home/username/projectname/media/ <- media /home/username/projectname/public_html/ <- empty, a harmless place to point apache /home/username/projectname/projects/ <- where all your projects live /home/username/projectname/projects/mysite/ <- your main project/app

Inside mysite/, you have settings.py. In settings.py, you've set MEDIA_ROOT = '/home/username/projectname/media/' and MEDIA_URL = '/media/' and ADMIN_MEDIA_PREFIX = '/adminmedia/'.

Using regex to validate django templatetag arguments

When writing custom templatetags, the arguments from the template are passed as a string to the tag function and it is necessary to parse these in some way to get the individual arguments. For example, with {% mytag arg1 arg2 %}, the templatetag function receives "mytag arg1 arg2" as a string (called "token") and has to figure out what each of the substrings mean. When there's fixed arguments, as in the official documentation's examples, splitting on whitespace can be sufficient: tag_name, format_string = token.split_contents()

When the tag becomes more complex and offers optional arguments, the error checking gets a bit hairy and extremely repetitive. An example from some code I wrote a while back:

def first_gallery(parser, token): tag_name = "first_gallery" try: bits = token.contents.split() except ValueError: raise TemplateSyntaxError, "%r usage: {% %r for app.model primary_key [as gallery] %}" % (tag_name, tag_name) noa = len(bits) if noa < 4: raise TemplateSyntaxError, "%r tag takes at least 3 arguments" % tag_name if bits[1] != 'for': raise TemplateSyntaxError, "%r tag requires the first argument to be 'for'" % tag_name if noa > 4: if noa < 6 or noa > 6: raise TemplateSyntaxError, "%r tag requires exactly 5 arguments when specifying a variable name" % tag_name if bits[4] != 'as': raiseTemplateSyntaxError, "%r tag requires 4th argument to be 'as' when specifying a variable name" % tag_name return FirstGalleryNode(bits[2], bits[3], bits[5]) return FirstGalleryNode(bits[2], bits[3])

Pretty ugly. A much cleaner way to do this is with regular expressions. The downside of using regex is the loss of fine-grained error output to the user of the template tag. For me it's worth it; the regex shouldn't be much more complicated than those in urls.py, and the user should probably already be able to deal with those.

The tag we'll work with is {% nearby_images %}, which sets two context variables ("previous" and "next" by default) which contain images close to the specified one in a particular gallery. You'd use this most often when creating a carousel of thumbnails, as in (php)Gallery. The full syntax is: {% nearby_images [5 near] image in gallery [as previous, next] %}. This example gets ugly if we use the split()-based validation from above since both optional sections have to be accounted for and the magic index numbers shifted around appropriately.

The main regex features we'll use are grouping () and capturing (?P<name>pattern). The most simple part to write a pattern for are the required arguments, including the tag name: nearby_images (?P<image>\w+) in (?P<gallery>\w+). This regex uses the same principle as the urls.py regex in that it captures the bits between the parentheses with the name given in between the angle brackets. We'll use it like so:

>>> p = "nearby_images (?P<image>\w+) in (?P<gallery>\w+)" >>> m = re.match(p, "nearby_images myimage in mygallery") >>> m.groupdict() {'image': 'myimage', 'gallery': 'mygallery'}

Next, add in the optional section by using a parenthetical grouping followed by a '?': nearby_images ((?P<num>\w+) near )?(?P<image>\w+) in (?P<gallery>\w+), making sure to include the whitespace inside the grouping. This pattern can be used in both cases:

>>> p = "nearby_images ((?P<num>\w+) near )?(?P<image>\w+) in (?P<gallery>\w+)" >>> m = re.match(p, "nearby_images myimage in mygallery") >>> m.groupdict() {'image': 'myimage', 'num': None, 'gallery': 'mygallery'} >>> m = re.match(p, "nearby_images 6 near myimage in mygallery") >>> m.groupdict() {'image': 'myimage', 'num': '6', 'gallery': 'mygallery'}

Finally, add the last optional group: nearby_images ((?P<num>\w+) near )?(?P<image>\w+) in (?P<gallery>\w+)( as (?P<pervious>\w+), (?P<next>\w+))? which lets us do:

>>> p = "nearby_images ((?P<num>\w+) near )?(?P<image>\w+) in (?P<gallery>\w+)( as (?P<pervious>\w+), (?P<next>\w+))?" >>> m = re.match(p, "nearby_images 6 near myimage in mygallery as FOO, BAR") >>> m.groupdict() {'previous': 'FOO', 'image': 'myimage', 'num': '6', 'gallery': 'mygallery', 'next': 'BAR'}

So now we've got the arguments to the template node in a dict, and so long as our arguments match the function signature, everything is peachy. Our finished tag function looks like this:

def nearby_images(parser, token): pattern = re.compile("nearby_images ((?P<num>\w+) near )?(?P<image>\w+) in (?P<gallery>\w+)( as (?P<pervious>\w+), (?P<next>\w+))?") m = pattern.match(token.contents) if m: return NearbyImagesNode(**m.groupdict()) else: raise TemplateSyntaxError, "FAIL"

And the matching Node class:

class NearbyImagesNode(Node): def __init__(self, num, image, gallery, previous, next): self.image = image ...

Alternatively, one can use kwargs in the Node as well, but then each argument has to be pulled from the dict:

class NearbyImagesNode(Node): def __init__(self, **kwargs): self.image = kwargs['image'] ...

And that's it. Cleaner argument parsing, with the downside of less fine-grained error messages.

When django admin won’t start

You've just installed django and set up your first project. You can't get to the admin. Why?

You're using sqlite and didn't create a database file.

$ touch database.db

You're using sqlite and didn't specify the full path to your database file in settings.py.

DATABASE_NAME = '/home/user/projects/mysite/database.db'

You're using sqlite and you didn't give apache sufficient permissions to access the DB or the project directory.

$ cd /home/user/projects/mysite/ $ chgrp www-data . database.db $ chmod g+w . database.db

You forgot to enable admin in settings.py.

INSTALLED_APPS = ( ... 'django.contrib.admin', ... )

You forgot to syncdb after enabling admin.

$ ./manage.py syncdb