From c933be9c24648a2e4e5e8bbaf6ba747f70d913bf Mon Sep 17 00:00:00 2001 From: Diederik van der Boor Date: Sun, 19 May 2013 17:15:41 +0200 Subject: [PATCH] Port documentation to Sphinx, cleanup README --- AUTHORS.rst | 1 + DOCS.rst | 661 ---------------------- README.rst | 218 +------ docs/Makefile | 153 +++++ docs/admin.rst | 85 +++ docs/advanced.rst | 272 +++++++++ docs/changelog.rst | 51 ++ CHANGES.rst => docs/changelog_archive.rst | 115 +--- docs/conf.py | 255 +++++++++ docs/contributing.rst | 51 ++ docs/index.rst | 71 +++ docs/make.bat | 190 +++++++ docs/managers.rst | 86 +++ docs/performance.rst | 40 ++ docs/quickstart.rst | 82 +++ polymorphic/tests.py | 1 + rst-to-html.py | 24 - rst.css | 206 ------- 18 files changed, 1390 insertions(+), 1172 deletions(-) delete mode 100644 DOCS.rst create mode 100644 docs/Makefile create mode 100644 docs/admin.rst create mode 100644 docs/advanced.rst create mode 100644 docs/changelog.rst rename CHANGES.rst => docs/changelog_archive.rst (82%) create mode 100644 docs/conf.py create mode 100644 docs/contributing.rst create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/managers.rst create mode 100644 docs/performance.rst create mode 100644 docs/quickstart.rst delete mode 100755 rst-to-html.py delete mode 100644 rst.css diff --git a/AUTHORS.rst b/AUTHORS.rst index 59f9376..5f0e842 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -20,4 +20,5 @@ Contributors Former authors / maintainers ============================ + * Bert Constantin 2009/2010 (Original author, disappeared from the internet :( ) diff --git a/DOCS.rst b/DOCS.rst deleted file mode 100644 index 4997ace..0000000 --- a/DOCS.rst +++ /dev/null @@ -1,661 +0,0 @@ -Polymorphic Models for Django -============================= - -.. contents:: Table of Contents - :depth: 1 - - -Quickstart -=========== - -Install -------- - -After uncompressing (if necessary), in the directory "...django_polymorphic", -execute (on Unix-like systems):: - - sudo python setup.py install - -Make Your Models Polymorphic ----------------------------- - -Use ``PolymorphicModel`` instead of Django's ``models.Model``, like so:: - - from polymorphic import PolymorphicModel - - class Project(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) - -All models inheriting from your polymorphic models will be polymorphic as well. - -Create some objects -------------------- - ->>> Project.objects.create(topic="Department Party") ->>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") ->>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") - -Get polymorphic query results ------------------------------ - ->>> Project.objects.all() -[ , - , - ] - -use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific subtypes: - ->>> Project.objects.instance_of(ArtProject) -[ ] - ->>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject) -[ , - ] - -Polymorphic filtering: Get all projects where Mr. Turner is involved as an artist -or supervisor (note the three underscores): - ->>> Project.objects.filter( Q(ArtProject___artist = 'T. Turner') | Q(ResearchProject___supervisor = 'T. Turner') ) -[ , - ] - -This is basically all you need to know, as django_polymorphic mostly -works fully automatic and just delivers the expected ("pythonic") results. - -Note: In all example output, above and below, for a nicer and more informative -output the ``ShowFieldType`` mixin has been used (documented below). - - -List of Features -================ - -* Fully automatic - generally makes sure that the same objects are - returned from the database that were stored there, regardless how - they are retrieved -* Only on models that request polymorphic behaviour (and the - models inheriting from them) -* Full support for ForeignKeys, ManyToManyFields and OneToToneFields -* Filtering for classes, equivalent to python's isinstance(): - ``instance_of(...)`` and ``not_instance_of(...)`` -* Polymorphic filtering/ordering etc., allowing the use of fields of - derived models ("ArtProject___artist") -* Support for user-defined custom managers -* Automatic inheritance of custom managers -* Support for user-defined custom queryset classes -* Non-polymorphic queries if needed, with no other change in - features/behaviour -* Combining querysets of different types/models ("qs3 = qs1 | qs2") -* Nice/informative display of polymorphic queryset results - - -More about Installation / Testing -================================= - -Requirements ------------- - -Django 1.1 (or later) and Python 2.4 or later. This code has been tested -on Django 1.1 / 1.2 / 1.3 and Python 2.4.6 / 2.5.4 / 2.6.4 on Linux. - -Included Test Suite -------------------- - -The repository (or tar file) contains a complete Django project -that may be used for tests or experiments, without any installation needed. - -To run the included test suite, in the directory "...django_polymorphic" execute:: - - ./manage test polymorphic - -The management command ``pcmd.py`` in the app ``pexp`` can be used -for quick tests or experiments - modify this file (pexp/management/commands/pcmd.py) -to your liking, then run:: - - ./manage syncdb # db is created in /var/tmp/... (settings.py) - ./manage pcmd - -Installation ------------- - -In the directory "...django_polymorphic", execute ``sudo python setup.py install``. - -Alternatively you can simply copy the ``polymorphic`` subdirectory -(under "django_polymorphic") into your Django project dir -(e.g. if you want to distribute your project with more 'batteries included'). - -If you want to run the test cases in `polymorphic/tests.py`, you need to add -``polymorphic`` to your INSTALLED_APPS setting. - -Django's ContentType framework (``django.contrib.contenttypes``) -needs to be listed in INSTALLED_APPS (usually it already is). - - -More Polymorphic Functionality -============================== - -In the examples below, these models are being used:: - - 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 in the admin interface ------------------------------------------------ - -Naturally, it's possible to register individual polymorphic models in the Django admin interface. -However, to use these models in a single cohesive interface, some extra base classes are available. - -The polymorphic admin interface works in a simple way: - -* The add screen gains an additional step where the desired child model is selected. -* The edit screen displays the admin interface of the child model. -* The list screen still displays all objects of the base class. - -The polymorphic admin is implemented via a parent admin that forwards the *edit* and *delete* views -to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent model admin. - -Both the parent model and child model need to have a ``ModelAdmin`` class. -Only the ``ModelAdmin`` class of the parent/base model has to be registered in the Django admin site. - -The parent model -~~~~~~~~~~~~~~~~ - -The parent model needs to inherit ``PolymorphicParentModelAdmin``, and implement the following: - -* ``base_model`` should be set -* ``child_models`` should be set, or: - - * ``get_child_models()`` should return a list with (Model, ModelAdmin) tuple. - -The exact implementation can depend on the way your module is structured. -For simple inheritance situations, ``child_models`` is best suited. -For large applications, this leaves room for a plugin registration system. - -The child models -~~~~~~~~~~~~~~~~ - -The admin interface of the derived models should inherit from ``PolymorphicChildModelAdmin``. -Again, ``base_model`` should be set in this class as well. -This class implements the following features: - -* It corrects the breadcrumbs in the admin pages. -* It extends the template lookup paths, to look for both the parent model and child model in the ``admin/app/model/change_form.html`` path. -* It allows to set ``base_form`` so the derived class will automatically include other fields in the form. -* It allows to set ``base_fieldsets`` so the derived class will automatically display any extra fields. - -By adding ``polymorphic`` to the ``INSTALLED_APPS``, the breadcrumbs will be -fixed as well, to stay the same for all child models. - -The standard ``ModelAdmin`` attributes ``form`` and ``fieldsets`` should rather be avoided at the base class, -because it will hide any additional fields which are defined in the derived model. Instead, -use the ``base_form`` and ``base_fieldsets`` instead. The ``PolymorphicChildModelAdmin`` will -automatically detect the additional fields that the child model has, display those in a separate fieldset. - - -Example -~~~~~~~ - -:: - - from django.contrib import admin - from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin - - - class ModelAChildAdmin(PolymorphicChildModelAdmin): - """ Base admin class for all child models """ - base_model = ModelA - - # By using these `base_...` attributes instead of the regular ModelAdmin `form` and `fieldsets`, - # the additional fields of the child models are automatically added to the admin form. - base_form = ... - base_fieldsets = ( - ... - ) - - class ModelBAdmin(ModelAChildAdmin): - # define custom features here - - class ModelCAdmin(ModelBAdmin): - # define custom features here - - - class ModelAParentAdmin(PolymorphicParentModelAdmin): - """ The parent model admin """ - base_model = ModelA - child_models = ( - (ModelB, ModelBAdmin), - (ModelC, ModelCAdmin), - ) - - # Only the parent needs to be registered: - admin.site.register(ModelA, ModelAParentAdmin) - - -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 ...]) - -You can also use this feature in Q-objects (with the same result as above): - ->>> ModelA.objects.filter( Q(instance_of=ModelB) ) - - -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 -syntax: ``exact model name + three _ + field name``): - ->>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') ) -. -[ , - ] - - -Combining Querysets -------------------- - -Querysets could 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) -. -[ , - ] - - -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() - [ , - , - ] - - -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 MyThirdPartyBaseModel(PolymorhpicModel, ThirdPartyModel): - pass # or add fields - -Or instead integrating the third party model anywhere into an -existing polymorphic inheritance tree:: - - class MyBaseModel(SomePolymorphicModel): - my_field = models.CharField(max_length=10) - - class MyModelWithThirdParty(MyBaseModel, ThirdPartyModel): - pass # or add fields - - -Non-Polymorphic Queries ------------------------ - -If you insert ``.non_polymorphic()`` anywhere into the query chain, then -django_polymorphic will simply leave out the final step of retrieving the -real objects, and the manager/queryset will return objects of the type of -the base class you used for the query, like vanilla Django would -(``ModelA`` in this example). - ->>> qs=ModelA.objects.non_polymorphic().all() ->>> qs -[ , - , - ] - -There are no other changes in the behaviour of the queryset. For example, -enhancements for ``filter()`` or ``instance_of()`` etc. still work as expected. -If you do the final step yourself, you get the usual polymorphic result: - ->>> ModelA.objects.get_real_instances(qs) -[ , - , - ] - - -About Queryset Methods ----------------------- - -* ``annotate()`` and ``aggregate()`` work just as usual, with the - addition that the ``ModelX___field`` syntax can be used for the - keyword arguments (but not for the non-keyword arguments). - -* ``order_by()`` now similarly supports the ``ModelX___field`` syntax - for specifying ordering through a field in a submodel. - -* ``distinct()`` works as expected. It only regards the fields of - the base class, but this should never make a difference. - -* ``select_related()`` works just as usual, but it can not (yet) be used - to select relations in derived models - (like ``ModelA.objects.select_related('ModelC___fieldxy')`` ) - -* ``extra()`` works as expected (it returns polymorphic results) but - currently has one restriction: The resulting objects are required to have - a unique primary key within the result set - otherwise an error is thrown - (this case could be made to work, however it may be mostly unneeded).. - The keyword-argument "polymorphic" is no longer supported. - You can get back the old non-polymorphic behaviour (before V1.0) - by using ``ModelA.objects.non_polymorphic().extra(...)``. - -* ``get_real_instances()`` allows you to turn a - queryset or list of base model objects efficiently into the real objects. - For example, you could do ``base_objects_queryset=ModelA.extra(...).non_polymorphic()`` - and then call ``real_objects=base_objects_queryset.get_real_instances()``.Or alternatively - .``real_objects=ModelA.objects..get_real_instances(base_objects_queryset_or_object_list)`` - -* ``values()`` & ``values_list()`` currently do not return polymorphic - results. This may change in the future however. If you want to use these - methods now, it's best if you use ``Model.base_objects.values...`` as - this is guaranteed to not change. - -* ``defer()`` and ``only()`` are not yet supported (support will be added - in the future). - - -Using enhanced Q-objects in any Places --------------------------------------- - -The queryset enhancements (e.g. ``instance_of``) only work as arguments -to the member functions of a polymorphic queryset. Occationally it may -be useful to be able to use Q objects with these enhancements in other places. -As Django doesn't understand these enhanced Q objects, you need to -transform them manually into normal Q objects before you can feed them -to a Django queryset or function:: - - normal_q_object = ModelA.translate_polymorphic_Q_object( Q(instance_of=Model2B) ) - -This function cannot be used at model creation time however (in models.py), -as it may need to access the ContentTypes database table. - - -Nicely Displaying Polymorphic Querysets ---------------------------------------- - -In order to get the output as seen in all examples here, you need to use the -ShowFieldType class mixin:: - - from polymorphic import PolymorphicModel, ShowFieldType - - class ModelA(ShowFieldType, PolymorphicModel): - field1 = models.CharField(max_length=10) - -You may also use ShowFieldContent or ShowFieldTypeAndContent to display -additional information when printing querysets (or converting them to text). - -When showing field contents, they will be truncated to 20 characters. You can -modify this behaviour by setting a class variable in your model like this:: - - class ModelA(ShowFieldType, PolymorphicModel): - polymorphic_showfield_max_field_width = 20 - ... - -Similarly, pre-V1.0 output formatting can be re-estated by using -``polymorphic_showfield_old_format = True``. - -Custom Managers, Querysets & Manager Inheritance -================================================ - -Using a Custom Manager ----------------------- - -A nice feature of Django is the possibility to define one's own custom object managers. -This is fully supported with django_polymorphic: For creating a custom polymorphic -manager class, just derive your manager from ``PolymorphicManager`` instead of -``models.Manager``. As with vanilla Django, in your model class, you should -explicitly add the default manager first, and then your custom manager:: - - from polymorphic import PolymorphicModel, PolymorphicManager - - class TimeOrderedManager(PolymorphicManager): - def get_query_set(self): - qs = super(TimeOrderedManager,self).get_query_set() - return qs.order_by('-start_date') # order the queryset - - def most_recent(self): - qs = self.get_query_set() # get my ordered queryset - return qs[:10] # limit => get ten most recent entries - - class Project(PolymorphicModel): - objects = PolymorphicManager() # add the default polymorphic manager first - objects_ordered = TimeOrderedManager() # then add your own manager - start_date = DateTimeField() # project start is this date/time - -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 -------------------- - -Polymorphic models inherit/propagate all managers from their -base models, as long as these are polymorphic. This means that all -managers defined in polymorphic base models continue to work as -expected in models inheriting from this base model:: - - from polymorphic import PolymorphicModel, PolymorphicManager - - class TimeOrderedManager(PolymorphicManager): - def get_query_set(self): - qs = super(TimeOrderedManager,self).get_query_set() - return qs.order_by('-start_date') # order the queryset - - def most_recent(self): - qs = self.get_query_set() # get my ordered queryset - return qs[:10] # limit => get ten most recent entries - - class Project(PolymorphicModel): - objects = PolymorphicManager() # add the default polymorphic manager first - objects_ordered = TimeOrderedManager() # then add your own manager - start_date = DateTimeField() # project start is this date/time - - class ArtProject(Project): # inherit from Project, inheriting its fields and managers - artist = models.CharField(max_length=30) - -ArtProject inherited the managers ``objects`` and ``objects_ordered`` from Project. - -``ArtProject.objects_ordered.all()`` will return all art projects ordered -regarding their start time and ``ArtProject.objects_ordered.most_recent()`` -will return the ten most recent art projects. -. - -Using a Custom Queryset Class ------------------------------ - -The ``PolymorphicManager`` class accepts one initialization argument, -which is the queryset class the manager should use. Just as with vanilla Django, -you may define your own custom queryset classes. Just use PolymorphicQuerySet -instead of Django's QuerySet as the base class:: - - from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet - - class MyQuerySet(PolymorphicQuerySet): - def my_queryset_method(...): - ... - - class MyModel(PolymorphicModel): - my_objects=PolymorphicManager(MyQuerySet) - ... - - -Performance Considerations -========================== - -The current implementation is rather simple and does not use any -custom SQL or Django DB layer internals - it is purely based on the -standard Django ORM. - -Specifically, 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 SQL query if all are -class ``ModelA``. If 50 objects are ``ModelA`` and 50 are ``ModelB``, then -two queries are executed. The pathological worst case is 101 db queries if -result_objects contains 100 different object types (with all of them -subclasses of ``ModelA``). - -Usually, when Django users create their own polymorphic ad-hoc solution -without a tool like django_polymorphic, this usually results in a variation of :: - - result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ] - -which has very bad performance, as it introduces one additional -SQL query for every object in the result which is not of class ``BaseModel``. - -Compared to these solutions, django_polymorphic has the advantage -that it only needs one sql request per *object type*, and not *per object*. - -.. _performance: - -Performance Problems with PostgreSQL, MySQL and SQLite3 -------------------------------------------------------- - -Current relational DBM systems seem to have general problems with -the SQL queries produced by object relational mappers like the Django -ORM, if these use multi-table inheritance like Django's ORM does. -The "inner joins" in these queries can perform very badly. -This is independent of django_polymorphic and affects all uses of -multi table Model inheritance. - -Concrete benchmark results are forthcoming (please see discussion forum). - -Please also see this `post (and comments) from Jacob Kaplan-Moss`_. - -.. _post (and comments) from Jacob Kaplan-Moss: http://www.jacobian.org/writing/concrete-inheritance/ - - -.. _restrictions: - -Restrictions & Caveats -====================== - -* Database Performance regarding concrete Model inheritance in general. - Please see "Performance Problems" above. - -* Queryset methods ``values()``, ``values_list()``, ``select_related()``, - ``defer()`` and ``only()`` are not yet fully supported (see above). - ``extra()`` has one restriction: the resulting objects are required to have - a unique primary key within the result set. - -* Diamond shaped inheritance: There seems to be a general problem - with diamond shaped multiple model inheritance with Django models - (tested with V1.1 - V1.3). - 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 enhanced filter-definitions/Q-objects only work as arguments - for the methods of the polymorphic querysets. Please see above - for ``translate_polymorphic_Q_object``. - -* A reference (``ContentType``) to the real/leaf model is stored - in the base model (the base model directly inheriting from - PolymorphicModel). You need to be aware of this when using the - ``dumpdata`` management command or any other low-level - database operations. E.g. if you rename models or apps or copy - objects from one database to another, then Django's ContentType - table needs to be corrected/copied too. This is of course generally - the case for any models using Django's ContentType. - -* Django 1.1 only - the names of polymorphic models must be unique - in the whole project, even if they are in two different apps. - This results from a restriction in the Django 1.1 "related_name" - option (fixed in Django 1.2). - -* Django 1.1 only - when ContentType is used in models, Django's - seralisation or fixtures cannot be used (all polymorphic models - use ContentType). This issue seems to be resolved for Django 1.2 - (changeset 11863: Fixed #7052, Added support for natural keys in serialization). - - + http://code.djangoproject.com/ticket/7052 - + http://stackoverflow.com/questions/853796/problems-with-contenttypes-when-loading-a-fixture-in-django - - -Project Status -============== - -Django_polymorphic works well for a considerable number of users now, -and no major problems have shown up for many months. -The API can be considered stable beginning with the V1.0 release. - - -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/README.rst b/README.rst index 745b7a9..4abe95f 100644 --- a/README.rst +++ b/README.rst @@ -1,36 +1,11 @@ Polymorphic Models for Django ============================= - -Quick Start, Docs, Contributing -------------------------------- - -* `What is django_polymorphic good for?`_ -* `Quickstart`_, or the complete `Installation and Usage Docs`_ -* `Release Notes, News and Discussion`_ (Google Group) or Changelog_ -* Download from GitHub_ or Bitbucket_, or as TGZ_ or ZIP_ -* Improve django_polymorphic, report issues, discuss, patch or fork (GitHub_, Bitbucket_, Group_, Mail_) - -.. _What is django_polymorphic good for?: #good-for -.. _release notes, news and discussion: http://groups.google.de/group/django-polymorphic/topics -.. _Group: http://groups.google.de/group/django-polymorphic/topics -.. _Mail: http://github.com/bconstantin/django_polymorphic/tree/master/setup.py -.. _Installation and Usage Docs: http://bserve.webhop.org/django_polymorphic/DOCS.html -.. _Quickstart: http://bserve.webhop.org/django_polymorphic/DOCS.html#quickstart -.. _GitHub: http://github.com/bconstantin/django_polymorphic -.. _Bitbucket: http://bitbucket.org/bconstantin/django_polymorphic -.. _TGZ: http://github.com/bconstantin/django_polymorphic/tarball/master -.. _ZIP: http://github.com/bconstantin/django_polymorphic/zipball/master -.. _Overview: http://bserve.webhop.org/django_polymorphic -.. _Changelog: http://bserve.webhop.org/django_polymorphic/CHANGES.html - -.. _good-for: - What is django_polymorphic good for? ------------------------------------ Let's assume the models ``ArtProject`` and ``ResearchProject`` are derived -from the model ``Project``, and let's store one of each into the database: +from the model ``Project``, and stored in the database: >>> Project.objects.create(topic="Department Party") >>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") @@ -40,192 +15,43 @@ If we want to retrieve all our projects, we do: >>> Project.objects.all() -Using django_polymorphic, we simply get what we stored:: +Using *django-polymorphic*, we simply get what we stored:: [ , , ] -Using vanilla Django, we get incomplete objects, which is probably not what we wanted:: +Using vanilla Django, we get the base class objects, which is probably not what we wanted:: [ , , ] -It's very similar for ForeignKeys, ManyToManyFields or OneToOneFields. +This also works when the polymorphic model is accessed via +ForeignKeys, ManyToManyFields or OneToOneFields. -In general, the effect of django_polymorphic is twofold: +Features +-------- -On one hand it makes sure that model inheritance just works as you -expect, by simply ensuring that you always get back exactly the same -objects from the database you stored there - regardless how you access -them, making model inheritance much more "pythonic". -This can save you a lot of unpleasant workarounds that tend to -make your code messy, error-prone, and slow. +* Full admin integation. +* ORM integration: -On the other hand, together with some small API additions to the Django -ORM, django_polymorphic enables a much more expressive and intuitive -programming style and also very advanced object oriented designs -that are not possible with vanilla Django. + * support for ForeignKey, ManyToManyField, OneToOneField descriptors. + * Filtering/ordering of derived models (``ArtProject___artist``). + * Filtering model types: ``instance_of(...)`` and ``not_instance_of(...)`` + * Combining querysets of different models (``qs3 = qs1 | qs2``) + * Support for custom user-defined managers. -Fortunately, most of the heavy duty machinery that is needed for this -functionality is already present in the original Django database layer. -Django_polymorphic adds a rather thin layer above that in order -to make real OO fully automatic and very easy to use. - -There is a catch however, which applies to concrete model inheritance -in general: Current DBM systems like PostgreSQL or MySQL are not very -good at processing the required sql queries and can be rather slow in -many cases. Concrete benchmarks are forthcoming (please see -discussion forum). - -For more information, please look at `Quickstart`_ or at the complete -`Installation and Usage Docs`_ and also see the `restrictions and caveats`_. - -.. _restrictions and caveats: http://bserve.webhop.org/django_polymorphic/DOCS.html#restrictions - - -This is a V1.0 Beta/Testing Release ------------------------------------ - -The release contains a considerable amount of changes in some of the more -critical parts of the software. It's intended for testing and development -environments and not for production environments. For these, it's best to -wait a few weeks for the proper V1.0 release, to allow some time for any -potential problems to turn up (if they exist). - -If you encounter any problems or have suggestions regarding the API or the -changes in this beta, please post them in the `discussion group`_ -or open an issue on GitHub_ or BitBucket_ (or send me an email). - -.. _discussion group: http://groups.google.de/group/django-polymorphic/topics +* Uses the minumum amount of queries needed to fetch the derived models. +* Disabling polymorphic behavior when needed. +While *django-polymorphic* makes subclassed models easy to use in Django, +we still encourage to use them with caution. Each subclassed model will require +Django to perform an ``INNER JOIN`` to fetch the model fields from the database. +While taking this in mind, there are valid reasons for using subclassed models. +That's what this library is designed for! License ======= -Django_polymorphic uses the same license as Django (BSD-like). - - -API Changes & Additions -======================= - - -November 11 2010, V1.0 API Changes -------------------------------------------------------------------- - -extra() queryset method -+++++++++++++++++++++++ - -``.extra()`` has been re-implemented. Now it's polymorphic by -default and works (nearly) without restrictions (please see docs). This is a (very) -incompatible API change regarding previous versions of django_polymorphic. -Support for the ``polymorphic`` keyword parameter has been removed. -You can get back the non-polymorphic behaviour by using -``ModelA.objects.non_polymorphic().extra()``. - -No Pretty-Printing of Querysets by default -++++++++++++++++++++++++++++++++++++++++++ - -In order to improve compatibility with vanilla Django, printing quersets -(__repr__ and __unicode__) does not use django_polymorphic's pretty printing -by default anymore. To get the old behaviour when printing querysets, -you need to replace your model definition: - ->>> class Project(PolymorphicModel): - -by: - ->>> class Project(PolymorphicModel, ShowFieldType): - -The mixin classes for pretty output have been renamed: - - ``ShowFieldTypes, ShowFields, ShowFieldsAndTypes`` - -are now: - - ``ShowFieldType, ShowFieldContent and ShowFieldTypeAndContent`` - -(the old ones still exist for compatibility) - -Pretty-Printing Output Format Changed -+++++++++++++++++++++++++++++++++++++ - -``ShowFieldContent`` and ``ShowFieldTypeAndContent`` now -use a slightly different output format. If this causes too much trouble for -your test cases, you can get the old behaviour back (mostly) by adding -``polymorphic_showfield_old_format = True`` to your model definitions. -``ShowField...`` now also produces more informative output for custom -primary keys. - -polymorphic_dumpdata -++++++++++++++++++++ - -The ``polymorphic_dumpdata`` management command is not needed anymore -and has been disabled, as the regular Django dumpdata command now automatically -works correctly with polymorphic models (for all supported versions of Django). - -Running the Test suite with Django 1.3 -++++++++++++++++++++++++++++++++++++++ - -Django 1.3 requires ``python manage.py test polymorphic`` instead of -just ``python manage.py test``. - - -November 01 2010, V1.0 API Additions -------------------------------------------------------------------- - -* ``.non_polymorphic()`` queryset member function added. This is preferable to - using ``.base_objects...``, as it just makes the resulting queryset non-polymorphic - and does not change anything else in the behaviour of the manager used (while - ``.base_objects`` is just a different manager). - -* ``.get_real_instances()`` has been elevated to an official part of the API. - It allows you to turn a queryset or list of base objects into a list of the real instances. - This is useful if e.g. you use ``ModelA.objects.non_polymorphic().extra(...)`` and then want to - transform the result to its polymorphic equivalent: - - >>> qs = ModelA.objects.all().non_polymorphic() - >>> real_objects = qs.get_real_instances() - - is equivalent to: - - >>> real_objects = ModelA.objects.all() - - Instead of ``qs.get_real_instances()``, ``ModelA.objects.get_real_instances(qs)`` may be used - as well. In the latter case, ``qs`` may be any list of objects of type ModelA. - -* ``translate_polymorphic_Q_object`` (see DOCS) - - -February 22 2010, Installation Note -------------------------------------------------------------------- - -The django_polymorphic source code has been restructured -and as a result needs to be installed like a normal Django App -- either via copying the "polymorphic" directory into your -Django project or by running setup.py. Adding 'polymorphic' -to INSTALLED_APPS in settings.py is still optional, however. - -The file `polymorphic.py` cannot be used as a standalone -extension module anymore (as is has been split into a number -of smaller files). - -Importing works slightly different now: All relevant symbols are -imported directly from 'polymorphic' instead from -'polymorphic.models':: - - # new way - from polymorphic import PolymorphicModel, ... - - # old way, doesn't work anymore - from polymorphic.models import PolymorphicModel, ... - - -January 26 2010: Database Schema Change -------------------------------------------------------------------- - -The update from January 26 changed the database schema (more info in the commit-log_). -Sorry for any inconvenience. But this should be the final DB schema now. - -.. _commit-log: http://github.com/bconstantin/django_polymorphic/commit/c2b420aea06637966a208329ef7ec853889fa4c7 +Django-polymorphic uses the same license as Django (BSD-like). diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..33ac4b5 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-polymorphic.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-polymorphic.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/django-polymorphic" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-polymorphic" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/admin.rst b/docs/admin.rst new file mode 100644 index 0000000..12e8e56 --- /dev/null +++ b/docs/admin.rst @@ -0,0 +1,85 @@ +Django admin integration +======================== + +Off course, it's possible to register individual polymorphic models in the Django admin interface. +However, to use these models in a single cohesive interface, some extra base classes are available. + +The polymorphic admin interface works in a simple way: + +* The add screen gains an additional step where the desired child model is selected. +* The edit screen displays the admin interface of the child model. +* The list screen still displays all objects of the base class. + +The polymorphic admin is implemented via a parent admin that forwards the *edit* and *delete* views +to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent model admin. + +Both the parent model and child model need to have a ``ModelAdmin`` class. +Only the ``ModelAdmin`` class of the parent/base model has to be registered in the Django admin site. + +The parent model +---------------- + +The parent model needs to inherit ``PolymorphicParentModelAdmin``, and implement the following: + +* ``base_model`` should be set +* ``child_models`` or ``get_child_models()`` should return a list with (Model, ModelAdmin) tuple. + +The exact implementation can depend on the way your module is structured. +For simple inheritance situations, ``child_models`` is the best solution. +For large applications, ``get_child_models()`` can be used to query a plugin registration system. + +The child models +---------------- + +The admin interface of the derived models should inherit from ``PolymorphicChildModelAdmin``. +Again, ``base_model`` should be set in this class as well. +This class implements the following features: + +* It corrects the breadcrumbs in the admin pages. +* It extends the template lookup paths, to look for both the parent model and child model in the ``admin/app/model/change_form.html`` path. +* It allows to set ``base_form`` so the derived class will automatically include other fields in the form. +* It allows to set ``base_fieldsets`` so the derived class will automatically display any extra fields. + +The standard ``ModelAdmin`` attributes ``form`` and ``fieldsets`` should rather be avoided at the base class, +because it will hide any additional fields which are defined in the derived model. Instead, +use the ``base_form`` and ``base_fieldsets`` instead. The ``PolymorphicChildModelAdmin`` will +automatically detect the additional fields that the child model has, display those in a separate fieldset. + + +Example +------- + +.. code-block:: python + + from django.contrib import admin + from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin + + + class ModelAChildAdmin(PolymorphicChildModelAdmin): + """ Base admin class for all child models """ + base_model = ModelA + + # By using these `base_...` attributes instead of the regular ModelAdmin `form` and `fieldsets`, + # the additional fields of the child models are automatically added to the admin form. + base_form = ... + base_fieldsets = ( + ... + ) + + class ModelBAdmin(ModelAChildAdmin): + # define custom features here + + class ModelCAdmin(ModelBAdmin): + # define custom features here + + + class ModelAParentAdmin(PolymorphicParentModelAdmin): + """ The parent model admin """ + base_model = ModelA + child_models = ( + (ModelB, ModelBAdmin), + (ModelC, ModelCAdmin), + ) + + # Only the parent needs to be registered: + admin.site.register(ModelA, ModelAParentAdmin) diff --git a/docs/advanced.rst b/docs/advanced.rst new file mode 100644 index 0000000..4a652e8 --- /dev/null +++ b/docs/advanced.rst @@ -0,0 +1,272 @@ +Advanced features +================= + +In the examples below, these models are being used:: + + 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) + +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 ...]) + +You can also use this feature in Q-objects (with the same result as above): + +>>> ModelA.objects.filter( Q(instance_of=ModelB) ) + + +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 +syntax: ``exact model name + three _ + field name``): + +>>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') ) +. +[ , + ] + + +Combining Querysets +------------------- + +Querysets could 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) +. +[ , + ] + + +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() + [ , + , + ] + + +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 MyThirdPartyBaseModel(PolymorhpicModel, ThirdPartyModel): + pass # or add fields + +Or instead integrating the third party model anywhere into an +existing polymorphic inheritance tree:: + + class MyBaseModel(SomePolymorphicModel): + my_field = models.CharField(max_length=10) + + class MyModelWithThirdParty(MyBaseModel, ThirdPartyModel): + pass # or add fields + + +Non-Polymorphic Queries +----------------------- + +If you insert ``.non_polymorphic()`` anywhere into the query chain, then +django_polymorphic will simply leave out the final step of retrieving the +real objects, and the manager/queryset will return objects of the type of +the base class you used for the query, like vanilla Django would +(``ModelA`` in this example). + +>>> qs=ModelA.objects.non_polymorphic().all() +>>> qs +[ , + , + ] + +There are no other changes in the behaviour of the queryset. For example, +enhancements for ``filter()`` or ``instance_of()`` etc. still work as expected. +If you do the final step yourself, you get the usual polymorphic result: + +>>> ModelA.objects.get_real_instances(qs) +[ , + , + ] + + +About Queryset Methods +---------------------- + +* ``annotate()`` and ``aggregate()`` work just as usual, with the + addition that the ``ModelX___field`` syntax can be used for the + keyword arguments (but not for the non-keyword arguments). + +* ``order_by()`` similarly supports the ``ModelX___field`` syntax + for specifying ordering through a field in a submodel. + +* ``distinct()`` works as expected. It only regards the fields of + the base class, but this should never make a difference. + +* ``select_related()`` works just as usual, but it can not (yet) be used + to select relations in derived models + (like ``ModelA.objects.select_related('ModelC___fieldxy')`` ) + +* ``extra()`` works as expected (it returns polymorphic results) but + currently has one restriction: The resulting objects are required to have + a unique primary key within the result set - otherwise an error is thrown + (this case could be made to work, however it may be mostly unneeded).. + The keyword-argument "polymorphic" is no longer supported. + You can get back the old non-polymorphic behaviour + by using ``ModelA.objects.non_polymorphic().extra(...)``. + +* ``get_real_instances()`` allows you to turn a + queryset or list of base model objects efficiently into the real objects. + For example, you could do ``base_objects_queryset=ModelA.extra(...).non_polymorphic()`` + and then call ``real_objects=base_objects_queryset.get_real_instances()``. Or alternatively + .``real_objects=ModelA.objects.get_real_instances(base_objects_queryset_or_object_list)`` + +* ``values()`` & ``values_list()`` currently do not return polymorphic + results. This may change in the future however. If you want to use these + methods now, it's best if you use ``Model.base_objects.values...`` as + this is guaranteed to not change. + +* ``defer()`` and ``only()`` are not yet supported (support will be added + in the future). + + +Using enhanced Q-objects in any Places +-------------------------------------- + +The queryset enhancements (e.g. ``instance_of``) only work as arguments +to the member functions of a polymorphic queryset. Occationally it may +be useful to be able to use Q objects with these enhancements in other places. +As Django doesn't understand these enhanced Q objects, you need to +transform them manually into normal Q objects before you can feed them +to a Django queryset or function:: + + normal_q_object = ModelA.translate_polymorphic_Q_object( Q(instance_of=Model2B) ) + +This function cannot be used at model creation time however (in models.py), +as it may need to access the ContentTypes database table. + + +Nicely Displaying Polymorphic Querysets +--------------------------------------- + +In order to get the output as seen in all examples here, you need to use the +:class:`~polymorphic.showfields.ShowFieldType` class mixin:: + + from polymorphic import PolymorphicModel, ShowFieldType + + class ModelA(ShowFieldType, PolymorphicModel): + field1 = models.CharField(max_length=10) + +You may also use :class:`~polymorphic.showfields.ShowFieldContent` +or :class:`~polymorphic.showfields.ShowFieldTypeAndContent` to display +additional information when printing querysets (or converting them to text). + +When showing field contents, they will be truncated to 20 characters. You can +modify this behaviour by setting a class variable in your model like this:: + + class ModelA(ShowFieldType, PolymorphicModel): + polymorphic_showfield_max_field_width = 20 + ... + +Similarly, pre-V1.0 output formatting can be re-estated by using +``polymorphic_showfield_old_format = True``. + + + +.. _restrictions: + +Restrictions & Caveats +---------------------- + +* Database Performance regarding concrete Model inheritance in general. + Please see the :ref:`performance`. + +* Queryset methods ``values()``, ``values_list()``, ``select_related()``, + ``defer()`` and ``only()`` are not yet fully supported (see above). + ``extra()`` has one restriction: the resulting objects are required to have + a unique primary key within the result set. + +* Diamond shaped inheritance: There seems to be a general problem + with diamond shaped multiple model inheritance with Django models + (tested with V1.1 - V1.3). + 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 enhanced filter-definitions/Q-objects only work as arguments + for the methods of the polymorphic querysets. Please see above + for ``translate_polymorphic_Q_object``. + +* When using the ``dumpdata`` management command on polymorphic tables + (or any table that has a reference to + :class:`~django.contrib.contenttypes.models.ContentType`), + include the ``--natural`` flag in the arguments. + + + +.. old 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/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..a73573b --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,51 @@ +Changelog +========== + +Version 0.4.2 (2013-04-10) +-------------------------- + +* Used proper ``__version__`` marker. + + +Version 0.4.1 (2013-04-10) +-------------------------- + +* Add Django 1.5 and 1.6 support +* Add proxy model support +* Add default admin ``list_filter`` for polymorphic model type. +* Fix queryset support of related objects. +* Performed an overall cleanup of the project +* **Deprecated** the ``queryset_class`` argument of the ``PolymorphicManager`` constructor, use the class attribute instead. +* **Dropped** Django 1.1, 1.2 and 1.3 support + + +Version 0.4 (2013-03-25) +------------------------ + +* Update example project for Django 1.4 +* Added tox and Travis configuration + + +Version 0.3.1 (2013-02-28) +-------------------------- + +* SQL optimization, avoid query in pre_save_polymorphic() + + +Version 0.3 (2013-02-28) +------------------------ + +Many changes to the codebase happened, but no new version was released to pypi for years. +0.3 contains fixes submitted by many contributors, huge thanks to everyone! + +* Added a polymorphic admin interface. +* PEP8 and code cleanups by various authors + + +Version 0.2 (2012-11-12) +------------------------ + +The 0.2 release serves as legacy release. +It supports Django 1.1 up till 1.4 and Python 2.4 up till 2.7. + +For a detailed list of it's changes, see the :doc:`archived changelog `. diff --git a/CHANGES.rst b/docs/changelog_archive.rst similarity index 82% rename from CHANGES.rst rename to docs/changelog_archive.rst index 0f22f5d..c491e39 100644 --- a/CHANGES.rst +++ b/docs/changelog_archive.rst @@ -1,56 +1,27 @@ -*django_polymorphic* -++++++++++++++++++++ -Changelog -++++++++++++++++++++ +Archive of old changelog entries +================================ -2013-02-28 Releasing Polymorphic v.0.3 -====================================== +2011-01-24 V1.0 Release Candidate 1 +------------------------------------ -Many changes to the codebase happened, but no new version was released to pypi -for years. -0.3 contains fixes submitted by many contributors, huge thanks to everyone! - - -Juli 5, 2012, Polymorphic admin interface -========================================= - -Added a polymorphic admin interface. The admin interface is able to add polymorphic models, -and the admin edit screen also displays the custom fields of the polymorphic model. +* Fixed GitHub issue 15 (query result incomplete with inheritance). + Thanks to John Debs for reporting and the test case. 2011-12-20 Renaming, refactoring, new maintainer -================================================ +------------------------------------------------ -Since the original author diseappeared from the internet, we undertook to +Since the original author disappeared from the internet, we undertook to maintain and upgrade this piece of software. -It works really well, but we cannot guarantee or otherwise support its current -state. - The latest "legacy" tag should be V1.0-RC-1. Anything above that should be considered experimental and unstable until further notice (there be dragons). -New features, bug fixes and other improvements will be added to trunk from now -on. +New features, bug fixes and other improvements will be added to trunk from now on. -2011-01-24 V1.0 Release Candidate 1 -=================================== - -Bugfixes ------------------------- - -* Fixed GitHub issue 15 (query result incomplete with inheritance). - Thanks to John Debs for reporting and the test case. - - ------------------------------------------------------------------- - 2010-11-11 V1.0 Beta 2 -====================== - -This is a V1.0 Testing Release ------------------------------- +----------------------- Beta 2 accumulated somewhat more changes than intended, and also has been delayed by DBMS benchmark testing I wanted to do on model @@ -58,8 +29,7 @@ inheritance. These benchmarks show that there are considerable problems with concrete model inheritance and contemporary DBM systems. The results will be forthcoming on the google discussion forum. -Please also see: -http://www.jacobian.org/writing/concrete-inheritance/ +Please also see: http://www.jacobian.org/writing/concrete-inheritance/ The API should be stable now with Beta 2, so it's just about potential bugfixes from now on regarding V1.0. @@ -70,9 +40,8 @@ and Beta 1 is used on a few production sites by some enterprising users. There will be a release candidate for V1.0 in the very near future. - -New Features and API changes in Beta 2 since Beta 1 ---------------------------------------------------- +New Features and changes +~~~~~~~~~~~~~~~~~~~~~~~~ * API CHANGE: ``.extra()`` has been re-implemented. Now it's polymorphic by default and works (nearly) without restrictions (please see docs). This is a (very) @@ -95,7 +64,7 @@ New Features and API changes in Beta 2 since Beta 1 * ``.get_real_instances()``: implementation modified to allow the following more simple and intuitive use:: - + >>> qs = ModelA.objects.all().non_polymorphic() >>> qs.get_real_instances() @@ -109,7 +78,7 @@ New Features and API changes in Beta 2 since Beta 1 * misc changes/improvements Bugfixes ------------------------- +~~~~~~~~ * Custom fields could cause problems when used as the primary key. In derived models, Django's automatic ".pk" field does not always work @@ -121,14 +90,8 @@ Bugfixes "python manage.py test polymorphic" also tests and reports on this problem now. Thanks to Mathieu Steele for reporting and the test case. - ------------------------------------------------------------------- - 2010-10-18 V1.0 Beta 1 -====================== - -This is a V1.0 Beta/Testing Release ------------------------------------ +---------------------- This release is mostly a cleanup and maintenance release that also improves a number of minor things and fixes one (non-critical) bug. @@ -153,7 +116,7 @@ There also have been a number of minor API changes. Please see the README for more information. New Features ------------------------- +~~~~~~~~~~~~ * official Django 1.3 alpha compatibility @@ -180,13 +143,13 @@ New Features * Changelog added: CHANGES.rst/html Bugfixes ------------------------- +~~~~~~~~ * Removed requirement for primary key to be an IntegerField. Thanks to Mathieu Steele and Malthe Borch. API Changes ------------ +~~~~~~~~~~~ **polymorphic_dumpdata** @@ -223,10 +186,8 @@ Django 1.3 requires ``python manage.py test polymorphic`` instead of just ``python manage.py test``. ------------------------------------------------------------------- - 2010-2-22 -========== +--------- IMPORTANT: API Changed (import path changed), and Installation Note @@ -253,12 +214,12 @@ imported directly from 'polymorphic' instead from + minor API addition: 'from polymorphic import VERSION, get_version' New Features ------------------------- +~~~~~~~~~~~~ Python 2.4 compatibility, contributed by Charles Leifer. Thanks! Bugfixes ------------------------- +~~~~~~~~ Fix: The exception "...has no attribute 'sub_and_superclass_dict'" could be raised. (This occurred if a subclass defined __init__ @@ -272,14 +233,11 @@ Now it is possible to give a field the same name as the class (Found through the example provided by Mattias Brändström) - ------------------------------------------------------------------- - 2010-2-4 -========== +-------- New features (and documentation) ------------------------------------------ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ queryset order_by method added @@ -296,7 +254,7 @@ More about these additions in the docs: http://bserve.webhop.org/wiki/django_polymorphic/doc Bugfixes ------------------------- +~~~~~~~~ * fix remaining potential accessor name clashes (but this only works with Django 1.2+, for 1.1 no changes). Thanks to Andrew Ingram. @@ -307,7 +265,7 @@ Bugfixes sel.-r. was just ignored) "Restrictions & Caveats" updated ----------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Django 1.1 only - the names of polymorphic models must be unique in the whole project, even if they are in two different apps. @@ -320,11 +278,8 @@ Bugfixes support for natural keys in serialization). - ------------------------------------------------------------------- - 2010-1-30 -========== +--------- Fixed ContentType related field accessor clash (an error emitted by model validation) by adding related_name to the ContentType @@ -332,11 +287,8 @@ ForeignKey. This happened if your polymorphc model used a ContentType ForeignKey. Thanks to Andrew Ingram. - ------------------------------------------------------------------- - 2010-1-29 -========== +--------- Restructured django_polymorphic into a regular Django add-on application. This is needed for the management commands, and @@ -348,11 +300,8 @@ The ``poly`` app - until now being used for test purposes only ("installation/testing") for more info. - ------------------------------------------------------------------- - 2010-1-28 -========== +--------- Added the polymorphic_dumpdata management command (github issue 4), for creating fixtures, this should be used instead of @@ -363,12 +312,8 @@ Important: Using ContentType together with dumpdata generally needs Django 1.2 (important as any polymorphic model uses ContentType). - - ------------------------------------------------------------------- - 2010-1-26 -========== +--------- IMPORTANT - database schema change (more info in change log). I hope I got this change in early enough before anyone started diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..bc0d510 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +# +# django-polymorphic documentation build configuration file, created by +# sphinx-quickstart on Sun May 19 12:20:47 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('_ext')) +sys.path.insert(0, os.path.abspath('..')) +os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings' + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.graphviz', + 'sphinx.ext.intersphinx' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-polymorphic' +copyright = u'2013, Bert Constantin, Chris Glass, Diederik van der Boor' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.4.2.dev0' +# The full version, including alpha/beta/rc tags. +release = '0.4.2' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-polymorphicdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'django-polymorphic.tex', u'django-polymorphic Documentation', + u'Bert Constantin, Chris Glass, Diederik van der Boor', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'django-polymorphic', u'django-polymorphic Documentation', + [u'Bert Constantin, Chris Glass, Diederik van der Boor'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'django-polymorphic', u'django-polymorphic Documentation', + u'Bert Constantin, Chris Glass, Diederik van der Boor', 'django-polymorphic', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + #'http://docs.python.org/': None, + 'https://docs.djangoproject.com/en/dev': 'https://docs.djangoproject.com/en/dev/_objects', +} diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..0b8ee79 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,51 @@ +Contributing +============ + +You can contribute to *django-polymorphic* to forking the code on GitHub: + + https://github.com/chrisglass/django_polymorphic + + +Running tests +------------- + +We require features to be backed by a unit test. +This way, we can test *django-polymorphic* against new Django versions. +To run the included test suite, execute:: + + ./runtests.py + +To test support for multiple Python and Django versions, run tox from the repository root:: + + pip install tox + tox + +The Python versions need to be installed at your system. +On Linux, download the versions at http://www.python.org/download/releases/. +On MacOS X, use Homebrew_ to install other Python versions. + +We currently support Python 2.6 and 2.7. +Python 3.3 support is being worked on. + + +Example project +---------------- + +The repository (or tar file) contains a complete Django project +that may be used for tests or experiments, without any installation needed. + +The management command ``pcmd.py`` in the app ``pexp`` can be used for quick tests +or experiments - modify this file (pexp/management/commands/pcmd.py) to your liking. + + +Supported Django versions +------------------------- + +The current release should be usable with the supported releases of Django; +the current stable release and the previous release. Supporting older Django +versions is a nice-to-have feature, but not mandatory. + +In case you need to use *django-polymorphic* with older Django versions, +consider installing a previous version. + +.. _Homebrew: http://mxcl.github.io/homebrew/ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..fbd9a97 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,71 @@ +Welcome to django-polymorphic's documentation! +============================================== + +Django-polymorphic simplifies using inherited models in Django projects. +When a query is made at the base model, the inherited model classes are returned. + +When we store models that derive from a ``Project`` model... + +>>> Project.objects.create(topic="Department Party") +>>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") +>>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") + +...and want to retrieve all our projects, the subclassed models are returned! + +>>> Project.objects.all() + [ , + , + ] + +Using vanilla Django, we get the base class objects, which is rarely what we wanted: + +>>> Project.objects.all() + [ , + , + ] + +Features +-------- + +* Full admin integation. +* ORM integration: + + * support for ForeignKey, ManyToManyField, OneToOneField descriptors. + * support for proxy models + * Filtering/ordering of derived models (``ArtProject___artist``). + * Filtering model types: ``instance_of(...)`` and ``not_instance_of(...)`` + * Combining querysets of different models (``qs3 = qs1 | qs2``) + * Support for custom user-defined managers. + +* Uses the minumum amount of queries needed to fetch the derived models. +* Disabling polymorphic behavior when needed. + + +Getting started +--------------- + +.. toctree:: + :maxdepth: 2 + + quickstart + admin + performance + +Advanced topics +--------------- + +.. toctree:: + :maxdepth: 2 + + advanced + managers + changelog + contributing + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..9b9249c --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-polymorphic.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-polymorphic.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/managers.rst b/docs/managers.rst new file mode 100644 index 0000000..c7c6beb --- /dev/null +++ b/docs/managers.rst @@ -0,0 +1,86 @@ + + +Custom Managers, Querysets & Manager Inheritance +================================================ + +Using a Custom Manager +---------------------- + +A nice feature of Django is the possibility to define one's own custom object managers. +This is fully supported with django_polymorphic: For creating a custom polymorphic +manager class, just derive your manager from ``PolymorphicManager`` instead of +``models.Manager``. As with vanilla Django, in your model class, you should +explicitly add the default manager first, and then your custom manager:: + + from polymorphic import PolymorphicModel, PolymorphicManager + + class TimeOrderedManager(PolymorphicManager): + def get_query_set(self): + qs = super(TimeOrderedManager,self).get_query_set() + return qs.order_by('-start_date') # order the queryset + + def most_recent(self): + qs = self.get_query_set() # get my ordered queryset + return qs[:10] # limit => get ten most recent entries + + class Project(PolymorphicModel): + objects = PolymorphicManager() # add the default polymorphic manager first + objects_ordered = TimeOrderedManager() # then add your own manager + start_date = DateTimeField() # project start is this date/time + +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 +------------------- + +Polymorphic models inherit/propagate all managers from their +base models, as long as these are polymorphic. This means that all +managers defined in polymorphic base models continue to work as +expected in models inheriting from this base model:: + + from polymorphic import PolymorphicModel, PolymorphicManager + + class TimeOrderedManager(PolymorphicManager): + def get_query_set(self): + qs = super(TimeOrderedManager,self).get_query_set() + return qs.order_by('-start_date') # order the queryset + + def most_recent(self): + qs = self.get_query_set() # get my ordered queryset + return qs[:10] # limit => get ten most recent entries + + class Project(PolymorphicModel): + objects = PolymorphicManager() # add the default polymorphic manager first + objects_ordered = TimeOrderedManager() # then add your own manager + start_date = DateTimeField() # project start is this date/time + + class ArtProject(Project): # inherit from Project, inheriting its fields and managers + artist = models.CharField(max_length=30) + +ArtProject inherited the managers ``objects`` and ``objects_ordered`` from Project. + +``ArtProject.objects_ordered.all()`` will return all art projects ordered +regarding their start time and ``ArtProject.objects_ordered.most_recent()`` +will return the ten most recent art projects. +. + +Using a Custom Queryset Class +----------------------------- + +The ``PolymorphicManager`` class accepts one initialization argument, +which is the queryset class the manager should use. Just as with vanilla Django, +you may define your own custom queryset classes. Just use PolymorphicQuerySet +instead of Django's QuerySet as the base class:: + + from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet + + class MyQuerySet(PolymorphicQuerySet): + def my_queryset_method(...): + ... + + class MyModel(PolymorphicModel): + my_objects=PolymorphicManager(MyQuerySet) + ... diff --git a/docs/performance.rst b/docs/performance.rst new file mode 100644 index 0000000..720aee6 --- /dev/null +++ b/docs/performance.rst @@ -0,0 +1,40 @@ +.. _performance: + +Performance Considerations +========================== + +Usually, when Django users create their own polymorphic ad-hoc solution +without a tool like *django-polymorphic*, this usually results in a variation of :: + + result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ] + +which has very bad performance, as it introduces one additional +SQL query for every object in the result which is not of class ``BaseModel``. +Compared to these solutions, *django-polymorphic* has the advantage +that it only needs 1 SQL query per *object type*, and not *per object*. + +The current implementation is does not use any custom SQL or Django DB layer +internals - it is purely based on the standard Django ORM. Specifically, 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 SQL query if all are +class ``ModelA``. If 50 objects are ``ModelA`` and 50 are ``ModelB``, then +two queries are executed. The pathological worst case is 101 db queries if +result_objects contains 100 different object types (with all of them +subclasses of ``ModelA``). + +Database notes +-------------- + +Current relational DBM systems seem to have general problems with +the SQL queries produced by object relational mappers like the Django +ORM, if these use multi-table inheritance like Django's ORM does. +The "inner joins" in these queries can perform very badly. +This is independent of django_polymorphic and affects all uses of +multi table Model inheritance. + +Please also see this `post (and comments) from Jacob Kaplan-Moss +`_. diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..45f6d23 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,82 @@ +Quickstart +=========== + +Install the project using:: + + pip install django-polymorphic + +Update the settings file:: + + INSTALLED_APPS += ( + 'polymorphic', + 'django.contrib.contenttypes', + ) + +The current release of *django-polymorphic* supports Django 1.4, 1.5 and 1.6. + +Making Your Models Polymorphic +------------------------------ + +Use ``PolymorphicModel`` instead of Django's ``models.Model``, like so:: + + from polymorphic import PolymorphicModel + + class Project(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) + +All models inheriting from your polymorphic models will be polymorphic as well. + +Using Polymorphic Models +------------------------ + +Create some objects: + +>>> Project.objects.create(topic="Department Party") +>>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") +>>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") + +Get polymorphic query results: + +>>> Project.objects.all() +[ , + , + ] + +Use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific subtypes: + +>>> Project.objects.instance_of(ArtProject) +[ ] + +>>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject) +[ , + ] + +Polymorphic filtering: Get all projects where Mr. Turner is involved as an artist +or supervisor (note the three underscores): + +>>> Project.objects.filter(Q(ArtProject___artist='T. Turner') | Q(ResearchProject___supervisor='T. Turner')) +[ , + ] + +This is basically all you need to know, as *django-polymorphic* mostly +works fully automatic and just delivers the expected results. + +Note: When using the ``dumpdata`` management command on polymorphic tables +(or any table that has a reference to :class:`~django.contrib.contenttypes.models.ContentType`), +include the ``--natural`` flag in the arguments. This makes sure the +:class:`~django.contrib.contenttypes.models.ContentType` models will be referenced by name +instead of their primary key as that changes between Django instances. + + +.. note:: + While *django-polymorphic* makes subclassed models easy to use in Django, + we still encourage to use them with caution. Each subclassed model will require + Django to perform an ``INNER JOIN`` to fetch the model fields from the database. + While taking this in mind, there are valid reasons for using subclassed models. + That's what this library is designed for! diff --git a/polymorphic/tests.py b/polymorphic/tests.py index cf01c37..a4d168b 100644 --- a/polymorphic/tests.py +++ b/polymorphic/tests.py @@ -248,6 +248,7 @@ class PolymorphicTests(TestCase): """ def test_diamond_inheritance(self): # Django diamond problem + # https://code.djangoproject.com/ticket/10808 o1 = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y') o2 = DiamondXY.objects.get() diff --git a/rst-to-html.py b/rst-to-html.py deleted file mode 100755 index a69d0c8..0000000 --- a/rst-to-html.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/python - -import sys,os - -dopart = None -if len(sys.argv)>1: dopart = sys.argv[1] -noshow = 'noshow' in sys.argv - -css='--stylesheet-path=rst.css' - -def conv(name): - print 'convert',name - if noshow: - os.system('rst2html.py '+css+' %s.rst >%s.html' % (name, name) ) - else: - os.system('rst2html.py '+css+' %s.rst >%s.html ; firefox %s.html' % (name, name, name) ) - -if not dopart or dopart=='1': conv('DOCS') -if not dopart or dopart=='2': conv('README') -if not dopart or dopart=='3': conv('CHANGES') - -sys.exit() - - diff --git a/rst.css b/rst.css deleted file mode 100644 index 4a1d90b..0000000 --- a/rst.css +++ /dev/null @@ -1,206 +0,0 @@ -h1, h2, h3, h4, -#table-of-contents -{ - color: #47c; -} -h1 { padding-top: 15px; } -h2 { padding-top: 10px; } -h3 { padding-top: 7px; } - -a:hover { border-bottom: 1px solid #0066cc; } -a {color: #0066cc; text-decoration: none;} - -li { - padding-top: 5px; - padding-bottom: 5px; -} - -tt { - color: #080; - } - -blockquote tt { - color: #000 -} - -.first { - margin-top: 0 } - -.last { - margin-bottom: 0 } - -/* -a.toc-backref { - text-decoration: none ; - color: black } -*/ - -dd { - margin-bottom: 0.5em } - -div.abstract { - margin: 2em 5em } - -div.abstract p.topic-title { - font-weight: bold ; - text-align: center } - -div.attention, div.caution, div.danger, div.error, div.hint, -div.important, div.note, div.tip, div.warning { - margin: 2em ; - border: medium outset ; - padding: 1em } - -div.attention p.admonition-title, div.caution p.admonition-title, -div.danger p.admonition-title, div.error p.admonition-title, -div.warning p.admonition-title { - color: red ; - font-weight: bold ; - font-family: sans-serif } - -div.hint p.admonition-title, div.important p.admonition-title, -div.note p.admonition-title, div.tip p.admonition-title { - font-weight: bold ; - font-family: sans-serif } - -div.dedication { - margin: 2em 5em ; - text-align: center ; - font-style: italic } - -div.dedication p.topic-title { - font-weight: bold ; - font-style: normal } - -div.figure { - margin-left: 2em } - -div.footer, div.header { - font-size: smaller } - -div.system-messages { - margin: 5em } - -div.system-messages h1 { - color: red } - -div.system-message { - border: medium outset ; - padding: 1em } - -div.system-message p.system-message-title { - color: red ; - font-weight: bold } - -div.topic { - margin: 2em } - -h1.title { - text-align: center } - -h2.subtitle { - text-align: center } - -hr { - width: 75% } - -ol.simple, ul.simple { - margin-bottom: 1em } - -ol.arabic { - list-style: decimal } - -ol.loweralpha { - list-style: lower-alpha } - -ol.upperalpha { - list-style: upper-alpha } - -ol.lowerroman { - list-style: lower-roman } - -ol.upperroman { - list-style: upper-roman } - -p.caption { - font-style: italic } - -p.credits { - font-style: italic ; - font-size: smaller } - -p.label { - white-space: nowrap } - -p.topic-title { - font-weight: bold } - -pre.address { - margin-bottom: 0 ; - margin-top: 0 ; - font-family: serif ; - font-size: 100% } - -pre.line-block { - font-family: serif ; - font-size: 100% } - -pre.literal-block, pre.doctest-block { - margin-left: 2em ; - margin-right: 2em ; - background-color: #eeeeee } - -span.classifier { - font-family: sans-serif ; - font-style: oblique } - -span.classifier-delimiter { - font-family: sans-serif ; - font-weight: bold } - -span.interpreted { - font-family: sans-serif } - -span.option-argument { - font-style: italic } - -span.pre { - white-space: pre } - -span.problematic { - color: red } - -table { - margin-top: 0.5em ; - margin-bottom: 0.5em } - -table.citation { - border-left: solid thin gray ; - padding-left: 0.5ex } - -table.docinfo { - margin: 2em 4em } - -table.footnote { - border-left: solid thin black ; - padding-left: 0.5ex } - -td, th { - padding-left: 0.5em ; - padding-right: 0.5em ; - vertical-align: top } - -th.docinfo-name, th.field-name { - font-weight: bold ; - text-align: left ; - white-space: nowrap } - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - font-size: 100% } - -tt, pre.literal-block, pre.doctest-block { - font-size: 115%; - line-height: 150% } - -ul.auto-toc { - list-style-type: none }