fix_request_path_info
Bert Constantin 2010-11-11 17:11:01 +01:00
parent 748d10d2d5
commit ca329ff9b4
8 changed files with 309 additions and 275 deletions

View File

@ -7,10 +7,11 @@
<title></title>
<style type="text/css">
h1, h2, h3, h4 {
h1, h2, h3, h4,
#table-of-contents
{
color: #47c;
}
h1 { padding-top: 15px; }
h2 { padding-top: 10px; }
h3 { padding-top: 7px; }
@ -18,6 +19,10 @@ 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;
@ -221,32 +226,45 @@ ul.auto-toc {
<div class="section" id="changelog">
<h1>Changelog</h1>
<div class="section" id="v1-0-beta-2">
<h2>2010-10-25 V1.0 Beta 2</h2>
<div class="section" id="this-is-a-v1-0-beta-testing-release">
<h3>This is a V1.0 Beta/Testing Release</h3>
<p>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 show up (if they exist).</p>
<h2>2010-11-11 V1.0 Beta 2</h2>
<div class="section" id="this-is-a-v1-0-testing-release">
<h3>This is a V1.0 Testing Release</h3>
<p>Beta 2 accumulated somewhat more changes than intended, and also
has been delayed by DBMS benchmark testing I wanted to do on model
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.</p>
<p>Please also see:
<a class="reference external" href="http://www.jacobian.org/writing/concrete-inheritance/">http://www.jacobian.org/writing/concrete-inheritance/</a></p>
<p>The API should be stable now with Beta 2, so it's just about potential
bugfixes from now on regarding V1.0.</p>
<p>Beta 2 is still intended for testing and development environments and not
for production. No complaints have been heard regarding Beta 1 however,
and Beta 1 is used on a few production sites by some enterprising users.</p>
<p>There will be a release candidate for V1.0 in the very near future.</p>
</div>
<div class="section" id="new-features-and-api-changes-since-beta-1">
<h3>New Features and API changes since Beta 1</h3>
<ul class="simple">
<li>API CHANGE: <tt class="docutils literal">.extra()</tt> has been re-implemented. Now it's polymorphic by
default and works (nearly) without restrictions (please see docs). This is an
<div class="section" id="new-features-and-api-changes-in-beta-2-since-beta-1">
<h3>New Features and API changes in Beta 2 since Beta 1</h3>
<ul>
<li><p class="first">API CHANGE: <tt class="docutils literal">.extra()</tt> 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 <tt class="docutils literal">polymorphic</tt> keyword parameter has been removed.
You can get back the non-polymorphic behaviour by using
<tt class="docutils literal"><span class="pre">ModelA.objects.non_polymorphic().extra()</span></tt>.</li>
</ul>
<ul class="simple">
<li><tt class="docutils literal">.non_polymorphic()</tt> queryset member function added. This is preferable to
<tt class="docutils literal"><span class="pre">ModelA.objects.non_polymorphic().extra(...)</span></tt>.</p>
</li>
<li><p class="first">API CHANGE: <tt class="docutils literal">ShowFieldContent</tt> and <tt class="docutils literal">ShowFieldTypeAndContent</tt> 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
<tt class="docutils literal">polymorphic_showfield_old_format = True</tt> to your model definitions.
<tt class="docutils literal"><span class="pre">ShowField...</span></tt> now also produces more informative output for custom
primary keys.</p>
</li>
<li><p class="first"><tt class="docutils literal">.non_polymorphic()</tt> queryset member function added. This is preferable to
using <tt class="docutils literal"><span class="pre">.base_objects...</span></tt>, as it just makes the resulting queryset non-polymorphic
and does not change anything else in the behaviour of the manager used (while
<tt class="docutils literal">.base_objects</tt> is just a different manager).</li>
</ul>
<ul>
<tt class="docutils literal">.base_objects</tt> is just a different manager).</p>
</li>
<li><p class="first"><tt class="docutils literal">.get_real_instances()</tt>: implementation modified to allow the following
more simple and intuitive use:</p>
<pre class="literal-block">
@ -258,16 +276,29 @@ more simple and intuitive use:</p>
&gt;&gt;&gt; ModelA.objects.all()
</pre>
</li>
<li><p class="first">misc changes/improvements</p>
</li>
</ul>
</div>
<div class="section" id="bugfixes">
<h3>Bugfixes</h3>
<ul class="simple">
<li>misc changes/improvements</li>
<li>Custom fields could cause problems when used as the primary key.
In derived models, Django's automatic &quot;.pk&quot; field does not always work
correctly for such custom fields: &quot;some_object.pk&quot; and &quot;some_object.id&quot;
return different results (which they shouldn't, as pk should always be just
an alias for the primary key field). It's unclear yet if the problem lies in
Django or the affected custom fields. Regardless, the problem resulting
from this has been fixed with a small workaround.
&quot;python manage.py test polymorphic&quot; also tests and reports on this problem now.
Thanks to Mathieu Steele for reporting and the test case.</li>
</ul>
</div>
</div>
<hr class="docutils" />
<div class="section" id="v1-0-beta-1">
<h2>2010-10-18 V1.0 Beta 1</h2>
<div class="section" id="id1">
<div class="section" id="this-is-a-v1-0-beta-testing-release">
<h3>This is a V1.0 Beta/Testing Release</h3>
<p>This release is mostly a cleanup and maintenance release that also
improves a number of minor things and fixes one (non-critical) bug.</p>
@ -315,10 +346,10 @@ transform the result to its polymorphic equivalent.</p>
</li>
</ul>
</div>
<div class="section" id="bugfixes">
<div class="section" id="id1">
<h3>Bugfixes</h3>
<ul class="simple">
<li>removed requirement for primary key to be an IntegerField.
<li>Removed requirement for primary key to be an IntegerField.
Thanks to Mathieu Steele and Malthe Borch.</li>
</ul>
</div>
@ -329,9 +360,10 @@ Thanks to Mathieu Steele and Malthe Borch.</li>
and has been disabled, as the regular Django dumpdata command now automatically
works correctly with polymorphic models (for all supported versions of Django).</p>
<p><strong>Output of Queryset or Object Printing</strong></p>
<p>In order to improve compatibility with vanilla Django, printing quersets 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:</p>
<p>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:</p>
<pre class="doctest-block">
&gt;&gt;&gt; class Project(PolymorphicModel):
</pre>

View File

@ -3,21 +3,36 @@
Changelog
++++++++++
2010-11-01 V1.0 Beta 2
2010-11-11 V1.0 Beta 2
======================
This is a V1.0 Beta/Testing Release
-----------------------------------
This is a V1.0 Testing Release
------------------------------
Beta 2 accumulated somewhat more changes than intended. It's still
intended for testing and development environments and not for production
(it's best to wait for the final V1.0 for production servers).
Beta 2 accumulated somewhat more changes than intended, and also
has been delayed by DBMS benchmark testing I wanted to do on model
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.
New Features and API changes since Beta 1
-----------------------------------------
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.
Beta 2 is still intended for testing and development environments and not
for production. No complaints have been heard regarding Beta 1 however,
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
---------------------------------------------------
* API CHANGE: ``.extra()`` has been re-implemented. Now it's polymorphic by
default and works (nearly) without restrictions (please see docs). This is an
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
@ -27,7 +42,7 @@ New Features and API changes since Beta 1
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...`` also produces more informative output for custom
``ShowField...`` now also produces more informative output for custom
primary keys.
* ``.non_polymorphic()`` queryset member function added. This is preferable to
@ -58,6 +73,7 @@ Bugfixes
Django or the affected custom fields. Regardless, the problem resulting
from this has been fixed with a small workaround.
"python manage.py test polymorphic" also tests and reports on this problem now.
Thanks to Mathieu Steele for reporting and the test case.
------------------------------------------------------------------
@ -134,9 +150,10 @@ works correctly with polymorphic models (for all supported versions of Django).
**Output of Queryset or Object Printing**
In order to improve compatibility with vanilla Django, printing quersets 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:
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):

141
DOCS.html
View File

@ -232,10 +232,9 @@ ul.auto-toc {
<li><a class="reference internal" href="#more-polymorphic-functionality" id="id5">More Polymorphic Functionality</a></li>
<li><a class="reference internal" href="#custom-managers-querysets-manager-inheritance" id="id6">Custom Managers, Querysets &amp; Manager Inheritance</a></li>
<li><a class="reference internal" href="#performance-considerations" id="id7">Performance Considerations</a></li>
<li><a class="reference internal" href="#possible-optimizations" id="id8">Possible Optimizations</a></li>
<li><a class="reference internal" href="#restrictions-caveats" id="id9">Restrictions &amp; Caveats</a></li>
<li><a class="reference internal" href="#project-status" id="id10">Project Status</a></li>
<li><a class="reference internal" href="#links" id="id11">Links</a></li>
<li><a class="reference internal" href="#restrictions-caveats" id="id8">Restrictions &amp; Caveats</a></li>
<li><a class="reference internal" href="#project-status" id="id9">Project Status</a></li>
<li><a class="reference internal" href="#links" id="id10">Links</a></li>
</ul>
</div>
</div>
@ -269,8 +268,8 @@ class ResearchProject(Project):
<div class="section" id="create-some-objects">
<h2>Create some objects</h2>
<pre class="doctest-block">
&gt;&gt;&gt; Project.objects.create(topic=&quot;John's Gathering&quot;)
&gt;&gt;&gt; ArtProject.objects.create(topic=&quot;Sculpting with Tim&quot;, artist=&quot;T. Turner&quot;)
&gt;&gt;&gt; Project.objects.create(topic=&quot;Department Party&quot;)
&gt;&gt;&gt; ArtProject.objects.create(topic=&quot;Painting with Tim&quot;, artist=&quot;T. Turner&quot;)
&gt;&gt;&gt; ResearchProject.objects.create(topic=&quot;Swallow Aerodynamics&quot;, supervisor=&quot;Dr. Winter&quot;)
</pre>
</div>
@ -278,7 +277,7 @@ class ResearchProject(Project):
<h2>Get polymorphic query results</h2>
<pre class="doctest-block">
&gt;&gt;&gt; Project.objects.all()
[ &lt;Project: id 1, topic &quot;John's Gathering&quot;&gt;,
[ &lt;Project: id 1, topic &quot;Department Party&quot;&gt;,
&lt;ArtProject: id 2, topic &quot;Painting with Tim&quot;, artist &quot;T. Turner&quot;&gt;,
&lt;ResearchProject: id 3, topic &quot;Swallow Aerodynamics&quot;, supervisor &quot;Dr. Winter&quot;&gt; ]
</pre>
@ -302,21 +301,27 @@ or supervisor (note the three underscores):</p>
<p>This is basically all you need to know, as django_polymorphic mostly
works fully automatic and just delivers the expected (&quot;pythonic&quot;) results.</p>
<p>Note: In all example output, above and below, for a nicer and more informative
output the <cite>ShowFieldType</cite> mixin has been used (documented below).</p>
output the <tt class="docutils literal">ShowFieldType</tt> mixin has been used (documented below).</p>
</div>
</div>
<div class="section" id="list-of-features">
<h1><a class="toc-backref" href="#id3">List of Features</a></h1>
<ul class="simple">
<li>Fully automatic; generally makes sure that the same objects are returned
from the database that were stored there, regardless how they are retrieved</li>
<li>Fully automatic - generally makes sure that the same objects are
returned from the database that were stored there, regardless how
they are retrieved</li>
<li>Only on models that request polymorphic behaviour however (and the
models inheriting from them)</li>
<li>Full support for ForeignKeys, ManyToManyFields and OneToToneFields</li>
<li>Filtering for classes, equivalent to python's isinstance(): instance_of(...), not_instance_of(...)</li>
<li>Polymorphic filtering/ordering etc., allowing the use of fields of derived models (&quot;ArtProject___artist&quot;)</li>
<li>Filtering for classes, equivalent to python's isinstance():
<tt class="docutils literal"><span class="pre">instance_of(...)</span></tt> and <tt class="docutils literal"><span class="pre">not_instance_of(...)</span></tt></li>
<li>Polymorphic filtering/ordering etc., allowing the use of fields of
derived models (&quot;ArtProject___artist&quot;)</li>
<li>Support for user-defined custom managers</li>
<li>Automatic inheritance of custom managers</li>
<li>Support for user-defined custom queryset classes</li>
<li>Non-polymorphic queries, if needed - with no other change in features/behaviour</li>
<li>Non-polymorphic queries if needed, with no other change in
features/behaviour</li>
<li>Combining querysets of different types/models (&quot;qs3 = qs1 | qs2&quot;)</li>
<li>Nice/informative display of polymorphic queryset results</li>
</ul>
@ -401,8 +406,8 @@ syntax: <tt class="docutils literal">exact model name + three _ + field name</tt
&lt;ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)&gt; ]
</pre>
</div>
<div class="section" id="combining-querysets-querysets-as-object-containers">
<h2>Combining Querysets / Querysets as &quot;Object Containers&quot;</h2>
<div class="section" id="combining-querysets">
<h2>Combining Querysets</h2>
<p>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
@ -465,24 +470,23 @@ class MyModelWithThirdParty(MyBaseModel, ThirdPartyModel):
</div>
<div class="section" id="non-polymorphic-queries">
<h2>Non-Polymorphic Queries</h2>
<pre class="doctest-block">
&gt;&gt;&gt; qs=ModelA.objects.non_polymorphic().all()
&gt;&gt;&gt; qs
.
[ &lt;ModelA: id 1, field1 (CharField)&gt;,
&lt;ModelA: id 2, field1 (CharField)&gt;,
&lt;ModelA: id 3, field1 (CharField)&gt; ]
</pre>
<p>If you insert <tt class="docutils literal">.non_polymorphic()</tt> 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
(<tt class="docutils literal">ModelA</tt> in this example).</p>
<pre class="doctest-block">
&gt;&gt;&gt; qs=ModelA.objects.non_polymorphic().all()
&gt;&gt;&gt; qs
[ &lt;ModelA: id 1, field1 (CharField)&gt;,
&lt;ModelA: id 2, field1 (CharField)&gt;,
&lt;ModelA: id 3, field1 (CharField)&gt; ]
</pre>
<p>There are no other changes in the behaviour of the queryset. For example,
enhancements for <tt class="docutils literal">filter()</tt> or <tt class="docutils literal">instance_of()</tt> etc. still work as expected.
If you do the final step yourself, you get the usual polymorphic result:</p>
<pre class="doctest-block">
&gt;&gt;&gt; qs.get_real_instances()
&gt;&gt;&gt; ModelA.objects.get_real_instances(qs)
[ &lt;ModelA: id 1, field1 (CharField)&gt;,
&lt;ModelB: id 2, field1 (CharField), field2 (CharField)&gt;,
&lt;ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)&gt; ]
@ -554,12 +558,14 @@ class ModelA(ShowFieldType, PolymorphicModel):
<p>You may also use ShowFieldContent or ShowFieldTypeAndContent to display
additional information when printing querysets (or converting them to text).</p>
<p>When showing field contents, they will be truncated to 20 characters. You can
modify this behaviour by setting a class variable like this:</p>
modify this behaviour by setting a class variable in your model like this:</p>
<pre class="literal-block">
class ModelA(ShowFieldType, PolymorphicModel):
polymorphic_showfield_max_field_width = 20
...
</pre>
<p>Similarly, pre-V1.0 output formatting can be re-estated by using
<tt class="docutils literal">polymorphic_showfield_old_format = True</tt>.</p>
</div>
</div>
<div class="section" id="custom-managers-querysets-manager-inheritance">
@ -649,11 +655,6 @@ class MyModel(PolymorphicModel):
<p>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.</p>
<p>The advantages are that the implementation naturally works on all
supported database management systems, and consists of rather
clean source code which can be easily understood and enhanced.</p>
<p>The disadvantage is that this approach can not deliver the optimum
performance as it introduces additional database queries.</p>
<p>Specifically, the query:</p>
<pre class="literal-block">
result_objects = list( ModelA.objects.filter(...) )
@ -670,69 +671,27 @@ without a tool like django_polymorphic, this usually results in a variation of</
<pre class="literal-block">
result_objects = [ o.get_real_instance() for o in BaseModel.objects.filter(...) ]
</pre>
<p>which has exceptionally bad performance, as it introduces one additional
SQL query for every object in the result which is not of class <tt class="docutils literal">BaseModel</tt>.
Relative to this, the performance of the current django_polymorphic
implementation is very good.</p>
<p>If your project however needs perfect performance and the current
performance implications of django_polymorphic are not acceptable, then
basically there are the two options of either foregoing of an essential aspect
of object oriented programming or optimizing django_polymorphic.</p>
<p>Foregoing the benefits of this aspect of object oriented programming
for projects that could benefit from it will however usually lead to bloated code,
unnecessary complexity and considerably more of the programmer's time to
create and update the implementation, together with the disadvantages
of a less flexible and less future-proof solution. Throwing a little more
hardware on the problem might be the least expensive solution in most cases.</p>
</div>
<div class="section" id="possible-optimizations">
<h1><a class="toc-backref" href="#id8">Possible Optimizations</a></h1>
<p>Django_polymorphic can be optimized to require only one
SQL query for the queryset evaluation and retrieval of all objects.</p>
<p>Probably all that would be needed seems support for an additional
queryset function in Django's database layer, like:</p>
<pre class="literal-block">
ModelA.objects.join_models(on=&quot;field_name&quot;, models=[ModelB, ModelC])
</pre>
<p>or, less general but more simple:</p>
<pre class="literal-block">
ModelA.objects.join_tables(on=&quot;field_name&quot;, tables=['tableB','tableC'])
</pre>
<p>This would add additional left outer joins to the query and then add
the resulting fields from this join to the result objects.
E.g. a query for <tt class="docutils literal">ModelA</tt> objects would need to join the <tt class="docutils literal">ModelB</tt>
and <tt class="docutils literal">ModelC</tt> tables on the the field <tt class="docutils literal">id</tt> and add the fields <tt class="docutils literal">field2</tt>
and <tt class="docutils literal">field3</tt> from the joined tables to the resulting objects.</p>
<p>An optimization like this might require an SQL database.
For non-SQL databases the implementation could fall back to
the current ORM-only implementation.</p>
<div class="section" id="sql-complexity-of-an-optimized-implementation">
<h2>SQL Complexity of an Optimized Implementation</h2>
<p>With only one SQL query, one SQL join for each possible subclass
would be needed (<tt class="docutils literal">BaseModel.__subclasses__()</tt>, recursively).</p>
<p>With two SQL queries, the number of joins could be reduced to the
number of actuallly occurring subclasses in the specific result.</p>
<p>A perfect implementation might want to use one query only
if the number of possible subclasses (and therefore joins) is not
too large, and two queries otherwise (using the first query to
determine the actually occurring subclasses, reducing the number
of joins for the second).</p>
<p>The number of joins needed for polymorphic object retrieval might
raise concerns regarding the efficiency of these database
queries. It seems likely however, that the increased number of joins
is no problem for the supported DBM systems in all realistic use cases.
Should the number of joins of the more extreme use cases turn out to
be problematic, it is possible to split any problematic query into, for example,
two queries with only half the number of joins each.</p>
<p>It seems that further optimization (down to one DB request)
of django_polymorphic would be restricted to a relatively small area of
the code (&quot;query.py&quot;), and be pretty much independent from the rest of the module.
Such an optimization can be done at any later time (like when it's needed).</p>
<p>which has very bad performance, as it introduces one additional
SQL query for every object in the result which is not of class <tt class="docutils literal">BaseModel</tt>.</p>
<p>Compared to these solutions, django_polymorphic has the advantage
that it only needs one sql request per <em>object type</em>, and not <em>per object</em>.</p>
<div class="section" id="performance-problems-with-postgresql-mysql-and-sqlite3">
<span id="performance"></span><h2>Performance Problems with PostgreSQL, MySQL and SQLite3</h2>
<p>Current relational DBM systems seem to be 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 &quot;inner joins&quot; in these queries can perform very badly.
This is independent of django_polymorphic and affects all uses of
multi table Model inheritance.</p>
<p>Concrete benchmark results are forthcoming (please see discussion forum).</p>
<p>Please also see this <a class="reference external" href="http://www.jacobian.org/writing/concrete-inheritance/">post (and comments) from Jacob Kaplan-Moss</a>.</p>
</div>
</div>
<div class="section" id="restrictions-caveats">
<span id="restrictions"></span><h1><a class="toc-backref" href="#id9">Restrictions &amp; Caveats</a></h1>
<span id="restrictions"></span><h1><a class="toc-backref" href="#id8">Restrictions &amp; Caveats</a></h1>
<ul class="simple">
<li>Database Performance regarding concrete Model inheritance in general
Please see &quot;Performance Problems&quot; above.</li>
<li>Queryset methods <tt class="docutils literal">values()</tt>, <tt class="docutils literal">values_list()</tt>, <tt class="docutils literal">select_related()</tt>,
<tt class="docutils literal">defer()</tt> and <tt class="docutils literal">only()</tt> are not yet fully supported (see above).
<tt class="docutils literal">extra()</tt> has one restriction: the resulting objects are required to have
@ -772,13 +731,13 @@ use ContentType). This issue seems to be resolved for Django 1.2
</ul>
</div>
<div class="section" id="project-status">
<h1><a class="toc-backref" href="#id10">Project Status</a></h1>
<h1><a class="toc-backref" href="#id9">Project Status</a></h1>
<p>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.</p>
</div>
<div class="section" id="links">
<h1><a class="toc-backref" href="#id11">Links</a></h1>
<h1><a class="toc-backref" href="#id10">Links</a></h1>
<ul class="simple">
<li><a class="reference external" href="http://code.djangoproject.com/wiki/ModelInheritance">http://code.djangoproject.com/wiki/ModelInheritance</a></li>
<li><a class="reference external" href="http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html">http://lazypython.blogspot.com/2009/02/second-look-at-inheritance-and.html</a></li>

View File

@ -184,8 +184,8 @@ syntax: ``exact model name + three _ + field name``):
<ModelC: id 3, field1 (CharField), field2 (CharField), field3 (CharField)> ]
Combining Querysets / Querysets as "Object Containers"
------------------------------------------------------
Combining Querysets
-------------------
Querysets could now be regarded as object containers that allow the
aggregation of different object types, very similar to python
@ -454,13 +454,6 @@ 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.
The advantages are that the implementation naturally works on all
supported database management systems, and consists of rather
clean source code which can be easily understood and enhanced.
The disadvantage is that this approach can not deliver the optimum
performance as it introduces additional database queries.
Specifically, the query::
result_objects = list( ModelA.objects.filter(...) )
@ -478,76 +471,29 @@ 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 exceptionally bad performance, as it introduces one additional
which has very bad performance, as it introduces one additional
SQL query for every object in the result which is not of class ``BaseModel``.
Relative to this, the performance of the current django_polymorphic
implementation is very good.
If your project however needs perfect performance and the current
performance implications of django_polymorphic are not acceptable, then
basically there are the two options of either foregoing of an essential aspect
of object oriented programming or optimizing django_polymorphic.
Compared to these solutions, django_polymorphic has the advantage
that it only needs one sql request per *object type*, and not *per object*.
Foregoing the benefits of this aspect of object oriented programming
for projects that could benefit from it will however usually lead to bloated code,
unnecessary complexity and considerably more of the programmer's time to
create and update the implementation, together with the disadvantages
of a less flexible and less future-proof solution. Throwing a little more
hardware on the problem might be the least expensive solution in most cases.
.. _performance:
Performance Problems with PostgreSQL, MySQL and SQLite3
-------------------------------------------------------
Possible Optimizations
======================
Current relational DBM systems seem to be 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.
Django_polymorphic can be optimized to require only one
SQL query for the queryset evaluation and retrieval of all objects.
Concrete benchmark results are forthcoming (please see discussion forum).
Probably all that would be needed seems support for an additional
queryset function in Django's database layer, like::
Please also see this `post (and comments) from Jacob Kaplan-Moss`_.
ModelA.objects.join_models(on="field_name", models=[ModelB, ModelC])
or, less general but more simple::
ModelA.objects.join_tables(on="field_name", tables=['myapp_modelb','myapp_modelc'])
This would add additional left outer joins to the query and then add
the resulting fields from this join to the result objects.
E.g. a query for ``ModelA`` objects would need to join the ``ModelB``
and ``ModelC`` tables on the the field ``id`` and add the fields ``field2``
and ``field3`` from the joined tables to the resulting objects.
An optimization like this might require an SQL database.
For non-SQL databases the implementation could fall back to
the current ORM-only implementation.
SQL Complexity of an Optimized Implementation
---------------------------------------------
With only one SQL query, one SQL join for each possible subclass
would be needed (``BaseModel.__subclasses__()``, recursively).
With two SQL queries, the number of joins could be reduced to the
number of actuallly occurring subclasses in the specific result.
A perfect implementation might want to use one query only
if the number of possible subclasses (and therefore joins) is not
too large, and two queries otherwise (using the first query to
determine the actually occurring subclasses, reducing the number
of joins for the second).
The number of joins needed for polymorphic object retrieval might
raise concerns regarding the efficiency of these database
queries. It seems likely however, that the increased number of joins
is no problem for the supported DBM systems in all realistic use cases.
Should the number of joins of the more extreme use cases turn out to
be problematic, it is possible to split any problematic query into, for example,
two queries with only half the number of joins each.
It seems that further optimization (down to one DB request)
of django_polymorphic would be restricted to a relatively small area of
the code ("query.py"), and be pretty much independent from the rest of the module.
Such an optimization can be done at any later time (like when it's needed).
.. _post (and comments) from Jacob Kaplan-Moss: http://www.jacobian.org/writing/concrete-inheritance/
.. _restrictions:
@ -555,6 +501,9 @@ Such an optimization can be done at any later time (like when it's needed).
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

View File

@ -229,23 +229,16 @@ ul.auto-toc {
<li><a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html#quickstart">Quickstart</a>, or the complete <a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html">Installation and Usage Docs</a></li>
<li><a class="reference external" href="http://groups.google.de/group/django-polymorphic/topics">Release Notes, News and Discussion</a> (Google Group) or <a class="reference external" href="http://bserve.webhop.org/django_polymorphic/CHANGES.html">Changelog</a></li>
<li>Download from <a class="reference external" href="http://github.com/bconstantin/django_polymorphic">GitHub</a> or <a class="reference external" href="http://bitbucket.org/bconstantin/django_polymorphic">Bitbucket</a>, or as <a class="reference external" href="http://github.com/bconstantin/django_polymorphic/tarball/master">TGZ</a> or <a class="reference external" href="http://github.com/bconstantin/django_polymorphic/zipball/master">ZIP</a></li>
<li>Improve django_polymorphic, report issues, participate, discuss, patch or fork (<a class="reference external" href="http://github.com/bconstantin/django_polymorphic">GitHub</a>, <a class="reference external" href="http://bitbucket.org/bconstantin/django_polymorphic">Bitbucket</a>, <a class="reference external" href="http://groups.google.de/group/django-polymorphic/topics">Group</a>, <a class="reference external" href="http://github.com/bconstantin/django_polymorphic/tree/master/setup.py">Mail</a>)</li>
<li>Improve django_polymorphic, report issues, discuss, patch or fork (<a class="reference external" href="http://github.com/bconstantin/django_polymorphic">GitHub</a>, <a class="reference external" href="http://bitbucket.org/bconstantin/django_polymorphic">Bitbucket</a>, <a class="reference external" href="http://groups.google.de/group/django-polymorphic/topics">Group</a>, <a class="reference external" href="http://github.com/bconstantin/django_polymorphic/tree/master/setup.py">Mail</a>)</li>
</ul>
</div>
<div class="section" id="id1">
<span id="good-for"></span><h2>What is django_polymorphic good for?</h2>
<p>If you work with Django's model inheritance, django_polymorphic might
save you from implementing unpleasant workarounds that make your code
messy, error-prone, and slow. Model inheritance becomes much more &quot;pythonic&quot;
and now just works as you as a Python programmer expect.</p>
</div>
<div class="section" id="it-s-best-to-look-at-an-example">
<h2>It's best to Look at an Example</h2>
<p>Let's assume the models <tt class="docutils literal">ArtProject</tt> and <tt class="docutils literal">ResearchProject</tt> are derived
from the model <tt class="docutils literal">Project</tt>, and let's store one of each into the database:</p>
<pre class="doctest-block">
&gt;&gt;&gt; Project.objects.create(topic=&quot;John's Gathering&quot;)
&gt;&gt;&gt; ArtProject.objects.create(topic=&quot;Sculpting with Tim&quot;, artist=&quot;T. Turner&quot;)
&gt;&gt;&gt; Project.objects.create(topic=&quot;Department Party&quot;)
&gt;&gt;&gt; ArtProject.objects.create(topic=&quot;Painting with Tim&quot;, artist=&quot;T. Turner&quot;)
&gt;&gt;&gt; ResearchProject.objects.create(topic=&quot;Swallow Aerodynamics&quot;, supervisor=&quot;Dr. Winter&quot;)
</pre>
<p>If we want to retrieve all our projects, we do:</p>
@ -254,32 +247,39 @@ from the model <tt class="docutils literal">Project</tt>, and let's store one of
</pre>
<p>Using django_polymorphic, we simply get what we stored:</p>
<pre class="literal-block">
[ &lt;Project: id 1, topic &quot;John's Gathering&quot;&gt;,
[ &lt;Project: id 1, topic &quot;Department Party&quot;&gt;,
&lt;ArtProject: id 2, topic &quot;Painting with Tim&quot;, artist &quot;T. Turner&quot;&gt;,
&lt;ResearchProject: id 3, topic &quot;Swallow Aerodynamics&quot;, supervisor &quot;Dr. Winter&quot;&gt; ]
</pre>
<p>Using vanilla Django, we get incomplete objects, which is probably not what we wanted:</p>
<pre class="literal-block">
[ &lt;Project: id 1, topic &quot;John's Gathering&quot;&gt;,
[ &lt;Project: id 1, topic &quot;Department Party&quot;&gt;,
&lt;Project: id 2, topic &quot;Painting with Tim&quot;&gt;,
&lt;Project: id 3, topic &quot;Swallow Aerodynamics&quot;&gt; ]
</pre>
<p>It's very similar for ForeignKeys, ManyToManyFields or OneToOneFields.</p>
<p>In general, the effect of django_polymorphic is twofold:</p>
<p>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.
This can save you a lot of unpleasant workarounds.</p>
<p>On the other hand, together with a few 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.</p>
<p>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 &quot;pythonic&quot;.
This can save you a lot of unpleasant workarounds that tend to
make your code messy, error-prone, and slow.</p>
<p>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.</p>
<p>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 small layer above that, which is
all that is required to make real OO fully automatic and very easy to use.</p>
<p>For more information, please look at <a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html#quickstart">Quickstart</a> or the complete
<a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html">Installation and Usage Docs</a>. Please also see the <a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html#restrictions">restrictions and caveats</a>.</p>
Django_polymorphic adds a rather thin layer above that in order
to make real OO fully automatic and very easy to use.</p>
<p>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).</p>
<p>For more information, please look at <a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html#quickstart">Quickstart</a> or at the complete
<a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html">Installation and Usage Docs</a> and also see the <a class="reference external" href="http://bserve.webhop.org/django_polymorphic/DOCS.html#restrictions">restrictions and caveats</a>.</p>
</div>
<div class="section" id="this-is-a-v1-0-beta-testing-release">
<h2>This is a V1.0 Beta/Testing Release</h2>
@ -299,22 +299,23 @@ or open an issue on <a class="reference external" href="http://github.com/bconst
</div>
<div class="section" id="api-changes-additions">
<h1>API Changes &amp; Additions</h1>
<div class="section" id="october-26-2010-v1-0-api-changes">
<h2>October 26 2010, V1.0 API Changes</h2>
<div class="section" id="november-11-2010-v1-0-api-changes">
<h2>November 11 2010, V1.0 API Changes</h2>
<div class="section" id="extra-queryset-method">
<h3>extra() queryset method</h3>
<p><tt class="docutils literal">.extra()</tt> has been re-implemented. Now it's polymorphic by
default and works (nearly) without restrictions (please see docs). This is an
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 <tt class="docutils literal">polymorphic</tt> keyword parameter has been removed.
You can get back the non-polymorphic behaviour by using
<tt class="docutils literal"><span class="pre">ModelA.objects.non_polymorphic().extra()</span></tt>.</p>
</div>
<div class="section" id="output-of-queryset-or-object-printing">
<h3>Output of Queryset or Object Printing</h3>
<p>In order to improve compatibility with vanilla Django, printing quersets 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:</p>
<div class="section" id="no-pretty-printing-of-querysets-by-default">
<h3>No Pretty-Printing of Querysets by default</h3>
<p>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:</p>
<pre class="doctest-block">
&gt;&gt;&gt; class Project(PolymorphicModel):
</pre>
@ -330,6 +331,15 @@ To get the old behaviour when printing querysets, you need to replace your model
<tt class="docutils literal">ShowFieldType, ShowFieldContent and ShowFieldTypeAndContent</tt></blockquote>
<p>(the old ones still exist for compatibility)</p>
</div>
<div class="section" id="pretty-printing-output-format-changed">
<h3>Pretty-Printing Output Format Changed</h3>
<p><tt class="docutils literal">ShowFieldContent</tt> and <tt class="docutils literal">ShowFieldTypeAndContent</tt> 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
<tt class="docutils literal">polymorphic_showfield_old_format = True</tt> to your model definitions.
<tt class="docutils literal"><span class="pre">ShowField...</span></tt> now also produces more informative output for custom
primary keys.</p>
</div>
<div class="section" id="polymorphic-dumpdata">
<h3>polymorphic_dumpdata</h3>
<p>The <tt class="docutils literal">polymorphic_dumpdata</tt> management command is not needed anymore
@ -342,8 +352,8 @@ works correctly with polymorphic models (for all supported versions of Django).<
just <tt class="docutils literal">python manage.py test</tt>.</p>
</div>
</div>
<div class="section" id="october-26-2010-v1-0-api-additions">
<h2>October 26 2010, V1.0 API Additions</h2>
<div class="section" id="november-01-2010-v1-0-api-additions">
<h2>November 01 2010, V1.0 API Additions</h2>
<ul>
<li><p class="first"><tt class="docutils literal">.non_polymorphic()</tt> queryset member function added. This is preferable to
using <tt class="docutils literal"><span class="pre">.base_objects...</span></tt>, as it just makes the resulting queryset non-polymorphic

View File

@ -9,7 +9,7 @@ Quick Start, Docs, Contributing
* `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, participate, discuss, patch or fork (GitHub_, Bitbucket_, Group_, Mail_)
* 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
@ -32,8 +32,8 @@ 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:
>>> Project.objects.create(topic="John's Gathering")
>>> ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
>>> 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")
If we want to retrieve all our projects, we do:
@ -57,7 +57,7 @@ It's very similar for ForeignKeys, ManyToManyFields or OneToOneFields.
In general, the effect of django_polymorphic is twofold:
On one hand it makes sure that model inheritance just works as you
expect, by simply ensuring that you always get back exactly thesame
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
@ -70,11 +70,17 @@ that are not possible with vanilla Django.
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, which is
all that is required to make real OO fully automatic and very easy to use.
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`_.Please also see the `restrictions and caveats`_.
`Installation and Usage Docs`_ and also see the `restrictions and caveats`_.
.. _restrictions and caveats: http://bserve.webhop.org/django_polymorphic/DOCS.html#restrictions
@ -105,14 +111,14 @@ API Changes & Additions
=======================
November 01 2010, V1.0 API Changes
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 an
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
@ -121,9 +127,10 @@ You can get back the non-polymorphic behaviour by using
No Pretty-Printing of Querysets by default
++++++++++++++++++++++++++++++++++++++++++
In order to improve compatibility with vanilla Django, printing quersets 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:
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):
@ -148,7 +155,7 @@ Pretty-Printing Output Format Changed
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...`` also produces more informative output for custom
``ShowField...`` now also produces more informative output for custom
primary keys.
polymorphic_dumpdata

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""
This module is a scratchpad for general development, testing & debugging
Well, even more so than pcmd.py. You best ignore p2cmd.py.
"""
import uuid
@ -8,6 +9,7 @@ from django.core.management.base import NoArgsCommand
from django.db.models import connection
from pprint import pprint
import settings
import time,sys
from pexp.models import *
@ -17,6 +19,27 @@ def reset_queries():
def show_queries():
print; print 'QUERIES:',len(connection.queries); pprint(connection.queries); print; connection.queries=[]
def print_timing(func, message='', iterations=1):
def wrapper(*arg):
results=[]
reset_queries()
for i in xrange(iterations):
t1 = time.time()
x = func(*arg)
t2 = time.time()
results.append((t2-t1)*1000.0)
res_sum=0
for r in results: res_sum +=r
median = res_sum / len(results)
print '%s%-19s: %.4f ms, %i queries (%i times)' % (
message,func.func_name,
res_sum,
len(connection.queries),
iterations
)
sys.stdout.flush()
return wrapper
class Command(NoArgsCommand):
help = ""
@ -24,27 +47,64 @@ class Command(NoArgsCommand):
print 'polycmd - sqlite test db is stored in:',settings.SQLITE_DB_PATH
print
Project.objects.all().delete()
a=Project.objects.create(topic="John's gathering")
b=ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
c=ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
print Project.objects.all()
print
if False:
ModelA.objects.all().delete()
a=ModelA.objects.create(field1='A1')
b=ModelB.objects.create(field1='B1', field2='B2')
c=ModelC.objects.create(field1='C1', field2='C2', field3='C3')
<<<<<<< HEAD:pexp/management/commands/p2cmd.py
print ModelA.objects.extra( select={"select1": "field1 = 'A1'", "select2": "field1 = 'A0'"} )
=======
print ModelA.objects.extra( select={"select1": "field1 = 'A1'", "select2": "field1 != 'A1'"} )
>>>>>>> 7c2be35... pexp:pexp/management/commands/p2cmd.py
print
reset_queries()
print ModelC.base_objects.all();
show_queries()
if not 'UUIDField' in globals(): return
UUIDModelA.objects.all().delete()
a=UUIDModelA.objects.create(field1='012345678900123456789001234567890012345678900123456789001234567890')
b=UUIDModelB.objects.create(field1='B1', field2='B2')
c=UUIDModelC.objects.create(field1='C1', field2='C2', field3='C3')
print UUIDModelA.objects.all()
if False:
ModelA.objects.all().delete()
for i in xrange(1000):
a=ModelA.objects.create(field1=str(i%100))
b=ModelB.objects.create(field1=str(i%100), field2=str(i%200))
c=ModelC.objects.create(field1=str(i%100), field2=str(i%200), field3=str(i%300))
if i%100==0: print i
f=print_timing(poly_sql_query,iterations=1000)
f()
f=print_timing(poly_sql_query2,iterations=1000)
f()
return
nModelA.objects.all().delete()
a=nModelA.objects.create(field1='A1')
b=nModelB.objects.create(field1='B1', field2='B2')
c=nModelC.objects.create(field1='C1', field2='C2', field3='C3')
qs=ModelA.objects.raw("SELECT * from pexp_modela")
for o in list(qs): print o
from django.db import connection, transaction
from random import Random
rnd=Random()
def poly_sql_query():
cursor = connection.cursor()
cursor.execute("""
SELECT id, pexp_modela.field1, pexp_modelb.field2, pexp_modelc.field3
FROM pexp_modela
LEFT OUTER JOIN pexp_modelb
ON pexp_modela.id = pexp_modelb.modela_ptr_id
LEFT OUTER JOIN pexp_modelc
ON pexp_modelb.modela_ptr_id = pexp_modelc.modelb_ptr_id
WHERE pexp_modela.field1=%i
ORDER BY pexp_modela.id
""" % rnd.randint(0,100) )
#row=cursor.fetchone()
return
def poly_sql_query2():
cursor = connection.cursor()
cursor.execute("""
SELECT id, pexp_modela.field1
FROM pexp_modela
WHERE pexp_modela.field1=%i
ORDER BY pexp_modela.id
""" % rnd.randint(0,100) )
#row=cursor.fetchone()
return

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
This module is a scratchpad for general development, testing & debugging
This module is a scratchpad for general development, testing & debugging.
"""
from django.core.management.base import NoArgsCommand