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 # Handle initalizing an object with the generic FK instaed of 39 # content-type/object-id fields. 40 if self.name in kwargs: 41 value = kwargs.pop(self.name) 42 kwargs[self.ct_field] = self.get_content_type(value) 43 kwargs[self.fk_field] = value._get_pk_val()
44
45 - def get_content_type(self, obj):
46 # Convenience function using get_model avoids a circular import when using this model 47 ContentType = get_model("contenttypes", "contenttype") 48 return ContentType.objects.get_for_model(obj)
49
50 - def __get__(self, instance, instance_type=None):
51 if instance is None: 52 raise AttributeError, u"%s must be accessed via instance" % self.name 53 54 try: 55 return getattr(instance, self.cache_attr) 56 except AttributeError: 57 rel_obj = None 58 ct = getattr(instance, self.ct_field) 59 if ct: 60 try: 61 rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field)) 62 except ObjectDoesNotExist: 63 pass 64 setattr(instance, self.cache_attr, rel_obj) 65 return rel_obj
66
67 - def __set__(self, instance, value):
68 if instance is None: 69 raise AttributeError, u"%s must be accessed via instance" % self.related.opts.object_name 70 71 ct = None 72 fk = None 73 if value is not None: 74 ct = self.get_content_type(value) 75 fk = value._get_pk_val() 76 77 setattr(instance, self.ct_field, ct) 78 setattr(instance, self.fk_field, fk) 79 setattr(instance, self.cache_attr, value)
80
81 -class GenericRelation(RelatedField, Field):
82 """Provides an accessor to generic related objects (i.e. comments)""" 83
84 - def __init__(self, to, **kwargs):
85 kwargs['verbose_name'] = kwargs.get('verbose_name', None) 86 kwargs['rel'] = GenericRel(to, 87 related_name=kwargs.pop('related_name', None), 88 limit_choices_to=kwargs.pop('limit_choices_to', None), 89 symmetrical=kwargs.pop('symmetrical', True)) 90 91 # Override content-type/object-id field names on the related class 92 self.object_id_field_name = kwargs.pop("object_id_field", "object_id") 93 self.content_type_field_name = kwargs.pop("content_type_field", "content_type") 94 95 kwargs['blank'] = True 96 kwargs['editable'] = False 97 kwargs['serialize'] = False 98 Field.__init__(self, **kwargs)
99
101 choices = self.get_choices_default() 102 return [curry(oldforms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
103
104 - def get_choices_default(self):
105 return Field.get_choices(self, include_blank=False)
106
107 - def flatten_data(self, follow, obj = None):
108 new_data = {} 109 if obj: 110 instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] 111 new_data[self.name] = instance_ids 112 return new_data
113
114 - def m2m_db_table(self):
115 return self.rel.to._meta.db_table
116
117 - def m2m_column_name(self):
118 return self.object_id_field_name
119
120 - def m2m_reverse_name(self):
121 return self.model._meta.pk.column
122
123 - def contribute_to_class(self, cls, name):
124 super(GenericRelation, self).contribute_to_class(cls, name) 125 126 # Save a reference to which model this class is on for future use 127 self.model = cls 128 129 # Add the descriptor for the m2m relation 130 setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self))
131 134
135 - def set_attributes_from_rel(self):
136 pass
137
138 - def get_internal_type(self):
139 return "ManyToManyField"
140
141 -class ReverseGenericRelatedObjectsDescriptor(object):
142 """ 143 This class provides the functionality that makes the related-object 144 managers available as attributes on a model class, for fields that have 145 multiple "remote" values and have a GenericRelation defined in their model 146 (rather than having another model pointed *at* them). In the example 147 "article.publications", the publications attribute is a 148 ReverseGenericRelatedObjectsDescriptor instance. 149 """
150 - def __init__(self, field):
151 self.field = field
152
153 - def __get__(self, instance, instance_type=None):
154 if instance is None: 155 raise AttributeError, "Manager must be accessed via instance" 156 157 # This import is done here to avoid circular import importing this module 158 from django.contrib.contenttypes.models import ContentType 159 160 # Dynamically create a class that subclasses the related model's 161 # default manager. 162 rel_model = self.field.rel.to 163 superclass = rel_model._default_manager.__class__ 164 RelatedManager = create_generic_related_manager(superclass) 165 166 qn = connection.ops.quote_name 167 168 manager = RelatedManager( 169 model = rel_model, 170 instance = instance, 171 symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model), 172 join_table = qn(self.field.m2m_db_table()), 173 source_col_name = qn(self.field.m2m_column_name()), 174 target_col_name = qn(self.field.m2m_reverse_name()), 175 content_type = ContentType.objects.get_for_model(self.field.model), 176 content_type_field_name = self.field.content_type_field_name, 177 object_id_field_name = self.field.object_id_field_name 178 ) 179 180 return manager
181
182 - def __set__(self, instance, value):
183 if instance is None: 184 raise AttributeError, "Manager must be accessed via instance" 185 186 manager = self.__get__(instance) 187 manager.clear() 188 for obj in value: 189 manager.add(obj)
190 215 216 def get_query_set(self): 217 query = { 218 '%s__pk' % self.content_type_field_name : self.content_type.id, 219 '%s__exact' % self.object_id_field_name : self.pk_val, 220 } 221 return superclass.get_query_set(self).filter(**query) 222 223 def add(self, *objs): 224 for obj in objs: 225 setattr(obj, self.content_type_field_name, self.content_type) 226 setattr(obj, self.object_id_field_name, self.pk_val) 227 obj.save() 228 add.alters_data = True 229 230 def remove(self, *objs): 231 for obj in objs: 232 obj.delete() 233 remove.alters_data = True 234 235 def clear(self): 236 for obj in self.all(): 237 obj.delete() 238 clear.alters_data = True 239 240 def create(self, **kwargs): 241 kwargs[self.content_type_field_name] = self.content_type 242 kwargs[self.object_id_field_name] = self.pk_val 243 obj = self.model(**kwargs) 244 obj.save() 245 return obj 246 create.alters_data = True 247 248 return GenericRelatedObjectManager 249
250 -class GenericRel(ManyToManyRel):
251 - def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True):
252 self.to = to 253 self.num_in_admin = 0 254 self.related_name = related_name 255 self.filter_interface = None 256 self.limit_choices_to = limit_choices_to or {} 257 self.edit_inline = False 258 self.raw_id_admin = False 259 self.symmetrical = symmetrical 260 self.multiple = True 261 assert not (self.raw_id_admin and self.filter_interface), \ 262 "Generic relations may not use both raw_id_admin and filter_interface"
263