Port documentation to Sphinx, cleanup README
parent
8cf313335c
commit
c933be9c24
|
|
@ -20,4 +20,5 @@ Contributors
|
|||
|
||||
Former authors / maintainers
|
||||
============================
|
||||
|
||||
* Bert Constantin 2009/2010 (Original author, disappeared from the internet :( )
|
||||
|
|
|
|||
661
DOCS.rst
661
DOCS.rst
|
|
@ -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()
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific subtypes:
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner"> ]
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
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') )
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 4, topic "Color Use in Late Cubism", 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)
|
||||
.
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
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') )
|
||||
.
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
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)
|
||||
.
|
||||
[ <ModelX: id 1, field_x (CharField)>,
|
||||
<ModelY: id 2, field_y (CharField)> ]
|
||||
|
||||
|
||||
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()
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
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
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelA: id 2, field1 (CharField)>,
|
||||
<ModelA: id 3, field1 (CharField)> ]
|
||||
|
||||
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)
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
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
|
||||
|
||||
218
README.rst
218
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::
|
||||
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
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::
|
||||
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<Project: id 2, topic "Painting with Tim">,
|
||||
<Project: id 3, topic "Swallow Aerodynamics"> ]
|
||||
|
||||
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).
|
||||
|
|
|
|||
|
|
@ -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 <target>' where <target> 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."
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
.
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
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') )
|
||||
.
|
||||
[ <ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
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)
|
||||
.
|
||||
[ <ModelX: id 1, field_x (CharField)>,
|
||||
<ModelY: id 2, field_y (CharField)> ]
|
||||
|
||||
|
||||
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()
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
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
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelA: id 2, field1 (CharField)>,
|
||||
<ModelA: id 3, field1 (CharField)> ]
|
||||
|
||||
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)
|
||||
[ <ModelA: id 1, field1 (CharField)>,
|
||||
<ModelB: id 2, field1 (CharField), field2 (CharField)>,
|
||||
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -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 <changelog_archive>`.
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
# "<project> v<release> 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 <link> 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',
|
||||
}
|
||||
|
|
@ -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/
|
||||
|
|
@ -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()
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
Using vanilla Django, we get the base class objects, which is rarely what we wanted:
|
||||
|
||||
>>> Project.objects.all()
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<Project: id 2, topic "Painting with Tim">,
|
||||
<Project: id 3, topic "Swallow Aerodynamics"> ]
|
||||
|
||||
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`
|
||||
|
||||
|
|
@ -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 ^<target^>` where ^<target^> 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
|
||||
|
|
@ -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)
|
||||
...
|
||||
|
|
@ -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
|
||||
<http://www.jacobian.org/writing/concrete-inheritance/>`_.
|
||||
|
|
@ -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()
|
||||
[ <Project: id 1, topic "Department Party">,
|
||||
<ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
Use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific subtypes:
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner"> ]
|
||||
|
||||
>>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject)
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 3, topic "Swallow Aerodynamics", supervisor "Dr. Winter"> ]
|
||||
|
||||
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'))
|
||||
[ <ArtProject: id 2, topic "Painting with Tim", artist "T. Turner">,
|
||||
<ResearchProject: id 4, topic "Color Use in Late Cubism", 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!
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
206
rst.css
206
rst.css
|
|
@ -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 }
|
||||
Loading…
Reference in New Issue