2006-06-17 03:18:30 +08:00
"""
2007-03-24 04:17:04 +08:00
34. Generic relations
2006-06-17 03:18:30 +08:00
Generic relations let an object have a foreign key to any object through a
2008-08-12 22:15:38 +08:00
content - type / object - id field . A ` ` GenericForeignKey ` ` field can point to any
object , be it animal , vegetable , or mineral .
2006-06-17 03:18:30 +08:00
2006-06-20 11:03:43 +08:00
The canonical example is tags ( although this example implementation is * far *
2006-06-17 03:18:30 +08:00
from complete ) .
"""
from django . db import models
from django . contrib . contenttypes . models import ContentType
2007-05-08 18:59:35 +08:00
from django . contrib . contenttypes import generic
2006-06-17 03:18:30 +08:00
class TaggedItem ( models . Model ) :
""" A tag on an item. """
tag = models . SlugField ( )
content_type = models . ForeignKey ( ContentType )
object_id = models . PositiveIntegerField ( )
2007-12-09 15:12:07 +08:00
2007-05-08 18:59:35 +08:00
content_object = generic . GenericForeignKey ( )
2007-12-09 15:12:07 +08:00
2006-06-17 03:18:30 +08:00
class Meta :
ordering = [ " tag " ]
2007-12-09 15:12:07 +08:00
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
def __unicode__ ( self ) :
2006-06-17 03:18:30 +08:00
return self . tag
2008-08-01 23:54:53 +08:00
class Comparison ( models . Model ) :
"""
A model that tests having multiple GenericForeignKeys
"""
comparative = models . CharField ( max_length = 50 )
2008-08-12 22:15:38 +08:00
2008-08-01 23:54:53 +08:00
content_type1 = models . ForeignKey ( ContentType , related_name = " comparative1_set " )
object_id1 = models . PositiveIntegerField ( )
content_type2 = models . ForeignKey ( ContentType , related_name = " comparative2_set " )
object_id2 = models . PositiveIntegerField ( )
first_obj = generic . GenericForeignKey ( ct_field = " content_type1 " , fk_field = " object_id1 " )
other_obj = generic . GenericForeignKey ( ct_field = " content_type2 " , fk_field = " object_id2 " )
def __unicode__ ( self ) :
return u " %s is %s than %s " % ( self . first_obj , self . comparative , self . other_obj )
2006-06-17 03:18:30 +08:00
class Animal ( models . Model ) :
2007-08-05 13:14:46 +08:00
common_name = models . CharField ( max_length = 150 )
latin_name = models . CharField ( max_length = 150 )
2007-12-09 15:12:07 +08:00
2007-05-08 18:59:35 +08:00
tags = generic . GenericRelation ( TaggedItem )
2008-08-12 22:15:38 +08:00
comparisons = generic . GenericRelation ( Comparison ,
2008-08-01 23:54:53 +08:00
object_id_field = " object_id1 " ,
content_type_field = " content_type1 " )
2006-06-17 03:18:30 +08:00
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
def __unicode__ ( self ) :
2006-06-17 03:18:30 +08:00
return self . common_name
2007-12-09 15:12:07 +08:00
2006-06-17 03:18:30 +08:00
class Vegetable ( models . Model ) :
2007-08-05 13:14:46 +08:00
name = models . CharField ( max_length = 150 )
2006-06-17 03:18:30 +08:00
is_yucky = models . BooleanField ( default = True )
2007-12-09 15:12:07 +08:00
2007-05-08 18:59:35 +08:00
tags = generic . GenericRelation ( TaggedItem )
2007-12-09 15:12:07 +08:00
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
def __unicode__ ( self ) :
2006-06-17 03:18:30 +08:00
return self . name
2007-12-09 15:12:07 +08:00
2006-06-17 03:18:30 +08:00
class Mineral ( models . Model ) :
2007-08-05 13:14:46 +08:00
name = models . CharField ( max_length = 150 )
2006-06-17 03:18:30 +08:00
hardness = models . PositiveSmallIntegerField ( )
2007-12-09 15:12:07 +08:00
2006-06-17 03:18:30 +08:00
# note the lack of an explicit GenericRelation here...
2007-12-09 15:12:07 +08:00
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
def __unicode__ ( self ) :
2006-06-17 03:18:30 +08:00
return self . name
2007-12-09 15:12:07 +08:00
2006-08-27 21:59:47 +08:00
__test__ = { ' API_TESTS ' : """
2006-06-17 03:18:30 +08:00
# Create the world in 7 lines of code...
>> > lion = Animal ( common_name = " Lion " , latin_name = " Panthera leo " )
>> > platypus = Animal ( common_name = " Platypus " , latin_name = " Ornithorhynchus anatinus " )
>> > eggplant = Vegetable ( name = " Eggplant " , is_yucky = True )
>> > bacon = Vegetable ( name = " Bacon " , is_yucky = False )
>> > quartz = Mineral ( name = " Quartz " , hardness = 7 )
2008-08-27 13:22:33 +08:00
>> > for o in ( platypus , lion , eggplant , bacon , quartz ) :
2006-06-17 03:18:30 +08:00
. . . o . save ( )
# Objects with declared GenericRelations can be tagged directly -- the API
2006-06-20 04:19:01 +08:00
# mimics the many-to-many API.
2006-06-17 03:18:30 +08:00
>> > bacon . tags . create ( tag = " fatty " )
< TaggedItem : fatty >
>> > bacon . tags . create ( tag = " salty " )
< TaggedItem : salty >
2007-01-25 19:24:17 +08:00
>> > lion . tags . create ( tag = " yellow " )
< TaggedItem : yellow >
>> > lion . tags . create ( tag = " hairy " )
< TaggedItem : hairy >
2008-08-27 13:22:33 +08:00
>> > platypus . tags . create ( tag = " fatty " )
< TaggedItem : fatty >
2006-06-17 03:18:30 +08:00
>> > lion . tags . all ( )
[ < TaggedItem : hairy > , < TaggedItem : yellow > ]
>> > bacon . tags . all ( )
[ < TaggedItem : fatty > , < TaggedItem : salty > ]
2006-06-20 04:19:01 +08:00
# You can easily access the content object like a foreign key.
2006-06-17 03:18:30 +08:00
>> > t = TaggedItem . objects . get ( tag = " salty " )
>> > t . content_object
< Vegetable : Bacon >
# Recall that the Mineral class doesn't have an explicit GenericRelation
2006-06-20 04:19:01 +08:00
# defined. That's OK, because you can create TaggedItems explicitly.
2006-06-17 03:18:30 +08:00
>> > tag1 = TaggedItem ( content_object = quartz , tag = " shiny " )
>> > tag2 = TaggedItem ( content_object = quartz , tag = " clearish " )
>> > tag1 . save ( )
>> > tag2 . save ( )
2006-06-20 04:19:01 +08:00
# However, excluding GenericRelations means your lookups have to be a bit more
# explicit.
2006-06-17 03:18:30 +08:00
>> > from django . contrib . contenttypes . models import ContentType
>> > ctype = ContentType . objects . get_for_model ( quartz )
>> > TaggedItem . objects . filter ( content_type__pk = ctype . id , object_id = quartz . id )
[ < TaggedItem : clearish > , < TaggedItem : shiny > ]
2006-06-20 04:19:01 +08:00
# You can set a generic foreign key in the way you'd expect.
2006-06-17 03:18:30 +08:00
>> > tag1 . content_object = platypus
>> > tag1 . save ( )
>> > platypus . tags . all ( )
2008-08-27 13:22:33 +08:00
[ < TaggedItem : fatty > , < TaggedItem : shiny > ]
2006-06-17 03:18:30 +08:00
>> > TaggedItem . objects . filter ( content_type__pk = ctype . id , object_id = quartz . id )
[ < TaggedItem : clearish > ]
2007-01-25 19:24:17 +08:00
2008-08-27 13:22:33 +08:00
# Queries across generic relations respect the content types. Even though there are two TaggedItems with a tag of "fatty", this query only pulls out the one with the content type related to Animals.
2008-08-28 13:00:23 +08:00
>> > Animal . objects . order_by ( ' common_name ' )
[ < Animal : Lion > , < Animal : Platypus > ]
2008-08-27 13:22:33 +08:00
>> > Animal . objects . filter ( tags__tag = ' fatty ' )
[ < Animal : Platypus > ]
2008-08-28 13:00:23 +08:00
>> > Animal . objects . exclude ( tags__tag = ' fatty ' )
[ < Animal : Lion > ]
2008-08-27 13:22:33 +08:00
2007-01-25 19:24:17 +08:00
# If you delete an object with an explicit Generic relation, the related
# objects are deleted when the source object is deleted.
# Original list of tags:
>> > [ ( t . tag , t . content_type , t . object_id ) for t in TaggedItem . objects . all ( ) ]
2008-08-27 13:22:33 +08:00
[ ( u ' clearish ' , < ContentType : mineral > , 1 ) , ( u ' fatty ' , < ContentType : vegetable > , 2 ) , ( u ' fatty ' , < ContentType : animal > , 1 ) , ( u ' hairy ' , < ContentType : animal > , 2 ) , ( u ' salty ' , < ContentType : vegetable > , 2 ) , ( u ' shiny ' , < ContentType : animal > , 1 ) , ( u ' yellow ' , < ContentType : animal > , 2 ) ]
2007-01-25 19:24:17 +08:00
>> > lion . delete ( )
>> > [ ( t . tag , t . content_type , t . object_id ) for t in TaggedItem . objects . all ( ) ]
2008-08-27 13:22:33 +08:00
[ ( u ' clearish ' , < ContentType : mineral > , 1 ) , ( u ' fatty ' , < ContentType : vegetable > , 2 ) , ( u ' fatty ' , < ContentType : animal > , 1 ) , ( u ' salty ' , < ContentType : vegetable > , 2 ) , ( u ' shiny ' , < ContentType : animal > , 1 ) ]
2007-01-25 19:24:17 +08:00
2007-12-09 15:12:07 +08:00
# If Generic Relation is not explicitly defined, any related objects
2007-01-25 19:24:17 +08:00
# remain after deletion of the source object.
>> > quartz . delete ( )
>> > [ ( t . tag , t . content_type , t . object_id ) for t in TaggedItem . objects . all ( ) ]
2008-08-27 13:22:33 +08:00
[ ( u ' clearish ' , < ContentType : mineral > , 1 ) , ( u ' fatty ' , < ContentType : vegetable > , 2 ) , ( u ' fatty ' , < ContentType : animal > , 1 ) , ( u ' salty ' , < ContentType : vegetable > , 2 ) , ( u ' shiny ' , < ContentType : animal > , 1 ) ]
2007-01-25 19:24:17 +08:00
2007-12-09 15:12:07 +08:00
# If you delete a tag, the objects using the tag are unaffected
2007-01-25 19:24:17 +08:00
# (other than losing a tag)
>> > tag = TaggedItem . objects . get ( id = 1 )
>> > tag . delete ( )
>> > bacon . tags . all ( )
[ < TaggedItem : salty > ]
>> > [ ( t . tag , t . content_type , t . object_id ) for t in TaggedItem . objects . all ( ) ]
2008-08-27 13:22:33 +08:00
[ ( u ' clearish ' , < ContentType : mineral > , 1 ) , ( u ' fatty ' , < ContentType : animal > , 1 ) , ( u ' salty ' , < ContentType : vegetable > , 2 ) , ( u ' shiny ' , < ContentType : animal > , 1 ) ]
>> > TaggedItem . objects . filter ( tag = ' fatty ' ) . delete ( )
2007-01-25 19:24:17 +08:00
2007-12-09 15:12:07 +08:00
>> > ctype = ContentType . objects . get_for_model ( lion )
>> > Animal . objects . filter ( tags__content_type = ctype )
[ < Animal : Platypus > ]
2008-08-01 23:54:53 +08:00
# Simple tests for multiple GenericForeignKeys
# only uses one model, since the above tests should be sufficient.
>> > tiger , cheetah , bear = Animal ( common_name = " tiger " ) , Animal ( common_name = " cheetah " ) , Animal ( common_name = " bear " )
>> > for o in [ tiger , cheetah , bear ] : o . save ( )
# Create directly
>> > Comparison ( first_obj = cheetah , other_obj = tiger , comparative = " faster " ) . save ( )
>> > Comparison ( first_obj = tiger , other_obj = cheetah , comparative = " cooler " ) . save ( )
# Create using GenericRelation
>> > tiger . comparisons . create ( other_obj = bear , comparative = " cooler " )
< Comparison : tiger is cooler than bear >
>> > tiger . comparisons . create ( other_obj = cheetah , comparative = " stronger " )
< Comparison : tiger is stronger than cheetah >
>> > cheetah . comparisons . all ( )
[ < Comparison : cheetah is faster than tiger > ]
# Filtering works
>> > tiger . comparisons . filter ( comparative = " cooler " )
[ < Comparison : tiger is cooler than cheetah > , < Comparison : tiger is cooler than bear > ]
# Filtering and deleting works
>> > subjective = [ " cooler " ]
>> > tiger . comparisons . filter ( comparative__in = subjective ) . delete ( )
>> > Comparison . objects . all ( )
[ < Comparison : cheetah is faster than tiger > , < Comparison : tiger is stronger than cheetah > ]
# If we delete cheetah, Comparisons with cheetah as 'first_obj' will be deleted
# since Animal has an explicit GenericRelation to Comparison through first_obj.
# Comparisons with cheetah as 'other_obj' will not be deleted.
>> > cheetah . delete ( )
>> > Comparison . objects . all ( )
[ < Comparison : tiger is stronger than None > ]
2008-08-10 12:03:01 +08:00
2008-08-27 13:22:33 +08:00
2008-08-10 12:03:01 +08:00
# GenericInlineFormSet tests ##################################################
>> > from django . contrib . contenttypes . generic import generic_inlineformset_factory
>> > GenericFormSet = generic_inlineformset_factory ( TaggedItem , extra = 1 )
>> > formset = GenericFormSet ( instance = Animal ( ) )
>> > for form in formset . forms :
. . . print form . as_p ( )
< p > < label for = " id_generic_relations-taggeditem-content_type-object_id-0-tag " > Tag : < / label > < input id = " id_generic_relations-taggeditem-content_type-object_id-0-tag " type = " text " name = " generic_relations-taggeditem-content_type-object_id-0-tag " maxlength = " 50 " / > < / p >
< p > < label for = " id_generic_relations-taggeditem-content_type-object_id-0-DELETE " > Delete : < / label > < input type = " checkbox " name = " generic_relations-taggeditem-content_type-object_id-0-DELETE " id = " id_generic_relations-taggeditem-content_type-object_id-0-DELETE " / > < input type = " hidden " name = " generic_relations-taggeditem-content_type-object_id-0-id " id = " id_generic_relations-taggeditem-content_type-object_id-0-id " / > < / p >
>> > formset = GenericFormSet ( instance = platypus )
>> > for form in formset . forms :
. . . print form . as_p ( )
< p > < label for = " id_generic_relations-taggeditem-content_type-object_id-0-tag " > Tag : < / label > < input id = " id_generic_relations-taggeditem-content_type-object_id-0-tag " type = " text " name = " generic_relations-taggeditem-content_type-object_id-0-tag " value = " shiny " maxlength = " 50 " / > < / p >
2008-08-27 13:22:33 +08:00
< p > < label for = " id_generic_relations-taggeditem-content_type-object_id-0-DELETE " > Delete : < / label > < input type = " checkbox " name = " generic_relations-taggeditem-content_type-object_id-0-DELETE " id = " id_generic_relations-taggeditem-content_type-object_id-0-DELETE " / > < input type = " hidden " name = " generic_relations-taggeditem-content_type-object_id-0-id " value = " ... " id = " id_generic_relations-taggeditem-content_type-object_id-0-id " / > < / p >
2008-08-10 12:03:01 +08:00
< p > < label for = " id_generic_relations-taggeditem-content_type-object_id-1-tag " > Tag : < / label > < input id = " id_generic_relations-taggeditem-content_type-object_id-1-tag " type = " text " name = " generic_relations-taggeditem-content_type-object_id-1-tag " maxlength = " 50 " / > < / p >
< p > < label for = " id_generic_relations-taggeditem-content_type-object_id-1-DELETE " > Delete : < / label > < input type = " checkbox " name = " generic_relations-taggeditem-content_type-object_id-1-DELETE " id = " id_generic_relations-taggeditem-content_type-object_id-1-DELETE " / > < input type = " hidden " name = " generic_relations-taggeditem-content_type-object_id-1-id " id = " id_generic_relations-taggeditem-content_type-object_id-1-id " / > < / p >
2006-08-27 21:59:47 +08:00
""" }