Dynamic choices on a ForeignKey field
Django | 2008-07-23 |
One proviso before I get started - this example works with the old (as of a few days ago) version of admin, prior to the newforms-admin merge, where the models still use inner admin classes. I haven't started changing my current project to work with the merge, but as I do, I'll update with new examples.
Most field types take choices
as an argument (actually, the Django documentation on choices says 'most', but the model page that describes field options says 'all').
A choices
list should be a tuple of tuples (a 2-tuple?) - think of it as a list of key/value pairs. All of the official Django documentation shows choices
examples as lists of static values **:
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
)
With the list name passed as an argument like so:
gender = models.ForeignKey(MyModel, choices=GENDER_CHOICES)
It is possible to pass in a dynamic list, though, by using a method to generate the choices
tuple.
When you create your model, start with a custom model manager at the top:
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.sites.models import Site
from django.contrib.auth.models import User
class CategoryManager(models.Manager):
def category_choices(self):
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
SELECT c.id, c.title
FROM category_users c
WHERE c.active = '1'
ORDER BY c.title ASC""")
choice_list = cursor.fetchall()
return choice_list
Note that the above manager class contains a method that will populate a choices tuple. There are probably shorter, faster, easier ways to do that, but this does work - I'm using plain SQL to return a set of rows with just two columns - an id and a value. In other words, a list of key/value pairs.
In the model that your choices
values will come from, create relationship with the Manager model:
class Category(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=40)
active = models.BooleanField()
created_at = models.DateField(auto_now_add=True)
updated_at = models.DateField(null=True, blank=True)
inactivated_at = models.DateField(null=True, blank=True)
objects = WhitepapersManager()
class Admin:
list_display = ('title',)
(Remember that the 'id' requested as 'c.id' in the query above is also a part of this model - it's created automatically as the primary key.)
Then in the model where you need the choices
list, get the tuple by calling up to the model method that returns it:
class CategoryUsers(models.Model):
CATEGORY_CHOICES = Category.objects.category_choices()
category = models.ForeignKey(Category, choices=CATEGORY_CHOICES)
class Admin:
list_display = ('category',)
** In most of the posts and comments I've seen, it's recommended that you only use choices
for static k/v pairs. So even though dynamic choices
are possible, they're not recommended. And after testing out this method on my development environment today, I see why - every time I added new rows to the main Category
table, I had to restart before the values would show up in my choices
list.