/polymorphic/polymorphic_model.py
Python | 190 lines | 158 code | 6 blank | 26 comment | 1 complexity | e41042214f2f45e995109bf221347617 MD5 | raw file
1# -*- coding: utf-8 -*- 2""" 3Seamless Polymorphic Inheritance for Django Models 4================================================== 5 6Please see README.rst and DOCS.rst for further information. 7 8Or on the Web: 9http://bserve.webhop.org/wiki/django_polymorphic 10http://github.com/bconstantin/django_polymorphic 11http://bitbucket.org/bconstantin/django_polymorphic 12 13Copyright: 14This code and affiliated files are (C) by Bert Constantin and individual contributors. 15Please see LICENSE and AUTHORS for more information. 16""" 17 18from pprint import pprint 19import sys 20from compatibility_tools import defaultdict 21 22from django.db import models 23from django.contrib.contenttypes.models import ContentType 24from django import VERSION as django_VERSION 25 26from base import PolymorphicModelBase 27from manager import PolymorphicManager 28from query import PolymorphicQuerySet 29from showfields import ShowFieldType 30from query_translate import translate_polymorphic_Q_object 31 32 33################################################################################### 34### PolymorphicModel 35 36class PolymorphicModel(models.Model): 37 """ 38 Abstract base class that provides polymorphic behaviour 39 for any model directly or indirectly derived from it. 40 41 For usage instructions & examples please see documentation. 42 43 PolymorphicModel declares one field for internal use (polymorphic_ctype) 44 and provides a polymorphic manager as the default manager 45 (and as 'objects'). 46 47 PolymorphicModel overrides the save() and __init__ methods. 48 49 If your derived class overrides any of these methods as well, then you need 50 to take care that you correctly call the method of the superclass, like: 51 52 super(YourClass,self).save(*args,**kwargs) 53 """ 54 __metaclass__ = PolymorphicModelBase 55 56 # for PolymorphicModelBase, so it can tell which models are polymorphic and which are not (duck typing) 57 polymorphic_model_marker = True 58 59 # for PolymorphicQuery, True => an overloaded __repr__ with nicer multi-line output is used by PolymorphicQuery 60 polymorphic_query_multiline_output = False 61 62 class Meta: 63 abstract = True 64 65 # avoid ContentType related field accessor clash (an error emitted by model validation) 66 # we really should use both app_label and model name, but this is only possible since Django 1.2 67 if django_VERSION[0] <= 1 and django_VERSION[1] <= 1: 68 p_related_name_template = 'polymorphic_%(class)s_set' 69 else: 70 p_related_name_template = 'polymorphic_%(app_label)s.%(class)s_set' 71 polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False, 72 related_name=p_related_name_template) 73 74 # some applications want to know the name of the fields that are added to its models 75 polymorphic_internal_model_fields = [ 'polymorphic_ctype' ] 76 77 objects = PolymorphicManager() 78 base_objects = models.Manager() 79 80 @classmethod 81 def translate_polymorphic_Q_object(self_class,q): 82 return translate_polymorphic_Q_object(self_class,q) 83 84 def pre_save_polymorphic(self): 85 """Normally not needed. 86 This function may be called manually in special use-cases. When the object 87 is saved for the first time, we store its real class in polymorphic_ctype. 88 When the object later is retrieved by PolymorphicQuerySet, it uses this 89 field to figure out the real class of this object 90 (used by PolymorphicQuerySet._get_real_instances) 91 """ 92 if not self.polymorphic_ctype: 93 self.polymorphic_ctype = ContentType.objects.get_for_model(self) 94 95 def save(self, *args, **kwargs): 96 """Overridden model save function which supports the polymorphism 97 functionality (through pre_save_polymorphic).""" 98 self.pre_save_polymorphic() 99 return super(PolymorphicModel, self).save(*args, **kwargs) 100 101 def get_real_instance_class(self): 102 """Normally not needed. 103 If a non-polymorphic manager (like base_objects) has been used to 104 retrieve objects, then the real class/type of these objects may be 105 determined using this method.""" 106 # the following line would be the easiest way to do this, but it produces sql queries 107 #return self.polymorphic_ctype.model_class() 108 # so we use the following version, which uses the CopntentType manager cache 109 return ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class() 110 111 def get_real_instance(self): 112 """Normally not needed. 113 If a non-polymorphic manager (like base_objects) has been used to 114 retrieve objects, then the complete object with it's real class/type 115 and all fields may be retrieved with this method. 116 Each method call executes one db query (if necessary).""" 117 real_model = self.get_real_instance_class() 118 if real_model == self.__class__: return self 119 return real_model.objects.get(pk=self.pk) 120 121 122 def __init__(self, * args, ** kwargs): 123 """Replace Django's inheritance accessor member functions for our model 124 (self.__class__) with our own versions. 125 We monkey patch them until a patch can be added to Django 126 (which would probably be very small and make all of this obsolete). 127 128 If we have inheritance of the form ModelA -> ModelB ->ModelC then 129 Django creates accessors like this: 130 - ModelA: modelb 131 - ModelB: modela_ptr, modelb, modelc 132 - ModelC: modela_ptr, modelb, modelb_ptr, modelc 133 134 These accessors allow Django (and everyone else) to travel up and down 135 the inheritance tree for the db object at hand. 136 137 The original Django accessors use our polymorphic manager. 138 But they should not. So we replace them with our own accessors that use 139 our appropriate base_objects manager. 140 """ 141 super(PolymorphicModel, self).__init__(*args, ** kwargs) 142 143 if self.__class__.polymorphic_super_sub_accessors_replaced: return 144 self.__class__.polymorphic_super_sub_accessors_replaced = True 145 146 def create_accessor_function_for_model(model, accessor_name): 147 def accessor_function(self): 148 attr = model.base_objects.get(pk=self.pk) 149 return attr 150 return accessor_function 151 152 subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models() 153 154 from django.db.models.fields.related import SingleRelatedObjectDescriptor, ReverseSingleRelatedObjectDescriptor 155 for name,model in subclasses_and_superclasses_accessors.iteritems(): 156 orig_accessor = getattr(self.__class__, name, None) 157 if type(orig_accessor) in [SingleRelatedObjectDescriptor,ReverseSingleRelatedObjectDescriptor]: 158 #print >>sys.stderr, '---------- replacing',name, orig_accessor 159 setattr(self.__class__, name, property(create_accessor_function_for_model(model, name)) ) 160 161 def _get_inheritance_relation_fields_and_models(self): 162 """helper function for __init__: 163 determine names of all Django inheritance accessor member functions for type(self)""" 164 165 def add_model(model, as_ptr, result): 166 name = model.__name__.lower() 167 if as_ptr: name+='_ptr' 168 result[name] = model 169 170 def add_model_if_regular(model, as_ptr, result): 171 if ( issubclass(model, models.Model) and model != models.Model 172 and model != self.__class__ 173 and model != PolymorphicModel ): 174 add_model(model,as_ptr,result) 175 176 def add_all_super_models(model, result): 177 add_model_if_regular(model, True, result) 178 for b in model.__bases__: 179 add_all_super_models(b, result) 180 181 def add_all_sub_models(model, result): 182 for b in model.__subclasses__(): 183 add_model_if_regular(b, False, result) 184 185 result = {} 186 add_all_super_models(self.__class__,result) 187 add_all_sub_models(self.__class__,result) 188 return result 189 190