Adamanteus 0.5 released, now with more PostgreSQL!
Jun 5th, 2010
I've now finally gotten around to adding PostgreSQL support to Adamanteus! It wasn't really difficult, just hard to find time to do with everything that's been going on lately (quite busy at work and with the new house). The usage of Adamanteus hasn't changed at all, now you can just specify 'postgres' as your backend rather than just 'mongodb' or 'mysql'.
There is one slight caveat, however: the pg_dump utility does not allow you to provide a password non-interactively. For the time being, at least, that means that if you're using Adamanteus to back up a PostgreSQL database you can't specify a password (it will throw an exception if you do). The solution to this is to either 1) set up a read-only passwordless user for running backups, or 2) set up a .pgpass file on the machine from which you intend to run your backups (documentation here). I recommend the .pgpass option, it's quick and easy.
Adamanteus: versioned backups of databases using Python and Mercurial
Mar 26th, 2010
Backing up databases is one of those things that I've always felt could be done in a better way. Traditionally I've done it with a simple shell script that used mysqldump or pg_dump to dump my database to an SQL file named using a timestamp, compress it, and maybe scp it off to some remote server for redundancy. This approach works just fine, except that I recently took a look at my backup directory for a project using that setup only to discover that there were nearly 5000 backup files taking up 11 GB (and this is using bzip2 to compress them!). Obviously not an optimal situation, especially considering that really very little changes from backup to backup, and it's quite possible that nothing changes at all for some of them. It simply makes no sense to store an entire dump of your database every single time!
Fortunately, this is a very familiar situation that we've got advanced tools to handle: version control systems. So I decided to write a little program to replace my shell script that would use a modern, advanced version control system to provide a much more reasonable solution. What I came up with was Adamanteus, a command line program written in Python that allows you to back up your database into a mercurial repository. It currently supports MongoDB and MySQL, and I plan on adding PostgreSQL support this weekend.
Using Mercurial immediately solves basically all the problems with my original approach. It stores diffs rather than full files, meaning you aren't wasting space with a lot of duplicate information. It also handles compression transparently keeping the file sizes down even for the diffs. Plus, because Mercurial is a distributed version control system it's very easy to provide redundancy by pushing and pulling to and from remote repositories. (Pushing/pulling to/from remote repositories isn't currently implemented, but that's also in my plans for this weekend.)
The project is far from complete, but I think it's sufficiently far developed to release as 0.1. Plans for the 1.0 release include:
- PostgreSQL support
- The ability to restore your database from a particular revision in the repository
- automated cloning/pushing/pulling of the repository
- Integration with Django as a management command
I think this is actually pretty close and it probably won't take too long for me to implement all of those, so hopefully I'll be able to push out a 1.0 release very soon. The one other issue holding up 1.0 is that I'd like to wait for MongoDB 1.5 which will bring mongoxport functionality in line with mongodump which is what I'm currently using. The issue here is that mongodump produces binary data files which don't play quite as nice with version control and lose you the advantage of only storing diffs. Mongoexport will export JSON or CSV files, which will allow it to take full advantage of Mercurial, but until 1.5 there's no easy way to use mongoexport to dump all the collections in a database which is the default behavior for mongodump.
Anyway, I'm definitely looking forward to some feedback on this project, as I suspect it could be quite useful to many people. Contributions are always welcome as well!
Storing IP addresses as integers in Python
Oct 7th, 2009
I just saw this very interesting (for a programmer) blog entry on Storing IP addresses as integers. As the article says, anyone who wants to store IP addresses in a database is generally going to do as a string. However if you're really concerned about memory efficiency, you might want to find a lighter data type to use. Since an IP address is really just a collection of integers it would make sense to store it as an integer, however doing so without losing important information (specifically, the placements of the dots) becomes an issue.
The blog post in question introduces the the pack() and unpack() functions, which 'allow you to create and extract data into and out of binary-packed strings' and provides the Ruby code necessary to encode an IP address as a simple integer and decode it back to a dotted quad. I thought this was pretty cool, so I decided to write the equivalent Python code. This is what I ended up with:
It seems to work as advertised, though the packed string the encode() function returns is different from what Ruby gives you, so they won't interoperate (it would probably only require changing the pack() and unpack() formats to do so, but I didn't feel like experimenting to figure out which ones).
Of course, being a Django guy, the immediate application of these functions that I see is creating a new IPAddressField that stores the address as an integer instead of a string transparently.
BostonChefs 2.0 is live!
Jan 14th, 2009
This post is actually a few weeks overdue, but we were away on vacation for a couple weeks, and I'm still in the process of catching up from everything. Anyway, the new BostonChefs.com is live! There haven't been any major changes since the public beta I announced a few months ago, but there have been a few minor tweaks and even a couple new features added. This is my first major Django project to go live, and while there's still some work to be done it's nice to see the product of so much work finally going out the door.
But there's no rest for the weary, and I've got a fairly large number of more projects in the pipeline including at least three more Django projects of varying sizes, and one in CodeIgniter. The next few months, if not the whole of 2009, promise to be quite busy!
Hours of Operation
Dec 16th, 2008
As you probably know, I've been working on a Django-based re-build of BostonChefs.com (the new version of which is actually live now, but due to DNS propagation issues isn't yet available to 100% of people which is why I haven't yet written a post about it). Among other things, BostonChefs.com provides information on some of the fantastic restaurants in the Boston area. One piece of information it provides is the hours of operation of those restaurants. In order to store this information I created a model called HoursOfOperation. It looks like this:
class HoursOfOperation(models.Model):
DAY_CHOICES = (
('0', 'Sun'),
('1', 'Mon'),
('2', 'Tue'),
('3', 'Wed'),
('4', 'Thur'),
('5', 'Fri'),
('6', 'Sat'),
)
restaurant = models.ForeignKey("Restaurant")
meal_period = models.ForeignKey("MealPeriod")
day = models.CharField(max_length=3, choices=DAY_CHOICES)
open_time = models.TimeField(default=datetime.datetime.now)
close_time = models.TimeField(default=datetime.datetime.now)
def _get_hours(self):
return "%s - %s" % (self.open_time.strftime('%I:%M%p'), self.close_time.strftime('%I:%M %p'))
hours = property(_get_hours)
As you can see, each 'hour' is related to a restaurant and a meal period, which allows us to display the information in a manner similar to that you might find on a store's front sign. For example, if you go to the Grill 23 & Bar page (my personal favorite restaurant in Boston, although Craigie on Main is a decent challenger), you'll see something like this:
DINNER
* Sun: 5:30 p.m.-10 p.m.
* Mon-Thur: 5:30 p.m.-10:30 p.m.
* Fri: 5:30 p.m.-11 p.m.
* Sat: 5 p.m.-11 p.m.
Building a list like that out of the above model proved slightly more difficult that I might have hoped. It required quite a lot of template logic, including writing a custom filter. The block of template code necessary to generate that list looks like this:
<div class="hours">
{% regroup restaurant.hoursofoperation_set.all by meal_period as periods %}
{% for period in periods %}
<div class="hoursMealPeriod">{{ period.grouper }}</div>
{% regroup period.list by hours as hour_list %}
<ul>
{% for hour in hour_list %}
<li>{{ hour.list|collapsedays }}</li>
{% endfor %}
</ul>
{% endfor %}
</div>
As you can see, somewhat complex. Those nested {% regroup %}s can be nasty to wrap your head around, if nothing else. But basically it's taking the set of HoursOfOperation objects related to the restaurant, grouping them by meal period, then taking the subset of those objects for each meal period, and grouping those by the hours of the day they represent. So what you're then left with is a list of all the different time periods (still represented as HoursOfOperation objects) that the restaurant is open for a given meal period, and the days on which it is open during those hours. As you can see above, the days are represented by number of the day of the week (0 for Sunday through 6 for Saturday).
Converting that list integers into something like 'Mon, Wed-Fri' was not very easy, and certainly not something I wanted to try to tackle using Django's template tags. I ended up drawing heavily on my hazy memories of CS 127 (many thanks to Dave who taught me all about recursion way back then) and creating a filter that considers the list of HoursOfOperation objects as a list of those integers, then recursively converts it into a list of lists representing the subsets of contiguous days in the list. So if you start out with [1, 3, 4, 5] you end up with [[1, 1], [3, 5]] which is then converted into 'Mon, Wed-Fri'. After several false starts I ended up with this beauty of a Django template filter:
from django.template import Library
from django.template.defaultfilters import time
from types import ListType
register = Library()
def simplify(index, found, days):
high = index+1
mid = index
low = index-1
if not found:
days[low] = [days[low], days[low]]
if high >= len(days):
if not isinstance(days[-1], ListType):
if days[-1] == days[-2][1]:
days.pop(-1)
else:
days[-1] = [days[-1], days[-1]]
return days
if int(days[high].day) - int(days[mid].day) == 1 and (found or int(days[mid].day) - int(days[low][0].day) == 1):
days[low][1] = days[high]
days.pop(mid)
high = high-1
found = True
else:
if found:
days.pop(mid)
found = False
return simplify(high, found, days)
@register.filter
def collapsedays(value):
hours = "%s-%s" % (time(value[0].open_time), time(value[0].close_time))
days = simplify(1, False, value)
for i in range(len(days)):
if days[i][0] == days[i][1]:
days[i] = days[i][0].get_day_display()
else:
days[i] = "%s-%s" % (days[i][0].get_day_display(),
days[i][1].get_day_display())
return "%s: %s" % (', '.join(days), hours)
Should have done this a long time ago
Dec 12th, 2008
Really, this was a pretty major oversight on my part, but I just now finished added a 'status' field to each entry in my blog app. It's a pretty simple thing in and of itself, I just added a tiny bit of code to my Entry model:
STATUS_CHOICES = (
('dr', 'draft'),
('ac', 'active'),
('ar', 'archive'),
)
status = models.CharField(choices=STATUS_CHOICES, max_length=2, default='dr')
And then a custom manager as well:
class EntryManager(models.Manager):
def active(self):
return self.get_query_set().filter(status='ac')
Remembering, of course, to make sure that manager was actually accessible from the model (in this case by adding 'objects = EntryManager()' to my model class).
Once I did that I was able to change the Entry.objects.all() in my view(s) to Entry.objects.active() and now I'm able to write a post and save it without actually publishing! As I said, something of a bonehead move that I didn't do this in the first place.
Since I was messing around in my code anyway I decided to clean up the admin for my Entry model as well. Since I'm the only one who ever uses the admin for my blog, I hadn't bothered to before, but now I've got a little more info available to me when looking at my entries:
class EntryAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('title',)}
save_on_top = True
list_display = ['title', 'post_date', 'site_list', 'status',]
list_filter = ('sites', 'status',)
So my admin now looks something like this:
A cool thing there is the 'site_list' column. By default, you can't use a ManyToManyField in the list_display for your model. But I've dealt with this before, and it's only 5 lines of code to make this work:
def site_list(self):
if self.sites:
sites = [site.name for site in self.sites.all()]
string = ", ".join(sites)
return string
Good stuff!
Custom fields and widgets for Django forms
Nov 19th, 2008
Every so often you run into a situation where Django's built-in form fields and widgets just don't meet your needs. I ran into this the other day when creating a credit card processing form. I wanted an easy way for the user to enter the expiration month and date of their card, but the tools provided by django only gave me a single text field and the option to use a javascript date picker. Neither of those was quite what I wanted, I just wanted two selects that would allow the user to pick the month and the year, as on pretty much every credit card form you've ever filled out online.
The obvious option would be to make two separate fields on your model, one for month, and one for year. But I don't really like that option. I would much rather have both selects show up on the same line, which is not the behavior you'd get with two separate fields. So I decided to write a custom widget and field to accomplish this. It was actually surprisingly easy to do. Probably the most difficult part was coming up with a decent way to populate the selects with worthwhile options. I'm not entirely pleased with the route I took there, but it's easy enough to change later. Here's my code:
from django import forms
import datetime
class MonthYearWidget(forms.MultiWidget):
"""
A widget that splits a date into Month/Year with selects.
"""
def __init__(self, attrs=None):
months = (
('01', 'Jan (01)'),
('02', 'Feb (02)'),
('03', 'Mar (03)'),
('04', 'Apr (04)'),
('05', 'May (05)'),
('06', 'Jun (06)'),
('07', 'Jul (07)'),
('08', 'Aug (08)'),
('09', 'Sep (09)'),
('10', 'Oct (10)'),
('11', 'Nov (11)'),
('12', 'Dec (12)'),
)
year = int(datetime.date.today().year)
year_digits = range(year, year+10)
years = [(year, year) for year in year_digits]
widgets = (forms.Select(attrs=attrs, choices=months), forms.Select(attrs=attrs, choices=years))
super(MonthYearWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.month, value.year]
return [None, None]
def render(self, name, value, attrs=None):
try:
value = datetime.date(month=int(value[0]), year=int(value[1]), day=1)
except:
value = ''
return super(MonthYearWidget, self).render(name, value, attrs)
class MonthYearField(forms.MultiValueField):
def compress(self, data_list):
if data_list:
return datetime.date(year=int(data_list[1]), month=int(data_list[0]), day=1)
return datetime.date.today()
Simple, no? The results end up looking like so:
Dynamic upload path for Django FileField/ImageField
Nov 18th, 2008
Django provides an extraordinarily easy way of integrating file uploads into your site in the FileField (also the ImageField which provides some nifty image-specific functionality). However as soon as you start dealing with file uploads you have to worry about where those files are going to be stored. To take care of this, the 'upload_to' argument is set when declaring your FileField which is a local filesystem path that will be appended to your MEDIA_ROOT to the upload location (you could also use an absolute path). By default this is just a static string, though it does handle strftime arguments to allow some flexibility and will look something like this:
def model(models.Model):
file = models.FileField(upload_to='my/file/uploads/')
...
However that's not always going to cut it. Sometimes you're going to want more flexibility that even just separating the files out by date information. In that case you can take advantage of the fact that a callable can be passed as an argument. In one of my projects I've defined a function that returns the appropriate path depending on a couple of different variables. In this particular case, I know that all uploads for a certain model will be PDFs, while all uploads for certain other models will be images, so I'm able to pretty easily generate appropriate paths for those uploads. We also wanted to obfuscate the filenames so that someone couldn't easily guess the path for other files, and didn't really like the way that Django simply keeps appending '_'s to filenames in the event of a filename collision until it ends up with a unique name. To handle that I simply generate a random string to use as the filename, which fits our needs quite nicely. In my specific case, the function lives in myproject.lib.files and looks like this:
import random, string
from django.contrib.contenttypes.models import ContentType
def get_path(instance, filename):
ctype = ContentType.objects.get_for_model(instance)
model = ctype.model
app = ctype.app_label
extension = filename.split('.')[-1]
dir = "site"
if model == "job":
dir += "/pdf/job_attachment"
else:
dir += "/img/%s" % app
if model == "image_type_1":
dir += "/type1/%s" % instance.category
elif model == "image_type_2":
dir += "/type2"
elif model == "restaurant":
dir += "/logo"
else:
dir += "/%s" % model
chars = string.letters + string.digits
name = string.join(random.sample(chars, 8), '')
return "%s/%s.%s" % (dir, name, extension)
As you can see, it's pretty simple to generate an upload path based on pretty much any characteristics you want. To then tell Django to use this function to get the upload path rather than a static string you modify your model definition like so:
from myproject.lib.files import get_path
def model(models.Model):
file = models.FileField(upload_to=get_path)
...
And that's all it takes. Now all my uploads are automatically renamed and uploaded into the particular directory structure that we want. And since all the logic involved is in a single function it's extremely easy to change it at any point if we decide to alter our directory structure.
Modifying the Django Admin: redirects after adding, changing, and deleting
Oct 27th, 2008
One of the Django projects that I've been working on for about a year and and will (fingers crossed) be going live in the very very very near future has involved a lot of modification to Django's admin interface. I plan on writing more about the many, many specific changes that I've made to the interface (without modifying the actual Django codebase, so that the changes can be easily applied by anyone without breaking updates), but to talk about them all at once would make far too long of a post. So I'll be taking them on one at a time.
The change I want to talk about right now involves redirecting the user to the page I want them to be at after they've finished adding or editing an object rather than to that object's model's change_list. Normally, if you're editing an Entry object in the Blog app, when you hit save it will take you to /admin/blog/entry/, which is a list of all the Entry objects in the database. However there are some instances in which this isn't the behavior I want. Once such instance involves model inheritance. Say, for example, you have multiple types of Entries which you've accomodated through multi-table-inheritance. Because the different sub-classes are different models, they all have their own change_lists in the Django admin. But I want to be able to view, edit, and create Entries of all types from one page.
Fortunately, Django makes this fairly easy to accomplish. All that is necessary is to override the appropriate methods in the Entry ModelAdmin. That will end up looking something like this:
class EntryAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
result = super(EntryAdminAdmin, self).change_view(request, object_id, extra_context)
if not request.POST.has_key('_addanother') and not request.POST.has_key('_continue'):
result['Location'] = iri_to_uri("/admin/blog/entry/")
return result
The exact same modification should be made to add_view as well, and a nearly identical modification to delete_view though it doesn't need to deal with the _addanother and _continue cases. You can then use the EntryAdmin class for all of your varioud Entry sub-classes, or, if you need some other changes to the admin for different Entries sub-classes you can sub-class EntryAdmin for them. Now, whenever you hit the save button after editing any sort of Entry, it will always take you back to /admin/blog/entry/ rather than /admin/blog/linkentry/ or whatever your other subclasses are. If you want it to only take you back to /admin/blog/entry/ if you're coming from a particular page and otherwise take you to /admin/blog/linkentry/ all you need is to add a GET variable to your url (something like '/admin/blog/linkentry/add/?return_to_main=True') and then check for it in your modified change_view, add_view, and delete_view methods with a request.GET.get('return_to_main', False). I've even used this between objects of different model types to create a 'dashboard' page that allows you to view, and alter the relationships between an object of one type with objects of another type. All that's necessary in a case like that is to pass the id of the object in your GET variable and take that into account when creating your uri. An added benefit of that it makes it easy to auto-fill the ForeignKey field when creating a related object. In such a case you'll also need to keep that GET variable in the URLs down the line in order to maintain compatilibility with the 'Save and add another' and 'Save and continue editing' features. But that's still a simple modification:
class EntryAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
result = super(EntryAdminAdmin, self).change_view(request, object_id, extra_context)
other_id = request.GET.get("other_id", None)
if not request.POST.has_key('_addanother') and not request.POST.has_key('_continue'):
if other_id:
result['Location'] = iri_to_uri("/admin/dashboard/%s/") % other_id
return result
elif request.POST.has_key('_continue'):
if other_id:
result['Location'] = iri_to_uri("?other_id=%s" % other_id)
return result
elif request.POST.has_key('_addanother'):
if other_id:
result['Location'] = iri_to_uri("%s?other_id=%s" % (result['Location'], other_id))
return result
return result
But more on that sort of thing in other posts.
Duplicating WebFaction's Apache setup
Oct 13th, 2008
I've been using WebFaction for my hosting for a while now, and have been extremely pleased with them. In addition to the fantastic service I've received, I've been very impressed with the intelligent way they have their servers setup. They've clearly done a lot of work to make things as modular as possible which makes it insanely easy for me to run multiple sites with very different requirements seamlessly on the same server.
Basically what they've done is segment out all of your different websites into 'applications'. Each application in represented as a directory in your ~/webapps/ directory, and is essentially a self-contained environment with it's own apache instance, and, in the case of a Django app, it's own $PYTHONPATH. The end result is that even though all the websites are being stored and run from within my home directory, they're entirely modular, can have different, or different versions of the same, dependencies installed, and can be shut down and restarted independently of one another. On top of all this is a fantastically simple custom web-based control panel that I'm pretty sure is built with Django.
I've been so impressed with how well this setup works, that I've decided to duplicate it on my home server for development purposes. Currently I do pretty much all my development work on my Gentoo Linux powered ThinkPad. To that end I've installed Apache, MySQL, PostgreSQL, SQLite, Python, PHP, &c.; to allow me to mimic the live sites as closely as possible and to allow me to continue working when I don't have internet access (such as when I'm flying or visiting Jessi's family out beyond the reach of broadband). This works very well, but as I'm just using a basic Apache install, without any VirtualHosts, it's not nearly as flexible and means I can really only work on a single site at a time with some work necessary to switch back and forth between projects. Of course most of the time I just use Django's built-in development server when working with Django, but I do end up relying on Apache sometimes, and I'd like to set up my home server as a more complete development environment for both myself and some friends I can grant VPN access to. So to that end I've been looking into WebFaction's setup with the idea of re-implementing it myself.
Turns out it's pretty simple. Simple enough that I almost feel like I should have thought of it myself. Basically, WebFaction's setup scripts create a new 'app' in your ~/webapps/ directory, and populate it, most importantly with a copy, owned by your user, of the Apache executable, some scripts to start, stop, and restart that executable, and an httpd.conf file that sets the (in the case of a Python-based app) $PYTHONPATH variable to include a ~/webapps/yourappname/lib/python2.5/ directory allowing each site to maintain it's own dependencies independently (you can also put things in your ~/lib/python2.5/ for global dependencies if you want). Oh, each application also gets it's own copies of the necessary Apache modules to the same effect. Each application's Apache instance(s) is set up to listen to a different (non-80) port. The end result of this is an extremely simple, extremely modular setup that works fantastically.
Obviously I've left out a step here. If each Apache isntance is listening to a different, non-80, port, how does your traffic get to your actual site? This is the one part that I can't really just peek into the configuration files for, because it doesn't (as far as I can tell, which makes sense) live on the same server as my sites. I assume that what's happening is that WebFaction's name servers are simply pointing requests to (for example) joshourisman.com:80 at my.webfaction.server:portnumber. Again, a simple, yet elegant solution that allows for easy customization and expansion.
I haven't yet tried to implement this setup myself (I first want to move my server from FreeBSD to Linux (which now that I'm using full-time again I'm just much more familiar with), but there's nothing about it that's particularly tricky. Really, the routing is probably going to be the hardest part, but I'm planning on replacing our rather lackluster TrendNet wireless router with a Linux box which will give me much greater control and (hopefully) better reliability.
Replacing files in a Django ImageField or FieldField
Sep 24th, 2008
Django provides a lot of really useful tools to simplify the development process and let you focus only on the important bits. The FileField and ImageField (a subclass of FileField) are good examples of that letting you simply tell Django that your model will have a file or image and letting it take care of the issues of uploading, storing, and all that. In the past, that's really been enough. It will even automatically delete the file/image when you delete its parent model object. One thing it doesn't do, however (and this has been the topic of much debate), is delete a previously uploaded file/image when you upload a new one for the same model. What I mean by this is if you have a model with a FileField defined and use it to upload some file associated with an object. If you then later decide that you want a different file associated with that object and upload it, it doesn't delete the original file and instead leaves it in place and only changes the path stored in the database to point to the new file. Depending on the nature and traffic your website gets, this can lead to massive amounts of storage being wasted on orphaned files (assuming you don't want to keep those old files, of course). I toyed with a couple different approaches to this, including the possibility of subclassing the FileField to try and add that functionalty directly to the field. While this would probably work, I instead opted for a less generalized method: overriding the save() method of the model to take care of this:
def save(self, force_insert=False, force_update=False):
try:
old_obj = ModelName.objects.get(pk=self.pk)
if old_obj.image.path != self.image.path:
path = old_obj.image.path
default_storage.delete(path)
except:
pass
super(ModelName, self).save(force_insert, force_update)
This work perfectly, though it has the disadvantage of being specific to a particular model, which violates the DRY principle (assuming you're going to use it on more than one model). Fortunately there's a simple way to solve this problem too: subclassing models.Model and then instead of having all your models subclass models.Model directly, have them subclass your own version of it instead. In that case you'll probably want to have it work generically on all ImageFields and/or FileFields rather than having to name them all specifically. This isn't too hard, and you can build up a list of all the ImageFields for a particular model like this (taken from the sorl-thumnail project):
for field in model._meta.fields:
if isinstance(field, models.ImageField):
if field.upload_to.find("%") == -1:
paths = paths.union((field.upload_to,))
[Edit: There was a bug in my code that I've corrected. Details are below in the comment by Comete.]
[Edit2: Fixed another problem in the code with variable names. Thanks, again Comete!]
My first Django patch
Sep 16th, 2008
I just submitted my first patch to Django! Among other things, this is my first real forray into the inner depths of the Django code. This patch fixes an issues that had been bothering me for quite some time. In Django's admin interface it's possible to specify that a particular field should be automatically filled in with the value(s) you enter in some other field(s). For example, as I typed 'My first Django patch' into the title field of the form I used to write this post, it was automatically filling in a slug field that's being used for the permalink to this post with 'my-first-django-patch'. This is a very useful features and uses just a little bit of javascript to accomplish it. The only problem is that it only works when you're trying to pull information from a text field. Sometimes, however, you might want to pull information from another sort of field, such as a drop-down menu. Previously Django simply wasn't capable of this. With my changes, however, it is able to handle this potentiality quite well.
It's not really a huge patch, just a fairly a little added code to a single javascript method, and there's no guarantee that my patch will every make it's way into the Django code base, but it's still fun to be able to contribute to one of my favorite open source projects ever. The patch, for those that are curious, can be found here, and I've also submitted it to djangosnippets.org here.
admin alternative energy apple blog books boston bostonchefs code databases diesel discovery discovery creative django downtime dydxtech ev food freebsd gentoo google healthcare hosting iceland insurance iphone iphone2.0 javascript kansas lakota libertarian linux mandarin oriental maryland massachusetts mbta media temple mercurial mingus mobileme move mysql obama photovoltaics php politics postgresql president programming projects python reading restaurant week rmv sean tevis solar somerville technology tesla travel unix web webfaction webhosting website wordpress work zfs