From ba9a0b1302dfd8c2c8a6325da1ca1adec6292613 Mon Sep 17 00:00:00 2001 From: Bert Constantin Date: Mon, 25 Jan 2010 16:49:23 +0100 Subject: [PATCH] updated docs, and moved them to DOCS.rst and README.rst --- AUTHORS | 4 + DOCS.rst | 434 ++++++++++++++++++++++ LICENSE | 4 +- README.rst | 485 ++----------------------- poly/management/commands/polycmd.py | 12 +- poly/models.py | 31 +- poly/polymorphic.py | 541 +++------------------------- 7 files changed, 544 insertions(+), 967 deletions(-) create mode 100644 AUTHORS create mode 100644 DOCS.rst diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..613e177 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +django_polymorphic was initially created by Bert Constantin in 2009/2010. + +If you contributed code, test cases etc., your name should +appear here as well. diff --git a/DOCS.rst b/DOCS.rst new file mode 100644 index 0000000..a10c557 --- /dev/null +++ b/DOCS.rst @@ -0,0 +1,434 @@ +=============================== +Fully Polymorphic Django Models +=============================== + + +'polymorphic.py' is an add-on module that adds automatic +polymorphism to the Django model inheritance system. + +The effect is: For enabled models, objects retrieved from the +database are always delivered just as they were created and saved, +with the same type/class and fields - regardless how they are +retrieved. The resulting querysets are polymorphic, i.e. may deliver +objects of several different types in a single query result. + +Please see the examples below as they demonstrate this best. + +Please note that this module is still very experimental. See below for +current restrictions, caveats, and performance implications. + + +Installation / Testing +====================== + +Requirements +------------ + +Django 1.1 (or later) and Python 2.5 (or later). This code has been tested +on Django 1.1.1 / 1.2 alpha and Python 2.5.4 / 2.6.4 on Linux. + +Testing +------- + +The repository (or tar file) contains a complete Django project +that may be used for tests or experiments. + +To run the included test suite, execute:: + + ./manage test poly + +'management/commands/polycmd.py' can be used for experiments +- modify this file to your liking, then run:: + + ./manage syncdb # db is created in /var/tmp/... (settings.py) + ./manage polycmd + +Using polymorphic models in your own projects +--------------------------------------------- + +Copy polymorphic.py (from the 'poly' dir) into a directory from where +you can import it, like your app directory (where your models.py and +views.py files live). + + +Defining Polymorphic Models +=========================== + +To make models polymorphic, use PolymorphicModel instead of Django's +models.Model as the superclass of your base model. All models +inheriting from your base class will be polymorphic as well:: + + from polymorphic import PolymorphicModel + + class ModelA(PolymorphicModel): + field1 = models.CharField(max_length=10) + + class ModelB(ModelA): + field2 = models.CharField(max_length=10) + + class ModelC(ModelB): + field3 = models.CharField(max_length=10) + + +Using Polymorphic Models +======================== + +Most of Django's standard ORM functionality is available +and works as expected: + +Create some objects +------------------- + + >>> ModelA.objects.create(field1='A1') + >>> ModelB.objects.create(field1='B1', field2='B2') + >>> ModelC.objects.create(field1='C1', field2='C2', field3='C3') + +Query results are polymorphic +----------------------------- + + >>> ModelA.objects.all() + . + [ , + , + ] + +Filtering for classes (equivalent to python's isinstance() ): +------------------------------------------------------------- + + >>> ModelA.objects.instance_of(ModelB) + . + [ , + ] + + In general, including or excluding parts of the inheritance tree:: + + ModelA.objects.instance_of(ModelB [, ModelC ...]) + ModelA.objects.not_instance_of(ModelB [, ModelC ...]) + +Polymorphic filtering (for fields in derived classes) +----------------------------------------------------- + + For example, cherrypicking objects from multiple derived classes + anywhere in the inheritance tree, using Q objects (with the + slightly enhanced syntax: exact model name + three _ + field name): + + >>> ModelA.objects.filter( Q( ModelB___field2 = 'B2' ) | Q( ModelC___field3 = 'C3' ) ) + . + [ , + ] + +Combining Querysets of different types/models +--------------------------------------------- + + Querysets may now be regarded as object containers that allow the + aggregation of different object types - very similar to python + lists (as long as the objects are accessed through the manager of + a common base class): + + >>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) + . + [ , + ] + +Using Third Party Models (without modifying them) +------------------------------------------------- + + Third party models can be used as polymorphic models without + restrictions by subclassing them. E.g. using a third party + model as the root of a polymorphic inheritance tree:: + + from thirdparty import ThirdPartyModel + + class MyThirdPartyModel(PolymorhpicModel, ThirdPartyModel): + pass # or add fields + + Or instead integrating the third party model anywhere into an + existing polymorphic inheritance tree:: + + class MyModel(SomePolymorphicModel): + my_field = models.CharField(max_length=10) + + class MyModelWithThirdParty(MyModel, ThirdPartyModel): + pass # or add fields + +ManyToManyField, ForeignKey, OneToOneField +------------------------------------------ + + Relationship fields referring to polymorphic models work as + expected: like polymorphic querysets they now always return the + referred objects with the same type/class these were created and + saved as. + + E.g., if in your model you define:: + + field1 = OneToOneField(ModelA) + + then field1 may now also refer to objects of type ModelB or ModelC. + + A ManyToManyField example:: + + # The model holding the relation may be any kind of model, polymorphic or not + class RelatingModel(models.Model): + many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model + + >>> o=RelatingModel.objects.create() + >>> o.many2many.add(ModelA.objects.get(id=1)) + >>> o.many2many.add(ModelB.objects.get(id=2)) + >>> o.many2many.add(ModelC.objects.get(id=3)) + + >>> o.many2many.all() + [ , + , + ] + +Non-Polymorphic Queries +----------------------- + + >>> ModelA.base_objects.all() + . + [ , + , + ] + + Each polymorphic model has 'base_objects' defined as a normal + Django manager. Of course, arbitrary custom managers may be + added to the models as well. + + +Custom Managers, Querysets & Inheritance +======================================== + +Using a Custom Manager +---------------------- + +For creating a custom polymorphic manager class, derive your manager +from PolymorphicManager instead of models.Manager. In your model +class, explicitly add the default manager first, and then your +custom manager:: + + class MyOrderedManager(PolymorphicManager): + def get_query_set(self): + return super(MyOrderedManager,self).get_query_set().order_by('some_field') + + class MyModel(PolymorphicModel): + objects = PolymorphicManager() # add the default polymorphic manager first + ordered_objects = MyOrderedManager() # then add your own manager + +The first manager defined ('objects' in the example) is used by +Django as automatic manager for several purposes, including accessing +related objects. It must not filter objects and it's safest to use +the plain PolymorphicManager here. + +Manager Inheritance / Propagation +--------------------------------- + +Polymorphic models unconditionally inherit all managers from their +base models (as long as these are polymorphic). + +An example (inheriting from MyModel above):: + + class MyModel2(MyModel): + pass + + # Managers inherited from MyModel, delivering MyModel2 objects (including MyModel2 subclass objects) + >>> MyModel2.objects.all() + >>> MyModel2.ordered_objects.all() + +Perhaps a more correct way to describe this: With polymorphic models the +managers are always fully propagated from all polymorphic base models +(as strictly speaking all managers are always inherited with Django models). + + +Using a Custom Queryset Class +----------------------------- + +The PolymorphicManager class accepts one initialization argument, +which is the queryset class the manager should use. A custom +custom queryset class can be defined and used like this:: + + class MyQuerySet(PolymorphicQuerySet): + def my_queryset_method(...): + ... + + class MyModel(PolymorphicModel): + my_objects=PolymorphicManager(MyQuerySet) + ... + + +Performance Considerations +========================== + +The current implementation is pretty simple and does not use any +custom sql - it is purely based on the Django ORM. Right now the +query :: + + result_objects = list( ModelA.objects.filter(...) ) + +performs one sql query to retrieve ModelA objects and one additional +query for each unique derived class occurring in result_objects. +The best case for retrieving 100 objects is 1 db query if all are +class ModelA. If 50 objects are ModelA and 50 are ModelB, then two +queries are executed. If result_objects contains only the base model +type (ModelA), the polymorphic models are just as efficient as plain +Django models (in terms of executed queries). The pathological worst +case is 101 db queries if result_objects contains 100 different +object types (with all of them subclasses of ModelA). + +Performance ist relative: when Django users create their own +polymorphic ad-hoc solution (without a module like polymorphic.py), +this usually results in a variation of :: + + result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ] + +which of has really bad performance. Relative to this, the +performance of the current polymorphic.py is pretty good. +It may well be efficient enough for the majority of use cases. + +Chunking: The implementation always requests objects in chunks of +size Polymorphic_QuerySet_objects_per_request. This limits the +complexity/duration for each query, including the pathological cases. + + +Possible Optimizations +====================== + +PolymorphicQuerySet can be optimized to require only one SQL query +for the queryset evaluation and retrieval of all objects. + +Basically, what ist needed is a possibility to pull in the fields +from all relevant sub-models with one sql query. However, some deeper +digging into the Django database layer will be required in order to +make this happen. + +A viable option might be to get the sql query from the QuerySet +(probably from django.db.models.sql.compiler.SQLCompiler.as_sql), +making sure that all necessary joins are done, and then doing a +custom SQL request from there (like in SQLCompiler.execute_sql). + +An optimized version could fall back to the current ORM-only +implementation for all non-SQL databases. + +SQL Complexity +-------------- + +With only one sql query, one sql join for each possible subclass +would be needed (BaseModel.__subclasses__(), recursively). +With two sql queries, the number of joins could be reduced to the +number of actuallly occurring subclasses in the result. A final +implementation might want to use one query only if the number of +possible subclasses (and therefore joins) is not too large, and +two queries otherwise (using the first query to determine the +actually occurring subclasses, reducing the number of joins for +the second). + +A relatively large number of joins may be needed in both cases, +which raises concerns regarding the efficiency of these database +queries. It is currently unclear however, how many model classes +will actually be involved in typical use cases - the total number +of classes in the inheritance tree as well as the number of distinct +classes in query results. It may well turn out that the increased +number of joins is no problem for the DBMS in all realistic use +cases. Alternatively, if the sql query execution time is +significantly longer even in common use cases, this may still be +acceptable in exchange for the added functionality. + +Let's not forget that all of the above is just about optimizations. +The current simplistic implementation already works well - perhaps +well enough for the majority of applications. + + +Loose Ends +========== + +Currently Unsupported Queryset Methods +-------------------------------------- + ++ aggregate() probably makes only sense in a purely non-OO/relational + way. So it seems an implementation would just fall back to the + Django vanilla equivalent. + ++ annotate(): The current '_get_real_instances' would need minor + enhancement. + ++ defer() and only(): Full support, including slight polymorphism + enhancements, seems to be straighforward + (depends on '_get_real_instances'). + ++ extra(): Does not really work with the current implementation of + '_get_real_instances'. It's unclear if it should be supported. + ++ select_related(): This would probably need Django core support + for traversing the reverse model inheritance OneToOne relations + with Django's select_related(), e.g.: + *select_related('modela__modelb__foreignkeyfield')*. + Also needs more thought/investigation. + ++ distinct() needs more thought and investigation as well + ++ values() & values_list(): Implementation seems to be mostly + straighforward + + +Restrictions & Caveats +---------------------- + ++ Diamond shaped inheritance: There seems to be a general problem + with diamond shaped multiple model inheritance with Django models + (tested with V1.1). + An example is here: http://code.djangoproject.com/ticket/10808. + This problem is aggravated when trying to enhance models.Model + by subclassing it instead of modifying Django core (as we do here + with PolymorphicModel). + ++ The name and appname of the leaf model is stored in the base model + (the base model directly inheriting from PolymorphicModel). + If a model or an app is renamed, then these fields need to be + corrected too, if the db content should stay usable after the rename. + Aside from this, these two fields should probably be combined into + one field (more db/sql efficiency) + ++ For all objects that are not instances of the base class type, but + instances of a subclass, the base class fields are currently + transferred twice from the database (an artefact of the current + implementation's simplicity). + ++ __getattribute__ hack: For base model inheritance back relation + fields (like basemodel_ptr), as well as implicit model inheritance + forward relation fields, Django internally tries to use our + polymorphic manager/queryset in some places, which of course it + should not. Currently this is solved with hackish __getattribute__ + in PolymorphicModel. A minor patch to Django core would probably + get rid of that. + +In General +---------- + +It is important to consider that this code is still very new and experimental. + +It has, however, been integrated into one larger system where all seems to work flawlessly +so far. A small number of people tested this code for their purposes and reported that it +works well for them. + +Right now this module is suitable only for the more enterprising early adopters. + + +Links +===== + +- http://code.djangoproject.com/wiki/ModelInheritance +- http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html +- http://www.djangosnippets.org/snippets/1031/ +- http://www.djangosnippets.org/snippets/1034/ +- http://groups.google.com/group/django-developers/browse_frm/thread/7d40ad373ebfa912/a20fabc661b7035d?lnk=gst&q=model+inheritance+CORBA#a20fabc661b7035d +- http://groups.google.com/group/django-developers/browse_thread/thread/9bc2aaec0796f4e0/0b92971ffc0aa6f8?lnk=gst&q=inheritance#0b92971ffc0aa6f8 +- http://groups.google.com/group/django-developers/browse_thread/thread/3947c594100c4adb/d8c0af3dacad412d?lnk=gst&q=inheritance#d8c0af3dacad412d +- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/b76c9d8c89a5574f +- http://peterbraden.co.uk/article/django-inheritance +- http://www.hopelessgeek.com/2009/11/25/a-hack-for-multi-table-inheritance-in-django +- http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982 +- http://stackoverflow.com/questions/1581024/django-inheritance-how-to-have-one-method-for-all-subclasses +- http://groups.google.com/group/django-users/browse_thread/thread/cbdaf2273781ccab/e676a537d735d9ef?lnk=gst&q=polymorphic#e676a537d735d9ef +- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/bc18c18b2e83881e?lnk=gst&q=model+inheritance#bc18c18b2e83881e +- http://code.djangoproject.com/ticket/10808 +- http://code.djangoproject.com/ticket/7270 + diff --git a/LICENSE b/LICENSE index c0f4712..1cb7761 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -Copyright (c) 2009 by Bert Constantin and individual contributors. +Copyright (c) 2009 or later by the individual contributors. +Please see the AUTHORS file. + All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.rst b/README.rst index 4c29dbc..cc4be0f 100644 --- a/README.rst +++ b/README.rst @@ -1,474 +1,39 @@ - =============================== Fully Polymorphic Django Models =============================== -Overview -======== +What it Does +============ -'polymorphic.py' is an add-on module that adds fully automatic -polymorphism to the Django model inheritance system. +If ``ArtProject`` and ``ResearchProject`` inherit from the model ``Project``:: -The effect is: For enabled models, objects retrieved from the -database are always delivered just as they were created and saved, -with the same type/class and fields - regardless how they are -retrieved. The resulting querysets are polymorphic, i.e. may deliver + >>> Project.objects.all() + . + [ , + , + ] + +In general, objects retrieved from the database are always delivered just as +they were created and saved, with the same type/class and fields. It doesn't +matter how you access these objects: be it through the model's own +managers/querysets, ForeignKey, ManyToMany or OneToOne fields. + +The resulting querysets are polymorphic, and may deliver objects of several different types in a single query result. -Please see the concrete examples below as they demonstrate this best. +``django_polymorphic`` consists of just one add-on module, ``polymorphic.py``, +that adds this kind of automatic polymorphism to Django's model +inheritance system (for models that request this behaviour). -Please note that this module is very experimental code. See below for -current restrictions, caveats, and performance implications. +Please see additional examples and the documentation here: + http://bserve.webhop.org/wiki/django_polymorphic -Installation / Testing -====================== +or in the DOCS.rst file in this repository. -Requirements ------------- - -Django 1.1 and Python 2.5+. This code has been tested -on Django 1.1.1 with Python 2.5.4 and 2.6.4 on Linux. - -Testing -------- - -The repository (or tar file) contains a complete Django project -that may be used for testing and experimentation. - -To run the included test suite, execute:: - - ./manage test poly - -'management/commands/polycmd.py' can be used for experiments -- modify this file to your liking, then run:: - - ./manage syncdb # db is created in /tmp/... (settings.py) - ./manage polycmd - -Using polymorphic models in your own projects ---------------------------------------------- - -Copy polymorphic.py (from the 'poly' dir) into a directory from where -you can import it, like your app directory (where your models.py and -views.py files live). - - -Defining Polymorphic Models -=========================== - -To make models polymorphic, use PolymorphicModel instead of Django's -models.Model as the superclass of your base model. All models -inheriting from your base class will be polymorphic as well:: - - from polymorphic import PolymorphicModel - - class ModelA(PolymorphicModel): - field1 = models.CharField(max_length=10) - - class ModelB(ModelA): - field2 = models.CharField(max_length=10) - - class ModelC(ModelB): - field3 = models.CharField(max_length=10) - - -Using Polymorphic Models -======================== - -Most of Django's standard ORM functionality is available -and works as expected: - -Create some objects -------------------- - - >>> ModelA.objects.create(field1='A1') - >>> ModelB.objects.create(field1='B1', field2='B2') - >>> ModelC.objects.create(field1='C1', field2='C2', field3='C3') - -Query results are polymorphic ------------------------------ - - >>> ModelA.objects.all() - . - [ , - , - ] - -Filtering for classes (equivalent to python's isinstance() ): -------------------------------------------------------------- - - >>> ModelA.objects.instance_of(ModelB) - . - [ , - ] - - In general, including or excluding parts of the inheritance tree:: - - ModelA.objects.instance_of(ModelB [, ModelC ...]) - ModelA.objects.not_instance_of(ModelB [, ModelC ...]) - -Polymorphic filtering (for fields in derived classes) ------------------------------------------------------ - - For example, cherrypicking objects from multiple derived classes - anywhere in the inheritance tree, using Q objects (with the - slightly enhanced syntax: exact model name + three _ + field name): - - >>> ModelA.objects.filter( Q( ModelB___field2 = 'B2' ) | Q( ModelC___field3 = 'C3' ) ) - . - [ , - ] - -Combining Querysets of different types/models ---------------------------------------------- - - Querysets may now be regarded as object containers that allow the - aggregation of different object types - very similar to python - lists (as long as the objects are accessed through the manager of - a common base class): - - >>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) - . - [ , - ] - -Using Third Party Models (without modifying them) -------------------------------------------------- - - Third party models can be used as polymorphic models without - restrictions by subclassing them. E.g. using a third party - model as the root of a polymorphic inheritance tree:: - - from thirdparty import ThirdPartyModel - - class MyThirdPartyModel(PolymorhpicModel, ThirdPartyModel): - pass # or add fields - - Or instead integrating the third party model anywhere into an - existing polymorphic inheritance tree:: - - class MyModel(SomePolymorphicModel): - my_field = models.CharField(max_length=10) - - class MyModelWithThirdParty(MyModel, ThirdPartyModel): - pass # or add fields - -ManyToManyField, ForeignKey, OneToOneField ------------------------------------------- - - Relationship fields referring to polymorphic models work as - expected: like polymorphic querysets they now always return the - referred objects with the same type/class these were created and - saved as. - - E.g., if in your model you define:: - - field1 = OneToOneField(ModelA) - - then field1 may now also refer to objects of type ModelB or ModelC. - - A ManyToManyField example:: - - # The model holding the relation may be any kind of model, polymorphic or not - class RelatingModel(models.Model): - many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model - - >>> o=RelatingModel.objects.create() - >>> o.many2many.add(ModelA.objects.get(id=1)) - >>> o.many2many.add(ModelB.objects.get(id=2)) - >>> o.many2many.add(ModelC.objects.get(id=3)) - - >>> o.many2many.all() - [ , - , - ] - -Non-Polymorphic Queries ------------------------ - - >>> ModelA.base_objects.all() - . - [ , - , - ] - - Each polymorphic model has 'base_objects' defined as a normal - Django manager. Of course, arbitrary custom managers may be - added to the models as well. - - -Custom Managers, Querysets & Inheritance -======================================== - -Using a Custom Manager ----------------------- - -For creating a custom polymorphic manager class, derive your manager -from PolymorphicManager instead of models.Manager. In your model -class, explicitly add the default manager first, and then your -custom manager:: - - class MyOrderedManager(PolymorphicManager): - def get_query_set(self): - return super(MyOrderedManager,self).get_query_set().order_by('some_field') - - class MyModel(PolymorphicModel): - objects = PolymorphicManager() # add the default polymorphic manager first - ordered_objects = MyOrderedManager() # then add your own manager - -The first manager defined ('objects' in the example) is used by -Django as automatic manager for several purposes, including accessing -related objects. It must not filter objects and it's safest to use -the plain PolymorphicManager here. - -Manager Inheritance -------------------- - -The current polymorphic models implementation unconditionally -inherits all managers from its base models (but only the -polymorphic base models). - -An example (inheriting from MyModel above):: - - class MyModel2(MyModel): - pass - - # Managers inherited from MyModel, delivering MyModel2 objects (including MyModel2 subclass objects) - >>> MyModel2.objects.all() - >>> MyModel2.ordered_objects.all() - -Manager inheritance is a somewhat complex topic that needs more -thought and more actual experience with real-world use-cases. - -Using a Custom Queryset Class ------------------------------ - -The PolymorphicManager class accepts one initialization argument, -which is the queryset class the manager should use. A custom -custom queryset class can be defined and used like this:: - - class MyQuerySet(PolymorphicQuerySet): - def my_queryset_method(...): - ... - - class MyModel(PolymorphicModel): - my_objects=PolymorphicManager(MyQuerySet) - ... - - -Performance Considerations -========================== - -The current implementation is pretty simple and does not use any -custom sql - it is purely based on the Django ORM. Right now the -query :: - - result_objects = list( ModelA.objects.filter(...) ) - -performs one sql query to retrieve ModelA objects and one additional -query for each unique derived class occurring in result_objects. -The best case for retrieving 100 objects is 1 db query if all are -class ModelA. If 50 objects are ModelA and 50 are ModelB, then two -queries are executed. If result_objects contains only the base model -type (ModelA), the polymorphic models are just as efficient as plain -Django models (in terms of executed queries). The pathological worst -case is 101 db queries if result_objects contains 100 different -object types (with all of them subclasses of ModelA). - -Performance ist relative: when Django users create their own -polymorphic ad-hoc solution (without a module like polymorphic.py), -they will tend to use a variation of :: - - result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ] - -which of course has really bad performance. Relative to this, the -performance of the current polymorphic.py is rather good. -It's well possible that the current implementation is already -efficient enough for the majority of use cases. - -Chunking: The implementation always requests objects in chunks of -size Polymorphic_QuerySet_objects_per_request. This limits the -complexity/duration for each query, including the pathological cases. - - -Possible Optimizations -====================== - -PolymorphicQuerySet can be optimized to require only one sql query -for the queryset evaluation and retrieval of all objects. - -Basically, what ist needed is a possibility to pull in the fields -from all relevant sub-models with one sql query. In order to do this -on top of the Django ORM, some kind of enhhancement would be needed. - -At first, it looks like a reverse select_related for OneToOne -relations might offer a solution (see http://code.djangoproject.com/ticket/7270):: - - ModelA.objects.filter(...).select_related('modelb','modelb__modelc') - -This approach has a number of problems, but nevertheless would -already execute the correct sql query and receive all the model -fields required from the db. - -A kind of "select_related for values" might be a better solution:: - - ModelA.objects.filter(...).values_related( - [ base field name list ], { - 'modelb' : [field name list ], - 'modelb__modelc' : [ field name list ] - }) - -Django's lower level db API in QuerySet.query (see BaseQuery in -django.db.models.sql.query) might still allow other, better or easier -ways to implement the needed functionality. - -SQL Complexity --------------- - -Regardless how these queries would be created, their complexity is -the same in any case: - -With only one sql query, one sql join for each possible subclass -would be needed (BaseModel.__subclasses__(), recursively). -With two sql queries, the number of joins could be reduced to the -number of actuallly occurring subclasses in the result. A final -implementation might want to use one query only if the number of -possible subclasses (and therefore joins) is not too large, and -two queries otherwise (using the first query to determine the -actually occurring subclasses, reducing the number of joins for -the second). - -A relatively large number of joins may be needed in both cases, -which raises concerns regarding the efficiency of these database -queries. It is currently unclear however, how many model classes -will actually be involved in typical use cases - the total number -of classes in the inheritance tree as well as the number of distinct -classes in query results. It may well turn out that the increased -number of joins is no problem for the DBMS in all realistic use -cases. Alternatively, if the sql query execution time is -significantly longer even in common use cases, this may still be -acceptable in exchange for the added functionality. - -Let's not forget that all of the above is just about optimizations. -The current simplistic implementation already works well - perhaps -well enough for the majority of applications. - - -Restrictions, Caveats, Loose Ends -================================= - -Unsupported Queryset Methods ----------------------------- - -+ aggregate() probably makes only sense in a purely non-OO/relational - way. So it seems an implementation would just fall back to the - Django vanilla equivalent. - -+ annotate(): The current '_get_real_instances' would need minor - enhancement. - -+ defer() and only(): Full support, including slight polymorphism - enhancements, seems to be straighforward - (depends on '_get_real_instances'). - -+ extra(): Does not really work with the current implementation of - '_get_real_instances'. It's unclear if it should be supported. - -+ select_related(): This would probably need Django core support - for traversing the reverse model inheritance OneToOne relations - with Django's select_related(), e.g.: - *select_related('modela__modelb__foreignkeyfield')*. - Also needs more thought/investigation. - -+ distinct() needs more thought and investigation as well - -+ values() & values_list(): Implementation seems to be mostly - straighforward - - -Restrictions & Caveats ----------------------- - -+ Diamond shaped inheritance: There seems to be a general problem - with diamond shaped multiple model inheritance with Django models - (tested with V1.1). - An example is here: http://code.djangoproject.com/ticket/10808. - This problem is aggravated when trying to enhance models.Model - by subclassing it instead of modifying Django core (as we do here - with PolymorphicModel). - -+ The name and appname of the leaf model is stored in the base model - (the base model directly inheriting from PolymorphicModel). - If a model or an app is renamed, then these fields need to be - corrected too, if the db content should stay usable after the rename. - Aside from this, these two fields should probably be combined into - one field (more db/sql efficiency) - -+ For all objects that are not instances of the base class type, but - instances of a subclass, the base class fields are currently - transferred twice from the database (an artefact of the current - implementation's simplicity). - -+ __getattribute__ hack: For base model inheritance back relation - fields (like basemodel_ptr), as well as implicit model inheritance - forward relation fields, Django internally tries to use our - polymorphic manager/queryset in some places, which of course it - should not. Currently this is solved with hackish __getattribute__ - in PolymorphicModel. A minor patch to Django core would probably - get rid of that. - -+ "instance_of" and "not_instance_of" may need some optimization. - - -More Investigation Needed -------------------------- - -There are a number of subtleties that have not yet been fully evaluated -or resolved, for example (among others) the exact implications of -'use_for_related_fields' in the polymorphic manager. - -There may also well be larger issues of conceptual or technical nature -that might basically be showstoppers (but have not yet been found). - - -In General ----------- - -It is important to consider that this code is very experimental -and very insufficiently tested. A number of test cases are included -but they need to be expanded. This implementation is currently more -a tool for exploring the concept of polymorphism within the Django -framework. After careful testing and consideration it may perhaps be -useful for actual projects, but it might be too early for this -right now. - - -Links -===== - -- http://code.djangoproject.com/wiki/ModelInheritance -- http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html -- http://www.djangosnippets.org/snippets/1031/ -- http://www.djangosnippets.org/snippets/1034/ -- http://groups.google.com/group/django-developers/browse_frm/thread/7d40ad373ebfa912/a20fabc661b7035d?lnk=gst&q=model+inheritance+CORBA#a20fabc661b7035d -- http://groups.google.com/group/django-developers/browse_thread/thread/9bc2aaec0796f4e0/0b92971ffc0aa6f8?lnk=gst&q=inheritance#0b92971ffc0aa6f8 -- http://groups.google.com/group/django-developers/browse_thread/thread/3947c594100c4adb/d8c0af3dacad412d?lnk=gst&q=inheritance#d8c0af3dacad412d -- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/b76c9d8c89a5574f -- http://peterbraden.co.uk/article/django-inheritance -- http://www.hopelessgeek.com/2009/11/25/a-hack-for-multi-table-inheritance-in-django -- http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982 -- http://stackoverflow.com/questions/1581024/django-inheritance-how-to-have-one-method-for-all-subclasses -- http://groups.google.com/group/django-users/browse_thread/thread/cbdaf2273781ccab/e676a537d735d9ef?lnk=gst&q=polymorphic#e676a537d735d9ef -- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/bc18c18b2e83881e?lnk=gst&q=model+inheritance#bc18c18b2e83881e -- http://code.djangoproject.com/ticket/10808 -- http://code.djangoproject.com/ticket/7270 - - -Copyright -========== - -| This code and affiliated files are (C) 2010 Bert Constantin and individual contributors. -| Please see LICENSE for more information. +Status +------ +This module is still very experimental. Please see the docs for current restrictions, +caveats, and performance implications. diff --git a/poly/management/commands/polycmd.py b/poly/management/commands/polycmd.py index 977d78a..45a4f6a 100644 --- a/poly/management/commands/polycmd.py +++ b/poly/management/commands/polycmd.py @@ -20,10 +20,20 @@ class Command(NoArgsCommand): def handle_noargs(self, **options): print "polycmd" + + Project.objects.all().delete() + o=Project.objects.create(topic="John's gathering") + o=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner") + o=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") + + print Project.objects.all() + print + ModelA.objects.all().delete() - o=ModelA.objects.create(field1='A1') o=ModelB.objects.create(field1='B1', field2='B2') o=ModelC.objects.create(field1='C1', field2='C2', field3='C3') print ModelA.objects.all() + + diff --git a/poly/models.py b/poly/models.py index 07f8323..c91f17a 100644 --- a/poly/models.py +++ b/poly/models.py @@ -1,20 +1,6 @@ from django.db import models -from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet - -class ShowFieldContent(object): - def __repr__(self): - out = 'id %d, ' % (self.id); last = self._meta.fields[-1] - for f in self._meta.fields: - if f.name in [ 'id', 'p_classname', 'p_appname' ] or 'ptr' in f.name: continue - out += f.name + ' (' + type(f).__name__ + ')' - if isinstance(f, (models.ForeignKey)): - o = getattr(self, f.name) - out += ': "' + ('None' if o == None else o.__class__.__name__) + '"' - else: - out += ': "' + getattr(self, f.name) + '"' - if f != last: out += ', ' - return '<' + self.__class__.__name__ + ': ' + out + '>' +from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet, ShowFields, ShowFieldsAndTypes class PlainA(models.Model): field1 = models.CharField(max_length=10) @@ -39,7 +25,7 @@ class ModelY(Base): class Enhance_Plain(models.Model): field_p = models.CharField(max_length=10) -class Enhance_Base(ShowFieldContent, PolymorphicModel): +class Enhance_Base(ShowFieldsAndTypes, PolymorphicModel): field_b = models.CharField(max_length=10) class Enhance_Inherit(Enhance_Base, Enhance_Plain): field_i = models.CharField(max_length=10) @@ -54,7 +40,7 @@ class DiamondY(DiamondBase): class DiamondXY(DiamondX, DiamondY): pass -class RelationBase(ShowFieldContent, PolymorphicModel): +class RelationBase(ShowFieldsAndTypes, PolymorphicModel): field_base = models.CharField(max_length=10) fk = models.ForeignKey('self', null=True) m2m = models.ManyToManyField('self') @@ -71,7 +57,7 @@ class RelatingModel(models.Model): class MyManager(PolymorphicManager): def get_query_set(self): return super(MyManager, self).get_query_set().order_by('-field1') -class ModelWithMyManager(ShowFieldContent, ModelA): +class ModelWithMyManager(ShowFieldsAndTypes, ModelA): objects = MyManager() field4 = models.CharField(max_length=10) @@ -91,6 +77,13 @@ class MgrInheritA(models.Model): class MgrInheritB(MgrInheritA): mgrB = models.Manager() field2 = models.CharField(max_length=10) -class MgrInheritC(ShowFieldContent, MgrInheritB): +class MgrInheritC(ShowFieldsAndTypes, MgrInheritB): pass +class Project(ShowFields,PolymorphicModel): + topic = models.CharField(max_length=30) +class ArtProject(Project): + artist = models.CharField(max_length=30) +class ResearchProject(Project): + supervisor = models.CharField(max_length=30) + diff --git a/poly/polymorphic.py b/poly/polymorphic.py index 28e33bf..2adca17 100644 --- a/poly/polymorphic.py +++ b/poly/polymorphic.py @@ -1,478 +1,15 @@ # -*- coding: utf-8 -*- """ -=============================== Fully Polymorphic Django Models -=============================== +Please see the examples and the documentation here: -Overview -======== +http://bserve.webhop.org/wiki/django_polymorphic -'polymorphic.py' is an add-on module that adds fully automatic -polymorphism to the Django model inheritance system. - -The effect is: For enabled models, objects retrieved from the -database are always delivered just as they were created and saved, -with the same type/class and fields - regardless how they are -retrieved. The resulting querysets are polymorphic, i.e. may deliver -objects of several different types in a single query result. - -Please see the concrete examples below as they demonstrate this best. - -Please note that this module is very experimental code. See below for -current restrictions, caveats, and performance implications. - - -Installation / Testing -====================== - -Requirements ------------- - -Django 1.1 and Python 2.5+. This code has been tested -on Django 1.1.1 with Python 2.5.4 and 2.6.4 on Linux. - -Testing -------- - -The repository (or tar file) contains a complete Django project -that may be used for testing and experimentation. - -To run the included test suite, execute:: - - ./manage test poly - -'management/commands/polycmd.py' can be used for experiments -- modify this file to your liking, then run:: - - ./manage syncdb # db is created in /tmp/... (settings.py) - ./manage polycmd - -Using polymorphic models in your own projects ---------------------------------------------- - -Copy polymorphic.py (from the 'poly' dir) into a directory from where -you can import it, like your app directory (where your models.py and -views.py files live). - - -Defining Polymorphic Models -=========================== - -To make models polymorphic, use PolymorphicModel instead of Django's -models.Model as the superclass of your base model. All models -inheriting from your base class will be polymorphic as well:: - - from polymorphic import PolymorphicModel - - class ModelA(PolymorphicModel): - field1 = models.CharField(max_length=10) - - class ModelB(ModelA): - field2 = models.CharField(max_length=10) - - class ModelC(ModelB): - field3 = models.CharField(max_length=10) - - -Using Polymorphic Models -======================== - -Most of Django's standard ORM functionality is available -and works as expected: - -Create some objects -------------------- - - >>> ModelA.objects.create(field1='A1') - >>> ModelB.objects.create(field1='B1', field2='B2') - >>> ModelC.objects.create(field1='C1', field2='C2', field3='C3') - -Query results are polymorphic ------------------------------ - - >>> ModelA.objects.all() - . - [ , - , - ] - -Filtering for classes (equivalent to python's isinstance() ): -------------------------------------------------------------- - - >>> ModelA.objects.instance_of(ModelB) - . - [ , - ] - - In general, including or excluding parts of the inheritance tree:: - - ModelA.objects.instance_of(ModelB [, ModelC ...]) - ModelA.objects.not_instance_of(ModelB [, ModelC ...]) - -Polymorphic filtering (for fields in derived classes) ------------------------------------------------------ - - For example, cherrypicking objects from multiple derived classes - anywhere in the inheritance tree, using Q objects (with the - slightly enhanced syntax: exact model name + three _ + field name): - - >>> ModelA.objects.filter( Q( ModelB___field2 = 'B2' ) | Q( ModelC___field3 = 'C3' ) ) - . - [ , - ] - -Combining Querysets of different types/models ---------------------------------------------- - - Querysets may now be regarded as object containers that allow the - aggregation of different object types - very similar to python - lists (as long as the objects are accessed through the manager of - a common base class): - - >>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) - . - [ , - ] - -Using Third Party Models (without modifying them) -------------------------------------------------- - - Third party models can be used as polymorphic models without - restrictions by subclassing them. E.g. using a third party - model as the root of a polymorphic inheritance tree:: - - from thirdparty import ThirdPartyModel - - class MyThirdPartyModel(PolymorhpicModel, ThirdPartyModel): - pass # or add fields - - Or instead integrating the third party model anywhere into an - existing polymorphic inheritance tree:: - - class MyModel(SomePolymorphicModel): - my_field = models.CharField(max_length=10) - - class MyModelWithThirdParty(MyModel, ThirdPartyModel): - pass # or add fields - -ManyToManyField, ForeignKey, OneToOneField ------------------------------------------- - - Relationship fields referring to polymorphic models work as - expected: like polymorphic querysets they now always return the - referred objects with the same type/class these were created and - saved as. - - E.g., if in your model you define:: - - field1 = OneToOneField(ModelA) - - then field1 may now also refer to objects of type ModelB or ModelC. - - A ManyToManyField example:: - - # The model holding the relation may be any kind of model, polymorphic or not - class RelatingModel(models.Model): - many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model - - >>> o=RelatingModel.objects.create() - >>> o.many2many.add(ModelA.objects.get(id=1)) - >>> o.many2many.add(ModelB.objects.get(id=2)) - >>> o.many2many.add(ModelC.objects.get(id=3)) - - >>> o.many2many.all() - [ , - , - ] - -Non-Polymorphic Queries ------------------------ - - >>> ModelA.base_objects.all() - . - [ , - , - ] - - Each polymorphic model has 'base_objects' defined as a normal - Django manager. Of course, arbitrary custom managers may be - added to the models as well. - - -Custom Managers, Querysets & Inheritance -======================================== - -Using a Custom Manager ----------------------- - -For creating a custom polymorphic manager class, derive your manager -from PolymorphicManager instead of models.Manager. In your model -class, explicitly add the default manager first, and then your -custom manager:: - - class MyOrderedManager(PolymorphicManager): - def get_query_set(self): - return super(MyOrderedManager,self).get_query_set().order_by('some_field') - - class MyModel(PolymorphicModel): - objects = PolymorphicManager() # add the default polymorphic manager first - ordered_objects = MyOrderedManager() # then add your own manager - -The first manager defined ('objects' in the example) is used by -Django as automatic manager for several purposes, including accessing -related objects. It must not filter objects and it's safest to use -the plain PolymorphicManager here. - -Manager Inheritance -------------------- - -The current polymorphic models implementation unconditionally -inherits all managers from its base models (but only the -polymorphic base models). - -An example (inheriting from MyModel above):: - - class MyModel2(MyModel): - pass - - # Managers inherited from MyModel, delivering MyModel2 objects (including MyModel2 subclass objects) - >>> MyModel2.objects.all() - >>> MyModel2.ordered_objects.all() - -Manager inheritance is a somewhat complex topic that needs more -thought and more actual experience with real-world use-cases. - -Using a Custom Queryset Class ------------------------------ - -The PolymorphicManager class accepts one initialization argument, -which is the queryset class the manager should use. A custom -custom queryset class can be defined and used like this:: - - class MyQuerySet(PolymorphicQuerySet): - def my_queryset_method(...): - ... - - class MyModel(PolymorphicModel): - my_objects=PolymorphicManager(MyQuerySet) - ... - - -Performance Considerations -========================== - -The current implementation is pretty simple and does not use any -custom sql - it is purely based on the Django ORM. Right now the -query :: - - result_objects = list( ModelA.objects.filter(...) ) - -performs one sql query to retrieve ModelA objects and one additional -query for each unique derived class occurring in result_objects. -The best case for retrieving 100 objects is 1 db query if all are -class ModelA. If 50 objects are ModelA and 50 are ModelB, then two -queries are executed. If result_objects contains only the base model -type (ModelA), the polymorphic models are just as efficient as plain -Django models (in terms of executed queries). The pathological worst -case is 101 db queries if result_objects contains 100 different -object types (with all of them subclasses of ModelA). - -Performance ist relative: when Django users create their own -polymorphic ad-hoc solution (without a module like polymorphic.py), -they will tend to use a variation of :: - - result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ] - -which of course has really bad performance. Relative to this, the -performance of the current polymorphic.py is rather good. -It's well possible that the current implementation is already -efficient enough for the majority of use cases. - -Chunking: The implementation always requests objects in chunks of -size Polymorphic_QuerySet_objects_per_request. This limits the -complexity/duration for each query, including the pathological cases. - - -Possible Optimizations -====================== - -PolymorphicQuerySet can be optimized to require only one sql query -for the queryset evaluation and retrieval of all objects. - -Basically, what ist needed is a possibility to pull in the fields -from all relevant sub-models with one sql query. In order to do this -on top of the Django ORM, some kind of enhhancement would be needed. - -At first, it looks like a reverse select_related for OneToOne -relations might offer a solution (see http://code.djangoproject.com/ticket/7270):: - - ModelA.objects.filter(...).select_related('modelb','modelb__modelc') - -This approach has a number of problems, but nevertheless would -already execute the correct sql query and receive all the model -fields required from the db. - -A kind of "select_related for values" might be a better solution:: - - ModelA.objects.filter(...).values_related( - [ base field name list ], { - 'modelb' : [field name list ], - 'modelb__modelc' : [ field name list ] - }) - -Django's lower level db API in QuerySet.query (see BaseQuery in -django.db.models.sql.query) might still allow other, better or easier -ways to implement the needed functionality. - -SQL Complexity --------------- - -Regardless how these queries would be created, their complexity is -the same in any case: - -With only one sql query, one sql join for each possible subclass -would be needed (BaseModel.__subclasses__(), recursively). -With two sql queries, the number of joins could be reduced to the -number of actuallly occurring subclasses in the result. A final -implementation might want to use one query only if the number of -possible subclasses (and therefore joins) is not too large, and -two queries otherwise (using the first query to determine the -actually occurring subclasses, reducing the number of joins for -the second). - -A relatively large number of joins may be needed in both cases, -which raises concerns regarding the efficiency of these database -queries. It is currently unclear however, how many model classes -will actually be involved in typical use cases - the total number -of classes in the inheritance tree as well as the number of distinct -classes in query results. It may well turn out that the increased -number of joins is no problem for the DBMS in all realistic use -cases. Alternatively, if the sql query execution time is -significantly longer even in common use cases, this may still be -acceptable in exchange for the added functionality. - -Let's not forget that all of the above is just about optimizations. -The current simplistic implementation already works well - perhaps -well enough for the majority of applications. - - -Restrictions, Caveats, Loose Ends -================================= - -Unsupported Queryset Methods ----------------------------- - -+ aggregate() probably makes only sense in a purely non-OO/relational - way. So it seems an implementation would just fall back to the - Django vanilla equivalent. - -+ annotate(): The current '_get_real_instances' would need minor - enhancement. - -+ defer() and only(): Full support, including slight polymorphism - enhancements, seems to be straighforward - (depends on '_get_real_instances'). - -+ extra(): Does not really work with the current implementation of - '_get_real_instances'. It's unclear if it should be supported. - -+ select_related(): This would probably need Django core support - for traversing the reverse model inheritance OneToOne relations - with Django's select_related(), e.g.: - *select_related('modela__modelb__foreignkeyfield')*. - Also needs more thought/investigation. - -+ distinct() needs more thought and investigation as well - -+ values() & values_list(): Implementation seems to be mostly - straighforward - - -Restrictions & Caveats ----------------------- - -+ Diamond shaped inheritance: There seems to be a general problem - with diamond shaped multiple model inheritance with Django models - (tested with V1.1). - An example is here: http://code.djangoproject.com/ticket/10808. - This problem is aggravated when trying to enhance models.Model - by subclassing it instead of modifying Django core (as we do here - with PolymorphicModel). - -+ The name and appname of the leaf model is stored in the base model - (the base model directly inheriting from PolymorphicModel). - If a model or an app is renamed, then these fields need to be - corrected too, if the db content should stay usable after the rename. - Aside from this, these two fields should probably be combined into - one field (more db/sql efficiency) - -+ For all objects that are not instances of the base class type, but - instances of a subclass, the base class fields are currently - transferred twice from the database (an artefact of the current - implementation's simplicity). - -+ __getattribute__ hack: For base model inheritance back relation - fields (like basemodel_ptr), as well as implicit model inheritance - forward relation fields, Django internally tries to use our - polymorphic manager/queryset in some places, which of course it - should not. Currently this is solved with hackish __getattribute__ - in PolymorphicModel. A minor patch to Django core would probably - get rid of that. - -+ "instance_of" and "not_instance_of" may need some optimization. - - -More Investigation Needed -------------------------- - -There are a number of subtleties that have not yet been fully evaluated -or resolved, for example (among others) the exact implications of -'use_for_related_fields' in the polymorphic manager. - -There may also well be larger issues of conceptual or technical nature -that might basically be showstoppers (but have not yet been found). - - -In General ----------- - -It is important to consider that this code is very experimental -and very insufficiently tested. A number of test cases are included -but they need to be expanded. This implementation is currently more -a tool for exploring the concept of polymorphism within the Django -framework. After careful testing and consideration it may perhaps be -useful for actual projects, but it might be too early for this -right now. - - -Links -===== - -- http://code.djangoproject.com/wiki/ModelInheritance -- http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html -- http://www.djangosnippets.org/snippets/1031/ -- http://www.djangosnippets.org/snippets/1034/ -- http://groups.google.com/group/django-developers/browse_frm/thread/7d40ad373ebfa912/a20fabc661b7035d?lnk=gst&q=model+inheritance+CORBA#a20fabc661b7035d -- http://groups.google.com/group/django-developers/browse_thread/thread/9bc2aaec0796f4e0/0b92971ffc0aa6f8?lnk=gst&q=inheritance#0b92971ffc0aa6f8 -- http://groups.google.com/group/django-developers/browse_thread/thread/3947c594100c4adb/d8c0af3dacad412d?lnk=gst&q=inheritance#d8c0af3dacad412d -- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/b76c9d8c89a5574f -- http://peterbraden.co.uk/article/django-inheritance -- http://www.hopelessgeek.com/2009/11/25/a-hack-for-multi-table-inheritance-in-django -- http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982 -- http://stackoverflow.com/questions/1581024/django-inheritance-how-to-have-one-method-for-all-subclasses -- http://groups.google.com/group/django-users/browse_thread/thread/cbdaf2273781ccab/e676a537d735d9ef?lnk=gst&q=polymorphic#e676a537d735d9ef -- http://groups.google.com/group/django-users/browse_thread/thread/52f72cffebb705e/bc18c18b2e83881e?lnk=gst&q=model+inheritance#bc18c18b2e83881e -- http://code.djangoproject.com/ticket/10808 -- http://code.djangoproject.com/ticket/7270 - - -Copyright -========== - -| This code and affiliated files are (C) 2010 Bert Constantin and individual contributors. -| Please see LICENSE for more information. +or in the included README.rst and DOCS.rst files +Copyright: This code and affiliated files are (C) 2010 Bert Constantin +and individual contributors. Please see LICENSE for more information. """ from django.db import models @@ -497,7 +34,7 @@ class PolymorphicManager(models.Manager): Usually not explicitly needed, except if a custom manager or a custom queryset class is to be used. - For more information, please see module docstring. + For more information, please see documentation. """ use_for_related_fields = True @@ -557,11 +94,10 @@ class PolymorphicQuerySet(QuerySet): base class query. The class of all of them is self.model (our base model). Some, many or all of these objects were not created and stored as - class self.model, but as a class derived from self.model. So, these - objects in base_result_objects have not the class they were created as - and do not contain all fields of their real class. + class self.model, but as a class derived from self.model. We want to fetch + their missing fields and return them as they saved. - We identify them by looking at o.p_classname & o.p_appname, which specify + We identify these objects by looking at o.p_classname & o.p_appname, which specify the real class of these objects (the class at the time they were saved). We replace them by the correct objects, which we fetch from the db. @@ -958,7 +494,7 @@ class PolymorphicModel(models.Model): Abstract base class that provides full polymorphism to any model directly or indirectly derived from it - For usage instructions & examples please see module docstring. + For usage instructions & examples please see documentation. PolymorphicModel declares two fields for internal use (p_classname and p_appname) and provides a polymorphic manager as the @@ -990,8 +526,8 @@ class PolymorphicModel(models.Model): def pre_save_polymorphic(self): """ - Normally not needed - this function may be called manually in special use-cases. - + 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 and app name into p_classname and p_appname. When the object later is retrieved by PolymorphicQuerySet, it uses these fields to figure out the real type of this object @@ -1010,18 +546,18 @@ class PolymorphicModel(models.Model): return super(PolymorphicModel, self).save(*args, **kwargs) def get_real_instance_class(self): - """Normally not needed - only if a non-polymorphic manager - (like base_objects) has been used to retrieve objects. - Then these objects have the wrong class (the base class). - This function returns the real/correct class for this object.""" + """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.""" return models.get_model(self.p_appname, self.p_classname) def get_real_instance(self): - """Normally not needed - only if a non-polymorphic manager - (like base_objects) has been used to retrieve objects. - Then these objects have the wrong class (the base class). - This function returns the real object with the correct class - and content. Each method call executes one db query.""" + """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 + retrieve the complete object with i's real class/type and all fields. + Each method call executes one db query.""" if self.p_classname == self.__class__.__name__ and self.p_appname == self.__class__._meta.app_label: return self return self.get_real_instance_class().objects.get(id=self.id) @@ -1067,7 +603,7 @@ class PolymorphicModel(models.Model): super(PolymorphicModel, self).__init__(*args, **kwargs) def __repr__(self): - "output object descriptions as seen in module docstring" + "output object descriptions as seen in documentation" out = self.__class__.__name__ + ': id %d, ' % (self.id or - 1); last = self._meta.fields[-1] for f in self._meta.fields: if f.name in [ 'id', 'p_classname', 'p_appname' ] or 'ptr' in f.name: continue @@ -1075,3 +611,36 @@ class PolymorphicModel(models.Model): if f != last: out += ', ' return '<' + out + '>' + +class ShowFields(object): + """ mixin that shows the object's class, it's fields and field contents """ + def __repr__(self): + out = 'id %d, ' % (self.id); last = self._meta.fields[-1] + for f in self._meta.fields: + if f.name in [ 'id' ] + self.polymorphic_internal_model_fields or 'ptr' in f.name: continue + out += f.name + if isinstance(f, (models.ForeignKey)): + o = getattr(self, f.name) + out += ': "' + ('None' if o == None else o.__class__.__name__) + '"' + else: + out += ': "' + getattr(self, f.name) + '"' + if f != last: out += ', ' + return '<' + (self.__class__.__name__ + ': ').ljust(17) + out + '>' + + +class ShowFieldsAndTypes(object): + """ like ShowFields, but also show field types """ + def __repr__(self): + out = 'id %d, ' % (self.id); last = self._meta.fields[-1] + for f in self._meta.fields: + if f.name in [ 'id' ] + self.polymorphic_internal_model_fields or 'ptr' in f.name: continue + out += f.name + ' (' + type(f).__name__ + ')' + if isinstance(f, (models.ForeignKey)): + o = getattr(self, f.name) + out += ': "' + ('None' if o == None else o.__class__.__name__) + '"' + else: + out += ': "' + getattr(self, f.name) + '"' + if f != last: out += ', ' + return '<' + self.__class__.__name__ + ': ' + out + '>' + +