Pluggable Django Apps and Django's Admin

There are a lot of us out there who build these reusable, pluggable Django applications and share them with the world. Some of the applications are more useful than others. Personally, I think my applications tend to fall in the less useful category, but I like to build and release them nonetheless.

The Background

The other day, I received a suggestion from one of the users of django-pendulum for how to make it a little more user-friendly. The problem was that he had not yet configured Pendulum for his current Site. As soon as he tried to access his site after installing django-pendulum, he was greeted with a nasty Django exception page, admonishing him to configure Pendulum for the site. It was pretty dirty and effective, but not very pleasant at all.

This user suggested that, instead of displaying the nasty exception page, I redirect the user to a place where they could in fact configure Pendulum. I thought this was a grand idea, and I set out to make it happen last night.

The Dilemma

That's when I realized I had a problem. django-pendulum is intended to be a reusable Django application. Most of the time it might be safe to assume that everyone uses /admin/ to get to the Django administration utility. However, there's also a good chance that this will not be the prefix used to access the administration utility. What do you do when you hardcode your applications to redirect a user to /admin/ and they use something more secure like /milwaukee/ for their administration utility?

So the question is this: how do you determine what URL will bring a user to the Django administration page for a particular Django model based on the specific site's URLconf setup?

The Solution

It turns out that one solution (not sure if it's safe to say "the solution" necessarily) with Django 1.1+ is to have something like this:

from django.core.urlresolvers import reverse
admin_url = reverse('admin_pendulum_pendulumconfiguration_add')

The named URLconf used in the reverse function should ensure that the appropriate administration URL prefix is used for whatever site your application is installed on.

I did a bit of browsing through the Django documentation, but I've never seen it stated anywhere what the names are for administration URLs actually are. I found out about this little nugget by examining the output of the django.contrib.admin.ModelAdmin.get_urls method (see get_urls(self)). From the looks of it, pretty much any Django model registered with your Django administration will have URLconf items similar to the following:

[<RegexURLPattern admin_pendulum_pendulumconfiguration_changelist ^$>,
 <RegexURLPattern admin_pendulum_pendulumconfiguration_add ^add/$>,
 <RegexURLPattern admin_pendulum_pendulumconfiguration_history ^(.+)/history/$>,
 <RegexURLPattern admin_pendulum_pendulumconfiguration_delete ^(.+)/delete/$>,
 <RegexURLPattern admin_pendulum_pendulumconfiguration_change ^(.+)/$>]

In other words, you get 5 URLconf patterns per model: changelist, add, history, delete, and change. These patterns are named, and they all appear to be prefixed with admin_[app_label]_[model]_. In the case of django-pendulum, the application is called pendulum so [app_label] becomes pendulum. The model in question is called PendulumConfiguration so the [model] becomes pendulumconfiguration. I assume this same pattern will be followed for any registered model. Please correct me if I'm wrong.

More Details

For those of you interested in more details, django-pendulum uses a middleware class to redirect users to the screen where they can configure Pendulum for the site in question (unless it's already been configured). Here's my middleware as of revision 18:

from django.contrib.auth.views import login
from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.conf import settings
from pendulum.models import PendulumConfiguration

SITE = Site.objects.get_current()
admin_url = reverse('admin_pendulum_pendulumconfiguration_add')
login_url = getattr(settings, 'LOGIN_URL', '/accounts/login/')

class PendulumMiddleware:
    """
    This middleware ensures that anyone trying to access Pendulum must be
    logged in.  If Pendulum hasn't been configured for the current site, the
    staff users will be redirected to the page to configure it.
    """
    def process_request(self, request):
        try:
            SITE.pendulumconfiguration
        except PendulumConfiguration.DoesNotExist:
            # this will force the user to configure pendulum if they're staff
            if request.user.has_perm('add_pendulumconfiguration') and \
                request.path not in (admin_url, login_url):
                # leave the user a message
                request.user.message_set.create(message='Please configure Pendulum for %s' % SITE)

                return HttpResponseRedirect(admin_url)
        else:
            entry_url = reverse('pendulum-entries')

            if request.path[:len(entry_url)] == entry_url and request.user.is_anonymous():
                if request.POST:
                    return login(request)
                else:
                    return HttpResponseRedirect('%s?next=%s' % (login_url, request.path))

This checks to see if Pendulum has been configured for the site. If not, the middleware will check to see if the current user has permission to add a configuration for Pendulum. If so, they will be redirected to the appropriate configuration screen.

If Pendulum has been configured for the site or the current user does not have permission to add a configuration, the user should be unaffected. If the user is trying to access Pendulum on the site's front-end, the middleware will ensure that they're actually logged in.

As always, suggestions are welcome!

Custom Django Settings and Default Values

This article is more about basic Python than anything else, but I hope it will help some newbies out there get further along faster. I should note that I learned Python by learning Django, so I don't exactly have the best foundation in Python. I think it's getting better though :)

Anyway, there are many occasions when I find myself creating applications that have custom settings which can be defined in a Django project's settings.py file. Sometimes I want to force the developer to define the setting (such as when a third-party API key is required). Other times I want to have sensible defaults which can easily be overridden by the developer if they desire. I struggled for a while to find an elegant solution for providing such default values for my custom settings.

I first tried something like this:

1
2
3
4
5
6
from django.conf import settings

try:
    MY_CUSTOM_SETTING = settings.MY_CUSTOM_SETTING
except AttributeError:
    MY_CUSTOM_SETTING = 'default value'

That seemed to work well enough, but it just felt dirty to me. After a while of doing thing that way, I started doing it like this:

1
2
3
4
5
6
from django.conf import settings

if hasattr(settings, 'MY_CUSTOM_SETTING'):
    MY_CUSTOM_SETTING = settings.MY_CUSTOM_SETTING
else:
    MY_CUSTOM_SETTING = 'default value'

Despite being essentially the same, it felt slightly better than the try..except method. I think I stuck with this method for a bit longer than the first method I mentioned. Finally, I found out that Python's built-in getattr function allows you to specify a default value if it fails to retrieve the value of the attribute you want:

1
2
3
from django.conf import settings

MY_CUSTOM_SETTING = getattr(settings, 'MY_CUSTOM_SETTING', 'default value')

So far, this is the most elegant and efficient method I've found for allowing custom settings with default values. Perhaps there is a better way, and I'd be delighted if someone would enlighten me and the rest of the readers.

New Site Design

I don't know how many of you have noticed this, but I just published a new design for Code Koala. Hooray!!

Along with the change in design came several improvements to the site, including a contact me form and some code optimizations.

Here are some screenshots to illustrate the difference:

Version 1.0

Version 1

Version 2.0

Version 2

Downtime and django-tracking 0.2.7

The Foul Side

Some of you may have noticed the ~11 hours of intermittent downtime that codekoala.com experienced from early on the 24th of January to just a little while ago. I was doing some work on my django-tracking application, which somehow seemed to break my site. CodeKoala.com uses PostgreSQL as the database backend, and as soon as I tried to apply the changes to django-tracking to my site, everything just seemed to die.

The weird thing was that the site would work if I put it on a sqlite or MySQL backend. I didn't change the database schema at all as part of my changes to django-tracking, so it made absolutely no sense. I was in touch with WebFaction's awesome support squad for a good deal of today trying to get things sorted out. We tried just about everything we could think of, short of porting the entire site to a different backend or restoring a recent backup.

Just as things were looking very grim, I tried this command: ./manage.py reset tracking. Voilà! The site started working again. I guess I just had some super funky junk in my tracking application's tables.

On the Brighter Side

As a result of all this work and toil, you all can now enjoy django-tracking 0.2.7! There were a lot of minor code optimizations that went into this release. The biggest change, however, is the fancy "active users map" that you see here.

This feature allows you to display a map of where your recently active users are likely to be based upon their IP address. A list is also available below the map with displays further information about each active visitor. The page updates itself every 5 seconds or so, which means that if a visitor hasn't been active for 10 minutes (or whatever your timeout happens to be), their marker will disappear from the map and their entry in the last will go away too! Pretty dang fancy if you ask me!

If you're interested in downloading and using django-tracking, please check out the links at the end of the article. The Google Code link explains what you need to do and how to configure things.

So folks!! Please play with it!

Announcing Pythug, Idaho's Python Users Group

I've decided something. I love Python. A lot. It's, like, the best programming language in the world. It's such a pleasure to use and program. It works on basically anything. And it's pretty.

Living in Idaho, there aren't quite as many passionate programmers as you might find in many other places of the world (even in a university setting). A while back, I started looking around for a possible Python Users Group (PUG) in my area so I could bask in Python goodness with others. I was very surprised to learn that there wasn't one nearby.

As a result, I took it upon myself to begin to create one, mainly for southeastern Idaho. Since I started using Python about a year and a half ago, I've spread the good word amongst many of my colleagues. Many have started to like Python, others have decided it's not their cup of tea. For the time being, it appears that the PUG will be comprised primarily of my colleagues and myself. However, I would like the group to be a place where beginners and experts alike can come together to learn and instruct each other. I have plans. Devious plans.

The problem I have is a lack of time. That shouldn't surprise anyone, because we all seem to be in the same boat when it comes to time. Anyway, there are still some details that need to be addressed. This will take time. I don't anticipate things being "ready" until April, around the time that my semester ends.

For any of you who are interested in joining such a community, I would encourage you to visit its brand spankin' new Web site to sign up for progress updates. Those who sign up for updates will be counted on the "unofficial" membership roster, but there is no obligation to really become a member when all is said and done. It's merely a way for me to update everyone at the same time a couple times a month.

If you live in Idaho and are interested in joining Pythug, or know someone who might be interested, please get the information in the form. Remember... there are plans. :D

Django + Aggregation = w00t

One of my good friends has just notified me of some great news in Django land: aggregates are upon us! As of revision 9742, Django includes two new operations: annotate() and aggregate().

Ok, ok, so I haven't actually played with Django's new aggregation functionality yet, but I definitely will!! And I'm sure it will rock.

More information:

Python Tip: Conditional Expressions

I learned something new today about Python 2.5 and newer. I thought it was so nifty that I decided to write a blog article about it. Hopefully someone out there finds it as interestingly useful as I do.

One of the things I find myself doing quite often in my code (be it Python, PHP, Java, or what have you) is a simple conditional assignment like so:

if foo:
    bar = 'baz'
else:
    bar = 'qux'

In a lot of languages these days, you can use a ternary operator as follows to turn these four lines into a one-liner:

<?php
$bar = $foo ? 'baz' : 'qux';
?>

However, this ternary operator does not exist in Python. I have used various means in the past to accomplish the same task without using code like we saw in the first code block above, but they were all very hackish. Today I was happy to learn that there is an official way to do it in Python:

bar = ('baz' if foo else 'qux')

I'm not sure which one is more confusing to newbies: Python's conditional expressions or other languages' ternary operator. Personally, I prefer constructs like these to make my code more concise. I my mind, they also make the code more readable. I have heard some folks argue that ternary operators and the like obfuscate the code more than necessary, so they discourage the use of such tactics and recommend using the classic approach featured in the first example.

For those who are interested, I learned about it and a few other neat things in Python 2.5 and newer at http://www.python.org/doc/2.5/whatsnew/pep-308.html.

Miscellaneous Site Updates

I figured I should probably post something since I haven't done so yet this year. I've been making several changes to the site lately. Most of them are pretty subtle, but I hope they're useful to you. Updates include

  • You can now send an article to some friends by clicking on the "envelope" icon in the top-right corner of each article. It's a pretty simple mechanism.

  • You can save any of my articles to your hard drive for later consumption in the form of a PDF. Just click the "save" button in the top right corner of any article, and you will be able to view/save the latest revision of the respective article as a PDF. One thing to note on this, though, is that the program I use to generate the PDFs does not support the line numbers in code blocks. Sorry folks.

    For those of you who are interested, I'm using rst2pdf to generate the PDFs from my reStructuredText-based articles.

  • I've removed the calendar from the sidebar and replaced it with my newest useless side project: django-bibliophile. It allows me to share my reading progress with my visitors, because I know you all care that much. I plan on officially releasing the project in the near future.

  • Pagination has been implemented in parts of the article archive.

  • I've added an "article distribution" chart when looking at a year's blog articles.

  • Other random improvements.

Giving OpenSUSE 11.1 An Honest Chance

I've decided that if I ever want to really understand Linux, I'll have to give as many distributions as possible a chance. In the past, I've tried to use OpenSUSE on my HP Pavilion dv8000 laptop, but it never seemed quite as robust or useful as many other distributions that I've tried on the same machine.

With the recent release of OpenSUSE 11.1, I downloaded the final 32-bit DVD ISO as I normally do for newly released distributions (even if I don't plan on using them--it's an addiction). I proceeded to install the GNOME version of it in a virtual machine to see what all the hubbub was about. Evaluating an operating system within a virtual machine is not the most effective way to do things, but everything seemed fairly solid. As such, and since I have always had difficulties keeping any RPM-based distro around for any length of time, I plan on using OpenSUSE 11.1 through March 2008 (perhaps longer if it grows on me). If it hoses my system, I will go back to something better. If it works, I will learn to use and appreciate it better.

The Installation

The first step when the installation program starts is to choose what language to use, after which you choose the type of installation you're going to be doing. Your choices are:

  • New Installation
  • Update
  • Repair Installed System

You also have the option of installing "Add-On Products" from another media. At this step, I chose to do a new installation.

Next, you get to choose your time zone. The interface is very intuitive. You get a map of the world, and you click on the region you want to zoom in on. Once you're zoomed in, you can select a city that is near you to specify your time zone. Alternatively, you can choose your region and time zone from a couple of drop down lists.

After setting your time zone, you get to choose which desktop environment you want to install. Your choices are:

  • GNOME 2.24.1
  • KDE 4.1.3
  • KDE 3.5.10
  • XFCE 4.4
  • Minimal X Window
  • Minimal Server Selection (Text Mode)

I will choose to install GNOME because it seems to be the desktop of the future, especially with the hideous beast that KDE has become in the 4.x series...

Now you get to play with the partitioning. Usually the installer's first guess is pretty good, but I've got a different arrangement for my partitions, so I'm going to customize things a bit.

The next step is to create a regular, unprivileged user account for your day-to-day computing needs. This screen is pretty self-explanatory if you've ever registered for an e-mail address or installed any other operating system.

One thing that seems to have been added to OpenSUSE 11.1 is the option to use your regular user password as the root password. This is probably a nice addition for a lot of people, but I'd rather feel like my computer is a little more secure by having a different password for administrative tasks.

You're also give a few other options, such as being able to receive system mail, logging in automatically, and modifying your authentication settings. Other than the administrative password option, I left everything the same. If you're like me, and choose to have a different administrative password, you will be prompted to enter the new password at the next step.

Finally, you're shown a summary of the installation tasks that will take place. I'm going to customize my software selection just a bit so I don't have to do it manually after the installation is complete. For example, while I do like GNOME to a degree, I prefer to use KDE 3.5.x, so I will choose to install that environment as well just in case I need the comfort of KDE programs. Also, since I like to use the command line interface for a lot of things, I will choose to install the "Console Tools" package, just because it sounds useful. Lastly, I will choose to install a few development packages, such as C/C++, Java, Python, and Tcl/Tk. These changes bumped up my installation size from about 2.8GB to just over 4GB.

After reviewing the remaining tasks, all you need to do is hit the "Install" button. You will be prompted to verify your desire to install OpenSUSE, after which the package installation will begin. While the installation is taking place, you have the option of watching a brain-washing slideshow, viewing the installation details as it progresses, or reading the release notes.

The actual installation took nearly 40 minutes on my laptop. While this isn't necessarily a great improvement over past releases, I'm sure the story would have been much different had I not customized the software I wanted to have installed. The introduction of installation images a few releases ago drastically improved installation times. If you don't customize your package selection, you'll probably notice the speed difference.

When all of the packages have been installed, the installation program begins to configure your newly installed OpenSUSE for your computer, with a "reboot" in between. This is when all of your hardware, such as your network adapters, graphics adapter, sound card, printers, etc are probed and configured. Strangely enough, this step seems to take a lot longer than it does in Windows, which is usually not the case with Linux. What is OpenSUSE up to I wonder?

When all is said and done, the installation program finishes on its own and loads up your desktop.

Annoyances

There are a couple things that really annoyed me right off the bat about OpenSUSE 11.1. The first was that the loading screen and installation program didn't use my laptop's native resolution. My screen is capable of 1680x1050. The installation program chopped off about 1.25 inches of screen real estate on either side of the program. I don't know if this was an intentional occurrence or not. It seems like the artwork in the installation may have been limited to a non-widescreen resolution. If so, that's completely retarded. I'd like to think that more computer users these days have a widescreen monitor than not, at least the ones who would be playing with Linux.

The second annoyance was that the installation program wouldn't use my external USB DVD drive, which I like to think more reliable than my internal DVD drive. I mean, everything would start up fine--I got the boot menu, the installation program loaded fine, and things seemed like they would work. That's up until the package repositories (the DVD) were being built. Then the USB drive just kept spinning and spinning. Once I popped the disc into my internal drive the program proceeded as expected.

Your Desktop

I thought it was interesting that I chose to install GNOME, but since I chose to install KDE 3.5.10 alongside it that's what it booted me into after the installation was completed. No real complaints, though, since I prefer KDE anyway. Nonetheless, I switched back to GNOME to stretch my limits all the more. At least the desktop took up the full resolution that my screen can handle, unlike the installation program and boot screen.

Things seem fairly responsive... nothing like Slackware though. I just received a little popup notification with an excuse for the lag I might be experiencing: the daily indexing has commenced and should be finished soon. Whatever it's up to, it's taking up a consistent 100% of my CPU. How nice. I hope whatever it's indexing ends up being useful.

Sound worked right from the get-go, which is nice. Hardware acceleration for my Radeon Xpress 200M doesn't work, nor does my Broadcom wireless card. These will be fixed soon.

The Wireless

It looks like the most important step in getting my wireless to work was executing these commands as root:

/usr/sbin/install_bcm43xx_firmware
modprobe b43

I did a lot of stuff to try to get my wireless to work before I executed those commands, but nothing did the trick until I tried them. Also, to make the wireless available each time you reboot without requiring the modprobe b43 command, you need to edit your sysconfig.

To do that, open up YaST and find the "/etc/sysconfig Editor" option. Expand the "System" node, and navigate to Kernel > MODULES_LOADED_ON_BOOT. Then put b43 in the value box. Apply the changes. The next time you reboot your computer, the wireless should be available from the get-go.

The Video Card

This section only really applies to folks with ATI graphics adapters.

I found a tutorial on ubuntuforums.org, strangely enough, which described the process for getting ATI drivers to work on OpenSUSE 11.1. The first step is to download the official ATI drivers for Linux. Each of these commands should be executed as root:

wget https://a248.e.akamai.net/f/674/9206/0/www2.ati.com/drivers/\
linux/ati-driver-installer-8-12-x86.x86_64.run

Next, you need to download the kernel source and ensure that you have a few other utilities required for compiling a kernel module:

zypper in kernel-source gcc make patch

Now you should be able to run through the ATI driver installation utility, accepting all of the defaults:

sh ati-driver-installer-8-12-x86.x86_64.run

If you're on 64-bit OpenSUSE, you need to take an extra step to make the driver available:

rm /usr/lib/dri/fglrx_dri.so && ln -s /usr/lib64/dri/fglrx_dri.so \
/usr/lib/dri/fglrx_dri.so

Backup your existing xorg.conf configuration file and configure Xorg to use the new driver:

cp /etc/X11/xorg.conf /etc/X11/xorg.conf.orig
aticonfig --initial -f

Finally, configure Sax2 with the ATI driver:

sax2 -r -m 0=fglrx

Upon rebooting your computer, you should be able to use the hardware-accelerated 3D capabilities of your ATI card. To verify that things are up and running, execute fglrxinfo as a normal user. This command renders the following output on my system:

display: :0.0  screen: 0
OpenGL vendor string: ATI Technologies Inc.
OpenGL renderer string: ATI Radeon Xpress Series
OpenGL version string: 2.1.8304 Release

Other Thoughts

After having played with OpenSUSE 11.1 for a couple hours, I think I might be able to keep it around for a little while. Despite the lack of speed exhibited by other Linux distributions, the "stability" that OpenSUSE seems to offer is attractive to me. It will likely take some time to get used to RPMs over DEBs for package management.

How bad can it be? I mean, it comes with OpenOffice 3.0.0, which is nice. It can handle dual-head mode on my laptop thanks to Xinerama, which no other distro to date has been able to do. This gives me a little more screen real estate to work with, which helps out a lot when I'm developing a Web site or working in an IDE. The package managers are slow, but how often do you really install software anyway?

Again, we'll just have to see how things pan out. Let's hope it turns out to be a positive experience.

Announcing django-smileys 0.1.0-rc1

I've released yet another absurdly useless application today. With all of my dirty finals lately, I needed something a little more leisurely to think about. I noticed that I put a lot of those funky smiley codes in my articles and whatnot, so I decided to beautify them a little by replacing the codes with emoticon images a la those silly forum sites I used to be crazy about as a kid.

It was nice to have a good 15-minute breather to work on this. Being such a quick application, I'm sure that there's a lot lacking in it. If you want more features or find a problem with it, just give me a holla and I'll try to update things.

Without any further ado, checkout the project pages:

B-)