Code


Version 1 (modified by David S. <davidschein@…>, 8 years ago) (diff)

--

See http://www.djangoproject.com/documentation/models/generic_relations/.

Note: As of today, 13-DEC-06, this admonishment applies:

Generic relations: a "generic foreign key" feature is currently under development, 
but not yet completed. There is no support for it yet in the admin application, and 
it is probably unwise to attempt to use it in a production application.

--from http://code.djangoproject.com/

Also, I do not think that this is particularly good data modeling if you have a choice, but when migrating an application and dealing with legacy data as you go, it can be useful if it is not abused.

Model Source Code

class AttributeManager(models.Manager):
    def with_attr(self, attr_name):
        return Attribute.objects.filter(name=attr_name)

    def type_with_attr(self, type_name, attr_name):
        return Attribute.objects.filter(content_type__name=type_name, name=attr_name)

    def type_with_attr_value(self, type_name, attr_name, value):
        return Attribute.objects.filter(content_type__name=type_name, name=attr_name, value=value)

    def obj_attr_list(self, obj):
        ctype = ContentType.objects.get_for_model(obj)
        return  Attribute.objects.filter(content_type__pk=ctype.id, object_id=obj.id)

    def obj_attr_value(self, obj, attr_name):
        ctype = ContentType.objects.get_for_model(obj)
        try:
            av = Attribute.objects.get(name=attr_name, content_type__pk=ctype.id, object_id=obj.id)
        except Attribute.DoesNotExist:
            return None
        return av.value

class Attribute(models.Model):
    name = models.CharField(maxlength=25)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    value = models.CharField(maxlength=255)
   
    content_object = models.GenericForeignKey()
    objects = AttributeManager()

    class meta:
        unique_together = (('name', 'content_type', 'object_id'),)
    
    def __str__(self):
        return "%s.%s.%s=%s" % (self.content_type.name, self.content_object, self.name, self.value,)

Sample API usage

This sample code assumes you added the above models to the sample found at http://www.djangoproject.com/documentation/models/generic_relations/.

>>> diamond = Mineral.objects.create(name="Diamond", hardness=10)
>>> emerald = Mineral.objects.create(name="Emerald", hardness=6)
>>> Attribute.objects.create(content_object=quartz, name='color', value='black')
<Attribute: mineral.Quartz.color=black>

>>> Attribute.objects.create(content_object=emerald, name='color', value='green')
<Attribute: mineral.Emerald.color=green>

>>> Attribute.objects.with_attr('color')
[<Attribute: mineral.Quartz.color=black>, <Attribute: mineral.Emerald.color=green>]

>>> Attribute.objects.type_with_attr('mineral', 'color')
[<Attribute: mineral.Quartz.color=black>, <Attribute: mineral.Emerald.color=green>]

>>> Attribute.objects.create(content_object=eggplant, name='color', value='purple')
<Attribute: vegetable.Eggplant.color=purple>

>>> Attribute.objects.with_attr('color')
[<Attribute: mineral.Quartz.color=black>, <Attribute: mineral.Emerald.color=green>, <Attribute: vegetable.Eggplant.color=
purple>]

>>> Attribute.objects.type_with_attr('mineral', 'color')
[<Attribute: mineral.Quartz.color=black>, <Attribute: mineral.Emerald.color=green>]

>>> Attribute.objects.type_with_attr_value('mineral', 'color', 'green')
[<Attribute: mineral.Emerald.color=green>]

>>> Attribute.objects.type_with_attr_value('mineral', 'color', 'red')
[]

>>> Attribute.objects.obj_attr_list(quartz)
[<Attribute: mineral.Quartz.color=black>]

>>> Attribute.objects.obj_attr_value(quartz, 'color')
'black'