Login

Manager introspecting attached model

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

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

jacobian (on February 25, 2008):

Another place to grab _review_field would be in contribute_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 since contribute_to_class is the "official" mechanism by which "things attached to models" get to poke at the model.

#

Please login first before commenting.