git-svn-id: http://code.djangoproject.com/svn/django/trunk@6900 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
25f548a852
commit
5ed205bc8a
|
@ -16,18 +16,18 @@ class GenericForeignKey(object):
|
||||||
Provides a generic relation to any object through content-type/object-id
|
Provides a generic relation to any object through content-type/object-id
|
||||||
fields.
|
fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ct_field="content_type", fk_field="object_id"):
|
def __init__(self, ct_field="content_type", fk_field="object_id"):
|
||||||
self.ct_field = ct_field
|
self.ct_field = ct_field
|
||||||
self.fk_field = fk_field
|
self.fk_field = fk_field
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
# Make sure the fields exist (these raise FieldDoesNotExist,
|
# Make sure the fields exist (these raise FieldDoesNotExist,
|
||||||
# which is a fine error to raise here)
|
# which is a fine error to raise here)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.model = cls
|
self.model = cls
|
||||||
self.cache_attr = "_%s_cache" % name
|
self.cache_attr = "_%s_cache" % name
|
||||||
|
|
||||||
# For some reason I don't totally understand, using weakrefs here doesn't work.
|
# For some reason I don't totally understand, using weakrefs here doesn't work.
|
||||||
dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
|
dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
|
||||||
|
|
||||||
|
@ -35,18 +35,18 @@ class GenericForeignKey(object):
|
||||||
setattr(cls, name, self)
|
setattr(cls, name, self)
|
||||||
|
|
||||||
def instance_pre_init(self, signal, sender, args, kwargs):
|
def instance_pre_init(self, signal, sender, args, kwargs):
|
||||||
# Handle initalizing an object with the generic FK instaed of
|
# Handle initalizing an object with the generic FK instaed of
|
||||||
# content-type/object-id fields.
|
# content-type/object-id fields.
|
||||||
if self.name in kwargs:
|
if self.name in kwargs:
|
||||||
value = kwargs.pop(self.name)
|
value = kwargs.pop(self.name)
|
||||||
kwargs[self.ct_field] = self.get_content_type(value)
|
kwargs[self.ct_field] = self.get_content_type(value)
|
||||||
kwargs[self.fk_field] = value._get_pk_val()
|
kwargs[self.fk_field] = value._get_pk_val()
|
||||||
|
|
||||||
def get_content_type(self, obj):
|
def get_content_type(self, obj):
|
||||||
# Convenience function using get_model avoids a circular import when using this model
|
# Convenience function using get_model avoids a circular import when using this model
|
||||||
ContentType = get_model("contenttypes", "contenttype")
|
ContentType = get_model("contenttypes", "contenttype")
|
||||||
return ContentType.objects.get_for_model(obj)
|
return ContentType.objects.get_for_model(obj)
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
raise AttributeError, u"%s must be accessed via instance" % self.name
|
raise AttributeError, u"%s must be accessed via instance" % self.name
|
||||||
|
@ -77,21 +77,21 @@ class GenericForeignKey(object):
|
||||||
setattr(instance, self.ct_field, ct)
|
setattr(instance, self.ct_field, ct)
|
||||||
setattr(instance, self.fk_field, fk)
|
setattr(instance, self.fk_field, fk)
|
||||||
setattr(instance, self.cache_attr, value)
|
setattr(instance, self.cache_attr, value)
|
||||||
|
|
||||||
class GenericRelation(RelatedField, Field):
|
class GenericRelation(RelatedField, Field):
|
||||||
"""Provides an accessor to generic related objects (i.e. comments)"""
|
"""Provides an accessor to generic related objects (i.e. comments)"""
|
||||||
|
|
||||||
def __init__(self, to, **kwargs):
|
def __init__(self, to, **kwargs):
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
kwargs['rel'] = GenericRel(to,
|
kwargs['rel'] = GenericRel(to,
|
||||||
related_name=kwargs.pop('related_name', None),
|
related_name=kwargs.pop('related_name', None),
|
||||||
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
limit_choices_to=kwargs.pop('limit_choices_to', None),
|
||||||
symmetrical=kwargs.pop('symmetrical', True))
|
symmetrical=kwargs.pop('symmetrical', True))
|
||||||
|
|
||||||
# Override content-type/object-id field names on the related class
|
# Override content-type/object-id field names on the related class
|
||||||
self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
|
self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
|
||||||
self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
|
self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
|
||||||
|
|
||||||
kwargs['blank'] = True
|
kwargs['blank'] = True
|
||||||
kwargs['editable'] = False
|
kwargs['editable'] = False
|
||||||
kwargs['serialize'] = False
|
kwargs['serialize'] = False
|
||||||
|
@ -116,9 +116,9 @@ class GenericRelation(RelatedField, Field):
|
||||||
|
|
||||||
def m2m_column_name(self):
|
def m2m_column_name(self):
|
||||||
return self.object_id_field_name
|
return self.object_id_field_name
|
||||||
|
|
||||||
def m2m_reverse_name(self):
|
def m2m_reverse_name(self):
|
||||||
return self.object_id_field_name
|
return self.model._meta.pk.column
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
def contribute_to_class(self, cls, name):
|
||||||
super(GenericRelation, self).contribute_to_class(cls, name)
|
super(GenericRelation, self).contribute_to_class(cls, name)
|
||||||
|
@ -131,13 +131,13 @@ class GenericRelation(RelatedField, Field):
|
||||||
|
|
||||||
def contribute_to_related_class(self, cls, related):
|
def contribute_to_related_class(self, cls, related):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def set_attributes_from_rel(self):
|
def set_attributes_from_rel(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "ManyToManyField"
|
return "ManyToManyField"
|
||||||
|
|
||||||
class ReverseGenericRelatedObjectsDescriptor(object):
|
class ReverseGenericRelatedObjectsDescriptor(object):
|
||||||
"""
|
"""
|
||||||
This class provides the functionality that makes the related-object
|
This class provides the functionality that makes the related-object
|
||||||
|
@ -193,12 +193,12 @@ def create_generic_related_manager(superclass):
|
||||||
Factory function for a manager that subclasses 'superclass' (which is a
|
Factory function for a manager that subclasses 'superclass' (which is a
|
||||||
Manager) and adds behavior for generic related objects.
|
Manager) and adds behavior for generic related objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class GenericRelatedObjectManager(superclass):
|
class GenericRelatedObjectManager(superclass):
|
||||||
def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
|
def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
|
||||||
join_table=None, source_col_name=None, target_col_name=None, content_type=None,
|
join_table=None, source_col_name=None, target_col_name=None, content_type=None,
|
||||||
content_type_field_name=None, object_id_field_name=None):
|
content_type_field_name=None, object_id_field_name=None):
|
||||||
|
|
||||||
super(GenericRelatedObjectManager, self).__init__()
|
super(GenericRelatedObjectManager, self).__init__()
|
||||||
self.core_filters = core_filters or {}
|
self.core_filters = core_filters or {}
|
||||||
self.model = model
|
self.model = model
|
||||||
|
@ -212,10 +212,10 @@ def create_generic_related_manager(superclass):
|
||||||
self.content_type_field_name = content_type_field_name
|
self.content_type_field_name = content_type_field_name
|
||||||
self.object_id_field_name = object_id_field_name
|
self.object_id_field_name = object_id_field_name
|
||||||
self.pk_val = self.instance._get_pk_val()
|
self.pk_val = self.instance._get_pk_val()
|
||||||
|
|
||||||
def get_query_set(self):
|
def get_query_set(self):
|
||||||
query = {
|
query = {
|
||||||
'%s__pk' % self.content_type_field_name : self.content_type.id,
|
'%s__pk' % self.content_type_field_name : self.content_type.id,
|
||||||
'%s__exact' % self.object_id_field_name : self.pk_val,
|
'%s__exact' % self.object_id_field_name : self.pk_val,
|
||||||
}
|
}
|
||||||
return superclass.get_query_set(self).filter(**query)
|
return superclass.get_query_set(self).filter(**query)
|
||||||
|
|
|
@ -18,42 +18,42 @@ class TaggedItem(models.Model):
|
||||||
tag = models.SlugField()
|
tag = models.SlugField()
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = generic.GenericForeignKey()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["tag"]
|
ordering = ["tag"]
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.tag
|
return self.tag
|
||||||
|
|
||||||
class Animal(models.Model):
|
class Animal(models.Model):
|
||||||
common_name = models.CharField(max_length=150)
|
common_name = models.CharField(max_length=150)
|
||||||
latin_name = models.CharField(max_length=150)
|
latin_name = models.CharField(max_length=150)
|
||||||
|
|
||||||
tags = generic.GenericRelation(TaggedItem)
|
tags = generic.GenericRelation(TaggedItem)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.common_name
|
return self.common_name
|
||||||
|
|
||||||
class Vegetable(models.Model):
|
class Vegetable(models.Model):
|
||||||
name = models.CharField(max_length=150)
|
name = models.CharField(max_length=150)
|
||||||
is_yucky = models.BooleanField(default=True)
|
is_yucky = models.BooleanField(default=True)
|
||||||
|
|
||||||
tags = generic.GenericRelation(TaggedItem)
|
tags = generic.GenericRelation(TaggedItem)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
class Mineral(models.Model):
|
class Mineral(models.Model):
|
||||||
name = models.CharField(max_length=150)
|
name = models.CharField(max_length=150)
|
||||||
hardness = models.PositiveSmallIntegerField()
|
hardness = models.PositiveSmallIntegerField()
|
||||||
|
|
||||||
# note the lack of an explicit GenericRelation here...
|
# note the lack of an explicit GenericRelation here...
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
__test__ = {'API_TESTS':"""
|
__test__ = {'API_TESTS':"""
|
||||||
# Create the world in 7 lines of code...
|
# Create the world in 7 lines of code...
|
||||||
>>> lion = Animal(common_name="Lion", latin_name="Panthera leo")
|
>>> lion = Animal(common_name="Lion", latin_name="Panthera leo")
|
||||||
|
@ -117,13 +117,13 @@ __test__ = {'API_TESTS':"""
|
||||||
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||||
[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
|
[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
|
||||||
|
|
||||||
# If Generic Relation is not explicitly defined, any related objects
|
# If Generic Relation is not explicitly defined, any related objects
|
||||||
# remain after deletion of the source object.
|
# remain after deletion of the source object.
|
||||||
>>> quartz.delete()
|
>>> quartz.delete()
|
||||||
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||||
[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
|
[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
|
||||||
|
|
||||||
# If you delete a tag, the objects using the tag are unaffected
|
# If you delete a tag, the objects using the tag are unaffected
|
||||||
# (other than losing a tag)
|
# (other than losing a tag)
|
||||||
>>> tag = TaggedItem.objects.get(id=1)
|
>>> tag = TaggedItem.objects.get(id=1)
|
||||||
>>> tag.delete()
|
>>> tag.delete()
|
||||||
|
@ -132,4 +132,8 @@ __test__ = {'API_TESTS':"""
|
||||||
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||||
[(u'clearish', <ContentType: mineral>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
|
[(u'clearish', <ContentType: mineral>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
|
||||||
|
|
||||||
|
>>> ctype = ContentType.objects.get_for_model(lion)
|
||||||
|
>>> Animal.objects.filter(tags__content_type=ctype)
|
||||||
|
[<Animal: Platypus>]
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
|
Loading…
Reference in New Issue