- Author:
- ubernostrum
- Posted:
- February 25, 2008
- Language:
- Python
- Version:
- .96
- Score:
- 2 (after 2 ratings)
A comment on a recent blog entry of mine asked about a setup where one model has foreign keys pointing at it from several others, and how to write a manager which could attach to any of those models and query seamlessly on the relation regardless of what it's named.
This is a simple example of how to do it: in this case, both Movie
and Restaurant
have foreign keys to Review
, albeit under different names. However, they both use ReviewedObjectManager
to provide a method for querying objects whose review assigned a certain rating; this works because an instance of ReviewedObjectManager
"knows" what model it's attached to, and can introspect that model, using Django's model-introspection API, to find out the correct name to use for the relation, and then use that to perform the query.
Using model introspection in this fashion is something of an advanced topic, but is extremely useful for writing flexible, reusable code.
Also, note that the introspection cannot be done in the manager's __init__()
method -- at that point, self.model
is still None
(it won't be filled in with the correct model until a bit later) -- so it's necessary to come up with some way to defer the introspection. In this case, I'm doing it in a method that's called when the relation name is first needed, and which caches the result in an attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | from django.db import models
class Review(models.Model):
LOW_RATING = 1
AVERAGE_RATING = 2
HIGH_RATING = 3
RATING_CHOICES = (
(LOW_RATING, 'Low'),
(AVERAGE_RATING, 'Average'),
(HIGH_RATING, 'High'),
)
title = models.CharField(max_length=250)
pub_date = models.DateTimeField()
body = models.TextField()
rating = models.IntegerField(choices=RATING_CHOICES)
class ReviewedObjectManager(models.Manager):
def _get_review_field(self):
if not hasattr(self, '_review_field'):
for f in self.model._meta.fields:
if f.rel and f.rel.to == Review:
setattr(self, '_review_field', f.name)
break
return self._review_field
def rating_equals(self, rating):
review_field = self._get_review_field()
return self.filter(**{ '%s__rating' % review_field: rating })
class Movie(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
running_time = models.PositiveIntegerField()
movie_review = models.ForeignKey(Review)
objects = ReviewedObjectManager()
class Restaurant(models.Model):
name = models.CharField(max_length=255)
address = models.CharField(max_length=255)
description = models.TextField()
restaurant_review = models.ForeignKey(Review)
objects = ReviewedObjectManager()
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Another place to grab
_review_field
would be incontribute_to_class
-- that's the method called when the model metaclass adds the manager to the class. Just be sure to call the superclass method!In fact, I'd say that
contribute_to_class
would be the correct place to do this initialization sincecontribute_to_class
is the "official" mechanism by which "things attached to models" get to poke at the model.#
Please login first before commenting.