Package django :: Package contrib :: Package contenttypes :: Module generic
[hide private]
[frames] | no frames]

Source Code for Module django.contrib.contenttypes.generic

  1  """ 
  2  Classes allowing "generic" relations through ContentType and object-id fields. 
  3  """ 
  4   
  5  from django import oldforms 
  6  from django.core.exceptions import ObjectDoesNotExist 
  7  from django.db import connection 
  8  from django.db.models import signals 
  9  from django.db.models.fields.related import RelatedField, Field, ManyToManyRel 
 10  from django.db.models.loading import get_model 
 11  from django.dispatch import dispatcher 
 12  from django.utils.functional import curry 
 13   
14 -class GenericForeignKey(object):
15 """ 16 Provides a generic relation to any object through content-type/object-id 17 fields. 18 """ 19
20 - def __init__(self, ct_field="content_type", fk_field="object_id"):
21 self.ct_field = ct_field 22 self.fk_field = fk_field
23
24 - def contribute_to_class(self, cls, name):
25 # Make sure the fields exist (these raise FieldDoesNotExist, 26 # which is a fine error to raise here) 27 self.name = name 28 self.model = cls 29 self.cache_attr = "_%s_cache" % name 30 31 # For some reason I don't totally understand, using weakrefs here doesn't work. 32 dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False) 33 34 # Connect myself as the descriptor for this field 35 setattr(cls, name, self)
36
37 - def instance_pre_init(self, signal, sender, args, kwargs):
38 """ 39 Handles initializing an object with the generic FK instaed of 40 content-type/object-id fields. 41 """ 42 if self.name in kwargs: 43 value = kwargs.pop(self.name) 44 kwargs[self.ct_field] = self.get_content_type(obj=value) 45 kwargs[self.fk_field] = value._get_pk_val()
46
47 - def get_content_type(self, obj=None, id=None):
48 # Convenience function using get_model avoids a circular import when 49 # using this model 50 ContentType = get_model("contenttypes", "contenttype") 51 if obj: 52 return ContentType.objects.get_for_model(obj) 53 elif id: 54 return ContentType.objects.get_for_id(id) 55 else: 56 # This should never happen. I love comments like this, don't you? 57 raise Exception("Impossible arguments to GFK.get_content_type!")
58
59 - def __get__(self, instance, instance_type=None):
60 if instance is None: 61 raise AttributeError, u"%s must be accessed via instance" % self.name 62 63 try: 64 return getattr(instance, self.cache_attr) 65 except AttributeError: 66 rel_obj = None 67 68 # Make sure to use ContentType.objects.get_for_id() to ensure that 69 # lookups are cached (see ticket #5570). This takes more code than 70 # the naive ``getattr(instance, self.ct_field)``, but has better 71 # performance when dealing with GFKs in loops and such. 72 f = self.model._meta.get_field(self.ct_field) 73 ct_id = getattr(instance, f.get_attname(), None) 74 if ct_id: 75 ct = self.get_content_type(id=ct_id) 76 try: 77 rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field)) 78 except ObjectDoesNotExist: 79 pass 80 setattr(instance, self.cache_attr, rel_obj) 81 return rel_obj
82
83 - def __set__(self, instance, value):
84 if instance is None: 85 raise AttributeError, u"%s must be accessed via instance" % self.related.opts.object_name 86 87 ct = None 88 fk = None 89 if value is not None: 90 ct = self.get_content_type(obj=value) 91 fk = value._get_pk_val() 92 93 setattr(instance, self.ct_field, ct) 94 setattr(instance, self.fk_field, fk) 95 setattr(instance, self.cache_attr, value)
96
97 -class GenericRelation(RelatedField, Field):
98 """Provides an accessor to generic related objects (i.e. comments)""" 99
100 - def __init__(self, to, **kwargs):
101 kwargs['verbose_name'] = kwargs.get('verbose_name', None) 102 kwargs['rel'] = GenericRel(to, 103 related_name=kwargs.pop('related_name', None), 104 limit_choices_to=kwargs.pop('limit_choices_to', None), 105 symmetrical=kwargs.pop('symmetrical', True)) 106 107 # Override content-type/object-id field names on the related class 108 self.object_id_field_name = kwargs.pop("object_id_field", "object_id") 109 self.content_type_field_name = kwargs.pop("content_type_field", "content_type") 110 111 kwargs['blank'] = True 112 kwargs['editable'] = False 113 kwargs['serialize'] = False 114 Field.