diff --git a/example/pexp/models.py b/example/pexp/models.py index b0ed8c5..0c73e31 100644 --- a/example/pexp/models.py +++ b/example/pexp/models.py @@ -2,7 +2,7 @@ from django.db import models -from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet +from polymorphic.models import PolymorphicModel from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent class Project(ShowFieldContent, PolymorphicModel): diff --git a/polymorphic/__init__.py b/polymorphic/__init__.py index c598b84..412e5d1 100644 --- a/polymorphic/__init__.py +++ b/polymorphic/__init__.py @@ -8,10 +8,6 @@ Please see LICENSE and AUTHORS for more information. """ from __future__ import absolute_import import django -from .polymorphic_model import PolymorphicModel -from .manager import PolymorphicManager -from .query import PolymorphicQuerySet -from .query_translate import translate_polymorphic_Q_object from .showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent from .showfields import ShowFields, ShowFieldTypes, ShowFieldsAndTypes # import old names for compatibility diff --git a/polymorphic/models.py b/polymorphic/models.py index 69652f9..cbfdef9 100644 --- a/polymorphic/models.py +++ b/polymorphic/models.py @@ -1,10 +1,229 @@ # -*- coding: utf-8 -*- """ -IMPORTANT: +Seamless Polymorphic Inheritance for Django Models +================================================== -The models.py module is not used anymore. -Please use the following import method in your apps: +Please see README.rst and DOCS.rst for further information. - from polymorphic import PolymorphicModel, ... +Or on the Web: +http://chrisglass.github.com/django_polymorphic/ +http://github.com/chrisglass/django_polymorphic +Copyright: +This code and affiliated files are (C) by Bert Constantin and individual contributors. +Please see LICENSE and AUTHORS for more information. """ +from __future__ import absolute_import + +from django.db import models +from django.contrib.contenttypes.models import ContentType +from django.utils import six + +from .base import PolymorphicModelBase +from .manager import PolymorphicManager +from .query_translate import translate_polymorphic_Q_object + +################################################################################### +### PolymorphicModel + +class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): + """ + Abstract base class that provides polymorphic behaviour + for any model directly or indirectly derived from it. + + For usage instructions & examples please see documentation. + + PolymorphicModel declares one field for internal use (polymorphic_ctype) + and provides a polymorphic manager as the default manager + (and as 'objects'). + + PolymorphicModel overrides the save() and __init__ methods. + + If your derived class overrides any of these methods as well, then you need + to take care that you correctly call the method of the superclass, like: + + super(YourClass,self).save(*args,**kwargs) + """ + + # for PolymorphicModelBase, so it can tell which models are polymorphic and which are not (duck typing) + polymorphic_model_marker = True + + # for PolymorphicQuery, True => an overloaded __repr__ with nicer multi-line output is used by PolymorphicQuery + polymorphic_query_multiline_output = False + + class Meta: + abstract = True + + # avoid ContentType related field accessor clash (an error emitted by model validation) + polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False, + related_name='polymorphic_%(app_label)s.%(class)s_set+') + + # some applications want to know the name of the fields that are added to its models + polymorphic_internal_model_fields = ['polymorphic_ctype'] + + # Note that Django 1.5 removes these managers because the model is abstract. + # They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers() + objects = PolymorphicManager() + base_objects = models.Manager() + + @classmethod + def translate_polymorphic_Q_object(self_class, q): + return translate_polymorphic_Q_object(self_class, q) + + def pre_save_polymorphic(self): + """Normally not needed. + This function may be called manually in special use-cases. When the object + is saved for the first time, we store its real class in polymorphic_ctype. + When the object later is retrieved by PolymorphicQuerySet, it uses this + field to figure out the real class of this object + (used by PolymorphicQuerySet._get_real_instances) + """ + if not self.polymorphic_ctype_id: + self.polymorphic_ctype = ContentType.objects.get_for_model(self, for_concrete_model=False) + pre_save_polymorphic.alters_data = True + + def save(self, *args, **kwargs): + """Overridden model save function which supports the polymorphism + functionality (through pre_save_polymorphic).""" + self.pre_save_polymorphic() + return super(PolymorphicModel, self).save(*args, **kwargs) + save.alters_data = True + + def get_real_instance_class(self): + """ + Normally not needed. + If a non-polymorphic manager (like base_objects) has been used to + retrieve objects, then the real class/type of these objects may be + determined using this method. + """ + # the following line would be the easiest way to do this, but it produces sql queries + # return self.polymorphic_ctype.model_class() + # so we use the following version, which uses the ContentType manager cache. + # Note that model_class() can return None for stale content types; + # when the content type record still exists but no longer refers to an existing model. + try: + model = ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class() + except AttributeError: + # Django <1.6 workaround + return None + + # Protect against bad imports (dumpdata without --natural) or other + # issues missing with the ContentType models. + if model is not None \ + and not issubclass(model, self.__class__) \ + and not issubclass(model, self.__class__._meta.proxy_for_model): + raise RuntimeError("ContentType {0} for {1} #{2} does not point to a subclass!".format( + self.polymorphic_ctype_id, model, self.pk, + )) + return model + + def get_real_concrete_instance_class_id(self): + model_class = self.get_real_instance_class() + if model_class is None: + return None + return ContentType.objects.get_for_model(model_class, for_concrete_model=True).pk + + def get_real_concrete_instance_class(self): + model_class = self.get_real_instance_class() + if model_class is None: + return None + return ContentType.objects.get_for_model(model_class, for_concrete_model=True).model_class() + + def get_real_instance(self): + """Normally not needed. + If a non-polymorphic manager (like base_objects) has been used to + retrieve objects, then the complete object with it's real class/type + and all fields may be retrieved with this method. + Each method call executes one db query (if necessary).""" + real_model = self.get_real_instance_class() + if real_model == self.__class__: + return self + return real_model.objects.get(pk=self.pk) + + def __init__(self, * args, ** kwargs): + """Replace Django's inheritance accessor member functions for our model + (self.__class__) with our own versions. + We monkey patch them until a patch can be added to Django + (which would probably be very small and make all of this obsolete). + + If we have inheritance of the form ModelA -> ModelB ->ModelC then + Django creates accessors like this: + - ModelA: modelb + - ModelB: modela_ptr, modelb, modelc + - ModelC: modela_ptr, modelb, modelb_ptr, modelc + + These accessors allow Django (and everyone else) to travel up and down + the inheritance tree for the db object at hand. + + The original Django accessors use our polymorphic manager. + But they should not. So we replace them with our own accessors that use + our appropriate base_objects manager. + """ + super(PolymorphicModel, self).__init__(*args, ** kwargs) + + if self.__class__.polymorphic_super_sub_accessors_replaced: + return + self.__class__.polymorphic_super_sub_accessors_replaced = True + + def create_accessor_function_for_model(model, accessor_name): + def accessor_function(self): + attr = model.base_objects.get(pk=self.pk) + return attr + return accessor_function + + subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models() + + try: + from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor + except ImportError: + # django < 1.9 + from django.db.models.fields.related import ( + SingleRelatedObjectDescriptor as ReverseOneToOneDescriptor, + ReverseSingleRelatedObjectDescriptor as ForwardManyToOneDescriptor, + ) + for name, model in subclasses_and_superclasses_accessors.items(): + orig_accessor = getattr(self.__class__, name, None) + if type(orig_accessor) in [ReverseOneToOneDescriptor, ForwardManyToOneDescriptor]: + #print >>sys.stderr, '---------- replacing', name, orig_accessor, '->', model + setattr(self.__class__, name, property(create_accessor_function_for_model(model, name))) + + def _get_inheritance_relation_fields_and_models(self): + """helper function for __init__: + determine names of all Django inheritance accessor member functions for type(self)""" + + def add_model(model, field_name, result): + result[field_name] = model + + def add_model_if_regular(model, field_name, result): + if (issubclass(model, models.Model) + and model != models.Model + and model != self.__class__ + and model != PolymorphicModel): + add_model(model, field_name, result) + + def add_all_super_models(model, result): + for super_cls, field_to_super in model._meta.parents.items(): + if field_to_super is not None: #if not a link to a proxy model + field_name = field_to_super.name #the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' + add_model_if_regular(super_cls, field_name, result) + add_all_super_models(super_cls, result) + + def add_all_sub_models(super_cls, result): + for sub_cls in super_cls.__subclasses__(): #go through all subclasses of model + if super_cls in sub_cls._meta.parents: #super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model + field_to_super = sub_cls._meta.parents[super_cls] #get the field that links sub_cls to super_cls + if field_to_super is not None: # if filed_to_super is not a link to a proxy model + super_to_sub_related_field = field_to_super.rel + if super_to_sub_related_field.related_name is None: + #if related name is None the related field is the name of the subclass + to_subclass_fieldname = sub_cls.__name__.lower() + else: + #otherwise use the given related name + to_subclass_fieldname = super_to_sub_related_field.related_name + + add_model_if_regular(sub_cls, to_subclass_fieldname, result) + + result = {} + add_all_super_models(self.__class__, result) + add_all_sub_models(self.__class__, result) + return result diff --git a/polymorphic/polymorphic_model.py b/polymorphic/polymorphic_model.py deleted file mode 100644 index cbfdef9..0000000 --- a/polymorphic/polymorphic_model.py +++ /dev/null @@ -1,229 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Seamless Polymorphic Inheritance for Django Models -================================================== - -Please see README.rst and DOCS.rst for further information. - -Or on the Web: -http://chrisglass.github.com/django_polymorphic/ -http://github.com/chrisglass/django_polymorphic - -Copyright: -This code and affiliated files are (C) by Bert Constantin and individual contributors. -Please see LICENSE and AUTHORS for more information. -""" -from __future__ import absolute_import - -from django.db import models -from django.contrib.contenttypes.models import ContentType -from django.utils import six - -from .base import PolymorphicModelBase -from .manager import PolymorphicManager -from .query_translate import translate_polymorphic_Q_object - -################################################################################### -### PolymorphicModel - -class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): - """ - Abstract base class that provides polymorphic behaviour - for any model directly or indirectly derived from it. - - For usage instructions & examples please see documentation. - - PolymorphicModel declares one field for internal use (polymorphic_ctype) - and provides a polymorphic manager as the default manager - (and as 'objects'). - - PolymorphicModel overrides the save() and __init__ methods. - - If your derived class overrides any of these methods as well, then you need - to take care that you correctly call the method of the superclass, like: - - super(YourClass,self).save(*args,**kwargs) - """ - - # for PolymorphicModelBase, so it can tell which models are polymorphic and which are not (duck typing) - polymorphic_model_marker = True - - # for PolymorphicQuery, True => an overloaded __repr__ with nicer multi-line output is used by PolymorphicQuery - polymorphic_query_multiline_output = False - - class Meta: - abstract = True - - # avoid ContentType related field accessor clash (an error emitted by model validation) - polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False, - related_name='polymorphic_%(app_label)s.%(class)s_set+') - - # some applications want to know the name of the fields that are added to its models - polymorphic_internal_model_fields = ['polymorphic_ctype'] - - # Note that Django 1.5 removes these managers because the model is abstract. - # They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers() - objects = PolymorphicManager() - base_objects = models.Manager() - - @classmethod - def translate_polymorphic_Q_object(self_class, q): - return translate_polymorphic_Q_object(self_class, q) - - def pre_save_polymorphic(self): - """Normally not needed. - This function may be called manually in special use-cases. When the object - is saved for the first time, we store its real class in polymorphic_ctype. - When the object later is retrieved by PolymorphicQuerySet, it uses this - field to figure out the real class of this object - (used by PolymorphicQuerySet._get_real_instances) - """ - if not self.polymorphic_ctype_id: - self.polymorphic_ctype = ContentType.objects.get_for_model(self, for_concrete_model=False) - pre_save_polymorphic.alters_data = True - - def save(self, *args, **kwargs): - """Overridden model save function which supports the polymorphism - functionality (through pre_save_polymorphic).""" - self.pre_save_polymorphic() - return super(PolymorphicModel, self).save(*args, **kwargs) - save.alters_data = True - - def get_real_instance_class(self): - """ - Normally not needed. - If a non-polymorphic manager (like base_objects) has been used to - retrieve objects, then the real class/type of these objects may be - determined using this method. - """ - # the following line would be the easiest way to do this, but it produces sql queries - # return self.polymorphic_ctype.model_class() - # so we use the following version, which uses the ContentType manager cache. - # Note that model_class() can return None for stale content types; - # when the content type record still exists but no longer refers to an existing model. - try: - model = ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class() - except AttributeError: - # Django <1.6 workaround - return None - - # Protect against bad imports (dumpdata without --natural) or other - # issues missing with the ContentType models. - if model is not None \ - and not issubclass(model, self.__class__) \ - and not issubclass(model, self.__class__._meta.proxy_for_model): - raise RuntimeError("ContentType {0} for {1} #{2} does not point to a subclass!".format( - self.polymorphic_ctype_id, model, self.pk, - )) - return model - - def get_real_concrete_instance_class_id(self): - model_class = self.get_real_instance_class() - if model_class is None: - return None - return ContentType.objects.get_for_model(model_class, for_concrete_model=True).pk - - def get_real_concrete_instance_class(self): - model_class = self.get_real_instance_class() - if model_class is None: - return None - return ContentType.objects.get_for_model(model_class, for_concrete_model=True).model_class() - - def get_real_instance(self): - """Normally not needed. - If a non-polymorphic manager (like base_objects) has been used to - retrieve objects, then the complete object with it's real class/type - and all fields may be retrieved with this method. - Each method call executes one db query (if necessary).""" - real_model = self.get_real_instance_class() - if real_model == self.__class__: - return self - return real_model.objects.get(pk=self.pk) - - def __init__(self, * args, ** kwargs): - """Replace Django's inheritance accessor member functions for our model - (self.__class__) with our own versions. - We monkey patch them until a patch can be added to Django - (which would probably be very small and make all of this obsolete). - - If we have inheritance of the form ModelA -> ModelB ->ModelC then - Django creates accessors like this: - - ModelA: modelb - - ModelB: modela_ptr, modelb, modelc - - ModelC: modela_ptr, modelb, modelb_ptr, modelc - - These accessors allow Django (and everyone else) to travel up and down - the inheritance tree for the db object at hand. - - The original Django accessors use our polymorphic manager. - But they should not. So we replace them with our own accessors that use - our appropriate base_objects manager. - """ - super(PolymorphicModel, self).__init__(*args, ** kwargs) - - if self.__class__.polymorphic_super_sub_accessors_replaced: - return - self.__class__.polymorphic_super_sub_accessors_replaced = True - - def create_accessor_function_for_model(model, accessor_name): - def accessor_function(self): - attr = model.base_objects.get(pk=self.pk) - return attr - return accessor_function - - subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models() - - try: - from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor - except ImportError: - # django < 1.9 - from django.db.models.fields.related import ( - SingleRelatedObjectDescriptor as ReverseOneToOneDescriptor, - ReverseSingleRelatedObjectDescriptor as ForwardManyToOneDescriptor, - ) - for name, model in subclasses_and_superclasses_accessors.items(): - orig_accessor = getattr(self.__class__, name, None) - if type(orig_accessor) in [ReverseOneToOneDescriptor, ForwardManyToOneDescriptor]: - #print >>sys.stderr, '---------- replacing', name, orig_accessor, '->', model - setattr(self.__class__, name, property(create_accessor_function_for_model(model, name))) - - def _get_inheritance_relation_fields_and_models(self): - """helper function for __init__: - determine names of all Django inheritance accessor member functions for type(self)""" - - def add_model(model, field_name, result): - result[field_name] = model - - def add_model_if_regular(model, field_name, result): - if (issubclass(model, models.Model) - and model != models.Model - and model != self.__class__ - and model != PolymorphicModel): - add_model(model, field_name, result) - - def add_all_super_models(model, result): - for super_cls, field_to_super in model._meta.parents.items(): - if field_to_super is not None: #if not a link to a proxy model - field_name = field_to_super.name #the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' - add_model_if_regular(super_cls, field_name, result) - add_all_super_models(super_cls, result) - - def add_all_sub_models(super_cls, result): - for sub_cls in super_cls.__subclasses__(): #go through all subclasses of model - if super_cls in sub_cls._meta.parents: #super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model - field_to_super = sub_cls._meta.parents[super_cls] #get the field that links sub_cls to super_cls - if field_to_super is not None: # if filed_to_super is not a link to a proxy model - super_to_sub_related_field = field_to_super.rel - if super_to_sub_related_field.related_name is None: - #if related name is None the related field is the name of the subclass - to_subclass_fieldname = sub_cls.__name__.lower() - else: - #otherwise use the given related name - to_subclass_fieldname = super_to_sub_related_field.related_name - - add_model_if_regular(sub_cls, to_subclass_fieldname, result) - - result = {} - add_all_super_models(self.__class__, result) - add_all_sub_models(self.__class__, result) - return result