pax_global_header00006660000000000000000000000064126405103150014507gustar00rootroot0000000000000052 comment=e9f549758f017046b980f90e8b576e15cd1b7909 django_polymorphic-0.8.1/000077500000000000000000000000001264051031500154045ustar00rootroot00000000000000django_polymorphic-0.8.1/.gitignore000066400000000000000000000002301264051031500173670ustar00rootroot00000000000000*.pyc *.pyo *.mo *.db *.egg-info/ *.egg/ .coverage .project .idea/ .pydevproject .idea/workspace.xml .tox/ .DS_Store build/ dist/ docs/_build/ htmlcov/ django_polymorphic-0.8.1/.travis.yml000066400000000000000000000021761264051031500175230ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.2" - "3.3" - "3.4" - "3.5" env: - DJANGO=">=1.4,<1.5" - DJANGO=">=1.5,<1.6" - DJANGO=">=1.6,<1.7" - DJANGO=">=1.7,<1.8" - DJANGO=">=1.8,<1.9" - DJANGO=">=1.9,<1.10" matrix: exclude: - python: "3.5" env: DJANGO=">=1.4,<1.5" - python: "3.5" env: DJANGO=">=1.5,<1.6" - python: "3.5" env: DJANGO=">=1.6,<1.7" - python: "3.5" env: DJANGO=">=1.7,<1.8" - python: "3.5" env: DJANGO=">=1.8,<1.9" - python: "3.4" env: DJANGO=">=1.4,<1.5" - python: "3.4" env: DJANGO=">=1.9,<1.10" - python: "3.3" env: DJANGO=">=1.4,<1.5" - python: "3.3" env: DJANGO=">=1.9,<1.10" - python: "3.2" env: DJANGO=">=1.4,<1.5" - python: "3.2" env: DJANGO=">=1.9,<1.10" - python: "2.6" env: DJANGO=">=1.7,<1.8" - python: "2.6" env: DJANGO=">=1.8,<1.9" - python: "2.6" env: DJANGO=">=1.9,<1.10" install: - pip install django$DJANGO coverage==3.6 script: - coverage run --source=polymorphic runtests.py - coverage report -m after_success: - pip install coveralls==0.2 - coveralls branches: only: - master django_polymorphic-0.8.1/AUTHORS.rst000066400000000000000000000013421264051031500172630ustar00rootroot00000000000000Main authors (commit rights to the main repository) =================================================== * Chris Glass * Diederik van der Boor Contributors ============= * Abel Daniel * Adam Wentz * Andrew Ingram (contributed setup.py) * Ben Konrath * Bertrand Bordage * Chad Shryock * Charles Leifer (python 2.4 compatibility) * David Sanders * Evan Borgstrom * Gavin Wahl * Germán M. Bravo * Hugo Osvaldo Barrera * Jacob Rief * Jedediah Smith (proxy models support) * John Furr * Jonas Obrist * Julian Wachholz * Kevin Armenat * Marius Lueck * Martin Brochhaus * Tomas Peterka * Vail Gold Former authors / maintainers ============================ * Bert Constantin 2009/2010 (Original author, disappeared from the internet :( ) django_polymorphic-0.8.1/LICENSE000066400000000000000000000030431264051031500164110ustar00rootroot00000000000000Copyright (c) 2009 or later by the individual contributors. Please see the AUTHORS file. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django_polymorphic-0.8.1/MANIFEST.in000066400000000000000000000001621264051031500171410ustar00rootroot00000000000000include README.rst include LICENSE include DOCS.rst include CHANGES.rst recursive-include polymorphic/templates * django_polymorphic-0.8.1/README.rst000066400000000000000000000046021264051031500170750ustar00rootroot00000000000000.. image:: https://travis-ci.org/chrisglass/django_polymorphic.png?branch=master :target: http://travis-ci.org/chrisglass/django_polymorphic :alt: build-status Polymorphic Models for Django ============================= 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 inherit from a ``Project`` model... >>> Project.objects.create(topic="Department Party") >>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") >>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") ...and want to retrieve all our projects, the subclassed models are returned! >>> Project.objects.all() [ , , ] Using vanilla Django, we get the base class objects, which is rarely what we wanted: >>> Project.objects.all() [ , , ] This also works when the polymorphic model is accessed via ForeignKeys, ManyToManyFields or OneToOneFields. Features -------- * Full admin integration. * ORM integration: * support for ForeignKey, ManyToManyField, OneToOneField descriptors. * Filtering/ordering of inherited 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 inherited 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! For more information, see the `documentation at Read the Docs `_. License ======= Django-polymorphic uses the same license as Django (BSD-like). django_polymorphic-0.8.1/docs/000077500000000000000000000000001264051031500163345ustar00rootroot00000000000000django_polymorphic-0.8.1/docs/Makefile000066400000000000000000000127541264051031500200050ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-polymorphic.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-polymorphic.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/django-polymorphic" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-polymorphic" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django_polymorphic-0.8.1/docs/admin.rst000066400000000000000000000112441264051031500201600ustar00rootroot00000000000000Django 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. By default, the non_polymorphic() method will be called on the queryset, so only the Parent model will be provided to the list template. This is to avoid the performance hit of retrieving child models. This can be controlled by setting the ``polymorphic_list`` property on the parent admin. Setting it to True will provide child models to the list template. 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. Polymorphic Inlines ------------------- To add a polymorphic child model as an Inline for another model, add a field to the inline's readonly_fields list formed by the lowercased name of the polymorphic parent model with the string "_ptr" appended to it. Otherwise, trying to save that model in the admin will raise an AttributeError with the message "can't set attribute". .. _admin-example: Example ------- The models are taken from :ref:`advanced-features`. .. code-block:: python from django.contrib import admin from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin from .models import ModelA, ModelB, ModelC, StandardModel 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): base_model = ModelB # define custom features here class ModelCAdmin(ModelBAdmin): base_model = ModelC # define custom features here class ModelAParentAdmin(PolymorphicParentModelAdmin): """ The parent model admin """ base_model = ModelA child_models = ( (ModelB, ModelBAdmin), (ModelC, ModelCAdmin), ) class ModelBInline(admin.StackedInline): model = ModelB fk_name = 'modelb' readonly_fields = ['modela_ptr'] class StandardModelAdmin(admin.ModelAdmin): inlines = [ModelBInline] # Only the parent needs to be registered: admin.site.register(ModelA, ModelAParentAdmin) admin.site.register(StandardModel, StandardModelAdmin) django_polymorphic-0.8.1/docs/advanced.rst000066400000000000000000000260531264051031500206410ustar00rootroot00000000000000.. _advanced-features: Advanced features ================= In the examples below, these models are being used:: from django.db import models from polymorphic.models import PolymorphicModel class ModelA(PolymorphicModel): field1 = models.CharField(max_length=10) class ModelB(ModelA): field2 = models.CharField(max_length=10) class ModelC(ModelB): field3 = models.CharField(max_length=10) Filtering for classes (equivalent to python's isinstance() ): ------------------------------------------------------------- >>> ModelA.objects.instance_of(ModelB) . [ , ] In general, including or excluding parts of the inheritance tree:: ModelA.objects.instance_of(ModelB [, ModelC ...]) ModelA.objects.not_instance_of(ModelB [, ModelC ...]) You can also use this feature in Q-objects (with the same result as above): >>> ModelA.objects.filter( Q(instance_of=ModelB) ) Polymorphic filtering (for fields in inherited classes) ------------------------------------------------------- For example, cherrypicking objects from multiple derived classes anywhere in the inheritance tree, using Q objects (with the syntax: ``exact model name + three _ + field name``): >>> ModelA.objects.filter( Q(ModelB___field2 = 'B2') | Q(ModelC___field3 = 'C3') ) . [ , ] Combining Querysets ------------------- Querysets could now be regarded as object containers that allow the aggregation of different object types, very similar to python lists - as long as the objects are accessed through the manager of a common base class: >>> Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) . [ , ] ManyToManyField, ForeignKey, OneToOneField ------------------------------------------ Relationship fields referring to polymorphic models work as expected: like polymorphic querysets they now always return the referred objects with the same type/class these were created and saved as. E.g., if in your model you define:: field1 = OneToOneField(ModelA) then field1 may now also refer to objects of type ``ModelB`` or ``ModelC``. A ManyToManyField example:: # The model holding the relation may be any kind of model, polymorphic or not class RelatingModel(models.Model): many2many = models.ManyToManyField('ModelA') # ManyToMany relation to a polymorphic model >>> o=RelatingModel.objects.create() >>> o.many2many.add(ModelA.objects.get(id=1)) >>> o.many2many.add(ModelB.objects.get(id=2)) >>> o.many2many.add(ModelC.objects.get(id=3)) >>> o.many2many.all() [ , , ] Using Third Party Models (without modifying them) ------------------------------------------------- Third party models can be used as polymorphic models without restrictions by subclassing them. E.g. using a third party model as the root of a polymorphic inheritance tree:: from thirdparty import ThirdPartyModel class MyThirdPartyBaseModel(PolymorphicModel, ThirdPartyModel): pass # or add fields Or instead integrating the third party model anywhere into an existing polymorphic inheritance tree:: class MyBaseModel(SomePolymorphicModel): my_field = models.CharField(max_length=10) class MyModelWithThirdParty(MyBaseModel, ThirdPartyModel): pass # or add fields Non-Polymorphic Queries ----------------------- If you insert ``.non_polymorphic()`` anywhere into the query chain, then django_polymorphic will simply leave out the final step of retrieving the real objects, and the manager/queryset will return objects of the type of the base class you used for the query, like vanilla Django would (``ModelA`` in this example). >>> qs=ModelA.objects.non_polymorphic().all() >>> qs [ , , ] There are no other changes in the behaviour of the queryset. For example, enhancements for ``filter()`` or ``instance_of()`` etc. still work as expected. If you do the final step yourself, you get the usual polymorphic result: >>> ModelA.objects.get_real_instances(qs) [ , , ] About Queryset Methods ---------------------- * ``annotate()`` and ``aggregate()`` work just as usual, with the addition that the ``ModelX___field`` syntax can be used for the keyword arguments (but not for the non-keyword arguments). * ``order_by()`` similarly supports the ``ModelX___field`` syntax for specifying ordering through a field in a submodel. * ``distinct()`` works as expected. It only regards the fields of the base class, but this should never make a difference. * ``select_related()`` works just as usual, but it can not (yet) be used to select relations in inherited 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. Occasionally 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.showfields 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 django_polymorphic-0.8.1/docs/changelog.rst000066400000000000000000000144641264051031500210260ustar00rootroot00000000000000Changelog ========== Version 0.8.1 (2015-12-29) -------------------------- * Fixed support for reverse relations for ``relname___field`` when the field starts with an ``_`` character. Otherwise, the query will be interpreted as subclass lookup (``ClassName___field``). Version 0.8 (2015-12-28) ------------------------ * Added Django 1.9 compatibility. * Renamed ``polymorphic.manager`` => ``polymorphic.managers`` for consistentcy. * **BACKWARDS INCOMPATIBILITY:** The import paths have changed to support Django 1.9. Instead of ``from polymorphic import X``, you'll have to import from the proper package. For example: .. code-block:: python polymorphic.models import PolymorphicModel polymorphic.managers import PolymorphicManager, PolymorphicQuerySet polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent * **BACKWARDS INCOMPATIBILITY:** Removed ``__version__.py`` in favor of a standard ``__version__`` in ``polymorphic/__init__.py``. * **BACKWARDS INCOMPATIBILITY:** Removed automatic proxying of method calls to the queryset class. Use the standard Django methods instead: .. code-block:: python # In model code: objects = PolymorphicQuerySet.as_manager() # For manager code: MyCustomManager = PolymorphicManager.from_queryset(MyCustomQuerySet) Version 0.7.2 (2015-10-01) -------------------------- * Added ``queryset.as_manager()`` support for Django 1.7/1.8 * Optimize model access for non-dumpdata usage; avoid ``__getattribute__()`` call each time to access the manager. * Fixed 500 error when using invalid PK's in the admin URL, return 404 instead. * Fixed possible issues when using an custom ``AdminSite`` class for the parent object. * Fixed Pickle exception when polymorphic model is cached. Version 0.7.1 (2015-04-30) -------------------------- * Fixed Django 1.8 support for related field widgets. Version 0.7 (2015-04-08) ------------------------ * Added Django 1.8 support * Added support for custom primary key defined using ``mybase_ptr = models.OneToOneField(BaseClass, parent_link=True, related_name="...")``. * Fixed Python 3 issue in the admin * Fixed ``_default_manager`` to be consistent with Django, it's now assigned directly instead of using ``add_to_class()`` * Fixed 500 error for admin URLs without a '/', e.g. ``admin/app/parentmodel/id``. * Fixed preserved filter for Django admin in delete views * Removed test noise for diamond inheritance problem (which Django 1.7 detects) Version 0.6.1 (2014-12-30) -------------------------- * Remove Django 1.7 warnings * Fix Django 1.4/1.5 queryset calls on related objects for unknown methods. The ``RelatedManager`` code overrides ``get_query_set()`` while ``__getattr__()`` used the new-style ``get_queryset()``. * Fix validate_model_fields(), caused errors when metaclass raises errors Version 0.6 (2014-10-14) ------------------------ * Added Django 1.7 support. * Added permission check for all child types. * **BACKWARDS INCOMPATIBILITY:** the ``get_child_type_choices()`` method receives 2 arguments now (request, action). If you have overwritten this method in your code, make sure the method signature is updated accordingly. Version 0.5.6 (2014-07-21) -------------------------- * Added ``pk_regex`` to the ``PolymorphicParentModelAdmin`` to support non-integer primary keys. * Fixed passing ``?ct_id=`` to the add view for Django 1.6 (fixes compatibility with django-parler_). Version 0.5.5 (2014-04-29) -------------------------- * Fixed ``get_real_instance_class()`` for proxy models (broke in 0.5.4). Version 0.5.4 (2014-04-09) -------------------------- * Fix ``.non_polymorphic()`` to returns a clone of the queryset, instead of effecting the existing queryset. * Fix missing ``alters_data = True`` annotations on the overwritten ``save()`` methods. * Fix infinite recursion bug in the admin with Django 1.6+ * Added detection of bad ``ContentType`` table data. Version 0.5.3 (2013-09-17) -------------------------- * Fix TypeError when ``base_form`` was not defined. * Fix passing ``/admin/app/model/id/XYZ`` urls to the correct admin backend. There is no need to include a ``?ct_id=..`` field, as the ID already provides enough information. Version 0.5.2 (2013-09-05) -------------------------- * Fix Grappelli_ breadcrumb support in the views. * Fix unwanted ``___`` handling in the ORM when a field name starts with an underscore; this detects you meant ``relatedfield__ _underscorefield`` instead of ``ClassName___field``. * Fix missing permission check in the "add type" view. This was caught however in the next step. * Fix admin validation errors related to additional non-model form fields. Version 0.5.1 (2013-07-05) -------------------------- * Add Django 1.6 support. * Fix Grappelli_ theme support in the "Add type" view. Version 0.5 (2013-04-20) ------------------------ * Add Python 3.2 and 3.3 support * Fix errors with ContentType objects that don't refer to an existing model. 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 (2011-04-27) ------------------------ 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 `. .. _Grappelli: http://grappelliproject.com/ .. _django-parler: https://github.com/edoburu/django-parler django_polymorphic-0.8.1/docs/changelog_archive.rst000066400000000000000000000261601264051031500225230ustar00rootroot00000000000000:orphan: Archive of old changelog entries ================================ 2011-01-24 V1.0 Release Candidate 1 ------------------------------------ * 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 disappeared from the internet, we undertook to maintain and upgrade this piece of software. 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. 2010-11-11 V1.0 Beta 2 ----------------------- 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. 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 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) 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(...)``. * API CHANGE: ``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. * ``.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()``: implementation modified to allow the following more simple and intuitive use:: >>> qs = ModelA.objects.all().non_polymorphic() >>> qs.get_real_instances() which is equivalent to:: >>> ModelA.objects.all() * added member function: ``normal_q_object = ModelA.translate_polymorphic_Q_object(enhanced_q_object)`` * misc changes/improvements Bugfixes ~~~~~~~~ * Custom fields could cause problems when used as the primary key. In inherited models, Django's automatic ".pk" field does not always work correctly for such custom fields: "some_object.pk" and "some_object.id" 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. "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 release is mostly a cleanup and maintenance release that also improves a number of minor things and fixes one (non-critical) bug. Some pending API changes and corrections have been folded into this release in order to make the upcoming V1.0 API as stable as possible. This release is also about getting feedback from you in case you don't approve of any of these changes or would like to get additional API fixes into V1.0. 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). If you encounter any such problems, please post them in the discussion group or open an issue on GitHub or BitBucket (or send me an email). 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 * ``PolymorphicModel.__getattribute__`` hack removed. This improves performance considerably as python's __getattribute__ generally causes a pretty large processing overhead. It's gone now. * 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). * ``.get_real_instances()`` has been elevated to an official part of the API:: real_objects = ModelA.objects.get_real_instances(base_objects_list_or_queryset) 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.base_objects.extra(...)`` and then want to transform the result to its polymorphic equivalent. * ``translate_polymorphic_Q_object`` (see DOCS) * improved testing * 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** The management command ``polymorphic_dumpdata`` 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). **Output of Queryset or Object Printing** 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) **Running the Test suite with Django 1.3** 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 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, ... + 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__ and accessed class members before calling the superclass __init__). Thanks to Mattias Brändström. Fix: There could be name conflicts if field_name == model_name.lower() or similar. Now it is possible to give a field the same name as the class (like with normal Django models). (Found through the example provided by Mattias Brändström) 2010-2-4 -------- New features (and documentation) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ queryset order_by method added queryset aggregate() and extra() methods implemented queryset annotate() method implemented queryset values(), values_list(), distinct() documented; defer(), only() allowed (but not yet supported) setup.py added. Thanks to Andrew Ingram. 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. * fix use of 'id' model field, replaced with 'pk'. * fix select_related bug for objects from derived classes (till now 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. 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. This issue seems to be resolved for Django 1.2 (changeset 11863: Fixed #7052, Added 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 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 also seems to be a generally good idea for future enhancements as well (and it makes sure the tests are always included). The ``poly`` app - until now being used for test purposes only - has been renamed to ``polymorphic``. See DOCS.rst ("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 the normal Django dumpdata command. Thanks to Charles Leifer. 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 to use polymorphic.py in earnest. Sorry for any inconvenience. This should be the final DB schema now. Django's ContentType is now used instead of app-label and model-name This is a cleaner and more efficient solution Thanks to Ilya Semenov for the suggestion. django_polymorphic-0.8.1/docs/conf.py000066400000000000000000000204201264051031500176310ustar00rootroot00000000000000# -*- 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 import 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.8.1' # The full version, including alpha/beta/rc tags. release = '0.8.1' # 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 = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'django-polymorphicdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'django-polymorphic.tex', u'django-polymorphic Documentation', u'Bert Constantin, Chris Glass, Diederik van der Boor', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'django-polymorphic', u'django-polymorphic Documentation', [u'Bert Constantin, Chris Glass, Diederik van der Boor'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'django-polymorphic', u'django-polymorphic Documentation', u'Bert Constantin, Chris Glass, Diederik van der Boor', 'django-polymorphic', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { #'http://docs.python.org/': None, 'https://docs.djangoproject.com/en/dev': 'https://docs.djangoproject.com/en/dev/_objects', } django_polymorphic-0.8.1/docs/contributing.rst000066400000000000000000000027221264051031500216000ustar00rootroot00000000000000Contributing ============ 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, 2.7, 3.2 and 3.3. Example project ---------------- The repository 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/ django_polymorphic-0.8.1/docs/index.rst000066400000000000000000000037171264051031500202050ustar00rootroot00000000000000Welcome 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 inherit from a ``Project`` model... >>> Project.objects.create(topic="Department Party") >>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") >>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") ...and want to retrieve all our projects, the subclassed models are returned! >>> Project.objects.all() [ , , ] Using vanilla Django, we get the base class objects, which is rarely what we wanted: >>> Project.objects.all() [ , , ] Features -------- * Full admin integration. * ORM integration: * Support for ForeignKey, ManyToManyField, OneToOneField descriptors. * Support for proxy models. * Filtering/ordering of inherited 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 minimum amount of queries needed to fetch the inherited models. * Disabling polymorphic behavior when needed. Getting started --------------- .. toctree:: :maxdepth: 2 quickstart admin performance Advanced topics --------------- .. toctree:: :maxdepth: 2 advanced managers third-party changelog contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` django_polymorphic-0.8.1/docs/make.bat000066400000000000000000000120001264051031500177320ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-polymorphic.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-polymorphic.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end django_polymorphic-0.8.1/docs/managers.rst000066400000000000000000000100141264051031500206570ustar00rootroot00000000000000Custom 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.models import PolymorphicModel from polymorphic.manager import PolymorphicManager class TimeOrderedManager(PolymorphicManager): def get_queryset(self): qs = super(TimeOrderedManager,self).get_queryset() return qs.order_by('-start_date') # order the queryset def most_recent(self): qs = self.get_queryset() # 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. Note that get_query_set is deprecated in Django 1.8 and creates warnings in Django 1.7. 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.models import PolymorphicModel from polymorphic.manager import PolymorphicManager class TimeOrderedManager(PolymorphicManager): def get_queryset(self): qs = super(TimeOrderedManager,self).get_queryset() return qs.order_by('-start_date') # order the queryset def most_recent(self): qs = self.get_queryset() # 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. . Note that get_query_set is deprecated in Django 1.8 and creates warnings in Django 1.7. 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.models import PolymorphicModel from polymorphic.manager import PolymorphicManager from polymorphic.query import PolymorphicQuerySet class MyQuerySet(PolymorphicQuerySet): def my_queryset_method(...): ... class MyModel(PolymorphicModel): my_objects=PolymorphicManager(MyQuerySet) ... django_polymorphic-0.8.1/docs/performance.rst000066400000000000000000000046331264051031500213750ustar00rootroot00000000000000.. _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 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``). ContentType retrieval --------------------- When fetching the :class:`~django.contrib.contenttypes.models.ContentType` class, it's tempting to read the ``object.polymorphic_ctype`` field directly. However, this performs an additional query via the :class:`~django.db.models.ForeignKey` object to fetch the :class:`~django.contrib.contenttypes.models.ContentType`. Instead, use: .. code-block:: python from django.contrib.contenttypes.models import ContentType ctype = ContentType.objects.get_for_id(object.polymorphic_ctype_id) This uses the :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_id` function which caches the results internally. 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 `_. django_polymorphic-0.8.1/docs/quickstart.rst000066400000000000000000000061611264051031500212640ustar00rootroot00000000000000Quickstart =========== 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 till 1.8 and Python 3 is supported. Making Your Models Polymorphic ------------------------------ Use ``PolymorphicModel`` instead of Django's ``models.Model``, like so:: from polymorphic.models import PolymorphicModel class Project(PolymorphicModel): topic = models.CharField(max_length=30) class ArtProject(Project): artist = models.CharField(max_length=30) class ResearchProject(Project): supervisor = models.CharField(max_length=30) All models inheriting from your polymorphic models will be polymorphic as well. Using Polymorphic Models ------------------------ Create some objects: >>> Project.objects.create(topic="Department Party") >>> ArtProject.objects.create(topic="Painting with Tim", artist="T. Turner") >>> ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") Get polymorphic query results: >>> Project.objects.all() [ , , ] Use ``instance_of`` or ``not_instance_of`` for narrowing the result to specific subtypes: >>> Project.objects.instance_of(ArtProject) [ ] >>> Project.objects.instance_of(ArtProject) | Project.objects.instance_of(ResearchProject) [ , ] Polymorphic filtering: Get all projects where Mr. Turner is involved as an artist or supervisor (note the three underscores): >>> Project.objects.filter(Q(ArtProject___artist='T. Turner') | Q(ResearchProject___supervisor='T. Turner')) [ , ] This is basically all you need to know, as *django-polymorphic* mostly works fully automatic and just delivers the expected results. Note: When using the ``dumpdata`` management command on polymorphic tables (or any table that has a reference to :class:`~django.contrib.contenttypes.models.ContentType`), include the ``--natural`` flag in the arguments. This makes sure the :class:`~django.contrib.contenttypes.models.ContentType` models will be referenced by name instead of their primary key as that changes between Django instances. .. note:: While *django-polymorphic* makes subclassed models easy to use in Django, we still encourage to use them with caution. Each subclassed model will require Django to perform an ``INNER JOIN`` to fetch the model fields from the database. While taking this in mind, there are valid reasons for using subclassed models. That's what this library is designed for! django_polymorphic-0.8.1/docs/third-party.rst000066400000000000000000000033361264051031500213420ustar00rootroot00000000000000Third-party applications support ================================ Django-reversion support ------------------------ `Django-reversion `_ works as expected with polymorphic models. However, they require more setup than standard models. We have to face these problems: * The children models are not registered in the admin site. You will therefore need to manually register them to django-reversion. * Polymorphic models use `multi-table inheritance `_. The django-reversion wiki explains `how to deal with this `_. Example ....... The admin :ref:`admin-example` becomes: .. code-block:: python from django.contrib import admin from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin import reversion from reversion import VersionAdmin from .models import ModelA, ModelB, ModelC class ModelAChildAdmin(PolymorphicChildModelAdmin): base_model = ModelA base_form = ... base_fieldsets = ( ... ) class ModelBAdmin(VersionAdmin, ModelAChildAdmin): # define custom features here class ModelCAdmin(ModelBAdmin): # define custom features here class ModelAParentAdmin(VersionAdmin, PolymorphicParentModelAdmin): base_model = ModelA child_models = ( (ModelB, ModelBAdmin), (ModelC, ModelCAdmin), ) reversion.register(ModelB, follow=['modela_ptr']) reversion.register(ModelC, follow=['modelb_ptr']) admin.site.register(ModelA, ModelAParentAdmin) django_polymorphic-0.8.1/example/000077500000000000000000000000001264051031500170375ustar00rootroot00000000000000django_polymorphic-0.8.1/example/example/000077500000000000000000000000001264051031500204725ustar00rootroot00000000000000django_polymorphic-0.8.1/example/example/__init__.py000066400000000000000000000000001264051031500225710ustar00rootroot00000000000000django_polymorphic-0.8.1/example/example/settings.py000066400000000000000000000046471264051031500227170ustar00rootroot00000000000000import django import os DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__)) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(PROJECT_ROOT, 'example.db'), } } SITE_ID = 1 # Make this unique, and don't share it with anybody. SECRET_KEY = '5$f%)&a4tc*bg(79+ku!7o$kri-duw99@hq_)va^_kaw9*l)!7' # Language # TIME_ZONE = 'America/Chicago' LANGUAGE_CODE = 'en-us' USE_I18N = True USE_L10N = True USE_TZ = True # Paths MEDIA_ROOT = '' MEDIA_URL = '/media/' STATIC_ROOT = '' STATIC_URL = '/static/' # Apps STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ) TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', ) ROOT_URLCONF = 'example.urls' WSGI_APPLICATION = 'example.wsgi.application' TEMPLATE_DIRS = () INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', #'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'polymorphic', # needed if you want to use the polymorphic admin 'pexp', # this Django app is for testing and experimentation; not needed otherwise ) if django.VERSION >= (1, 7): TEST_RUNNER = 'django.test.runner.DiscoverRunner' # silence system checks # Logging configuration LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, } } django_polymorphic-0.8.1/example/example/urls.py000066400000000000000000000005331264051031500220320ustar00rootroot00000000000000from django.conf.urls import include, url from django.contrib import admin from django.core.urlresolvers import reverse_lazy from django.views.generic import RedirectView admin.autodiscover() urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^$', RedirectView.as_view(url=reverse_lazy('admin:index'), permanent=False)), ] django_polymorphic-0.8.1/example/example/wsgi.py000066400000000000000000000021601264051031500220140ustar00rootroot00000000000000""" WSGI config for example project. This module contains the WSGI application used by Django's development server and any production WSGI deployments. It should expose a module-level variable named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover this application via the ``WSGI_APPLICATION`` setting. Usually you will have the standard Django WSGI application here, but it also might make sense to replace the whole Django WSGI application with a custom one that later delegates to the Django one. For example, you could introduce WSGI middleware here, or combine a Django application with an application of another framework. """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) django_polymorphic-0.8.1/example/manage.py000077500000000000000000000006231264051031500206450ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") # Import polymorphic from this folder. SRC_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) sys.path.insert(0, SRC_ROOT) from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django_polymorphic-0.8.1/example/pexp/000077500000000000000000000000001264051031500200135ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/__init__.py000066400000000000000000000000001264051031500221120ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/admin.py000066400000000000000000000044351264051031500214630ustar00rootroot00000000000000from django.contrib import admin from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter from pexp.models import * class ProjectChildAdmin(PolymorphicChildModelAdmin): base_model = Project # On purpose, only have the shared fields here. # The fields of the derived model should still be displayed. base_fieldsets = ( ("Base fields", { 'fields': ('topic',) }), ) class ProjectAdmin(PolymorphicParentModelAdmin): base_model = Project list_filter = (PolymorphicChildModelFilter,) child_models = ( (Project, ProjectChildAdmin), (ArtProject, ProjectChildAdmin), (ResearchProject, ProjectChildAdmin), ) admin.site.register(Project, ProjectAdmin) class ModelAChildAdmin(PolymorphicChildModelAdmin): base_model = ModelA class ModelAAdmin(PolymorphicParentModelAdmin): base_model = ModelA list_filter = (PolymorphicChildModelFilter,) child_models = ( (ModelA, ModelAChildAdmin), (ModelB, ModelAChildAdmin), (ModelC, ModelAChildAdmin), ) admin.site.register(ModelA, ModelAAdmin) class Model2AChildAdmin(PolymorphicChildModelAdmin): base_model = Model2A class Model2AAdmin(PolymorphicParentModelAdmin): base_model = Model2A list_filter = (PolymorphicChildModelFilter,) child_models = ( (Model2A, Model2AChildAdmin), (Model2B, Model2AChildAdmin), (Model2C, Model2AChildAdmin), ) admin.site.register(Model2A, Model2AAdmin) class UUIDModelAChildAdmin(PolymorphicChildModelAdmin): base_model = UUIDModelA class UUIDModelAAdmin(PolymorphicParentModelAdmin): base_model = UUIDModelA list_filter = (PolymorphicChildModelFilter,) child_models = ( (UUIDModelA, UUIDModelAChildAdmin), (UUIDModelB, UUIDModelAChildAdmin), (UUIDModelC, UUIDModelAChildAdmin), ) admin.site.register(UUIDModelA, UUIDModelAAdmin) class ProxyChildAdmin(PolymorphicChildModelAdmin): base_model = ProxyBase class ProxyAdmin(PolymorphicParentModelAdmin): base_model = ProxyBase list_filter = (PolymorphicChildModelFilter,) child_models = ( (ProxyA, ProxyChildAdmin), (ProxyB, ProxyChildAdmin), ) admin.site.register(ProxyBase, ProxyAdmin) django_polymorphic-0.8.1/example/pexp/dumpdata_test_correct_output.txt000066400000000000000000000014431264051031500265550ustar00rootroot00000000000000[ { "pk": 1, "model": "pexp.project", "fields": { "topic": "John's gathering", "polymorphic_ctype": 2 } }, { "pk": 2, "model": "pexp.project", "fields": { "topic": "Sculpting with Tim", "polymorphic_ctype": 3 } }, { "pk": 3, "model": "pexp.project", "fields": { "topic": "Swallow Aerodynamics", "polymorphic_ctype": 4 } }, { "pk": 2, "model": "pexp.artproject", "fields": { "artist": "T. Turner" } }, { "pk": 3, "model": "pexp.researchproject", "fields": { "supervisor": "Dr. Winter" } } ] django_polymorphic-0.8.1/example/pexp/management/000077500000000000000000000000001264051031500221275ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/management/__init__.py000066400000000000000000000000001264051031500242260ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/management/commands/000077500000000000000000000000001264051031500237305ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/management/commands/__init__.py000066400000000000000000000000001264051031500260270ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/management/commands/p2cmd.py000066400000000000000000000066541264051031500253220ustar00rootroot00000000000000# -*- 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 import django from django.core.management.base import NoArgsCommand from django.db import connection from pprint import pprint import time import sys from pexp.models import * def reset_queries(): if django.VERSION < (1, 9): connection.queries = [] else: connection.queries_log.clear() 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 = "" def handle_noargs(self, **options): 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') reset_queries() print ModelC.base_objects.all() show_queries() 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 django_polymorphic-0.8.1/example/pexp/management/commands/pcmd.py000066400000000000000000000021561264051031500252310ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module is a scratchpad for general development, testing & debugging. """ from django.core.management.base import NoArgsCommand from django.db import connection from pprint import pprint from pexp.models import * def reset_queries(): connection.queries = [] def show_queries(): print print 'QUERIES:', len(connection.queries) pprint(connection.queries) print connection.queries = [] class Command(NoArgsCommand): help = "" def handle_noargs(self, **options): 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 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') print ModelA.objects.all() print django_polymorphic-0.8.1/example/pexp/management/commands/polybench.py000066400000000000000000000055011264051031500262660ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module is a scratchpad for general development, testing & debugging """ import django from django.core.management.base import NoArgsCommand from django.db import connection from pprint import pprint import sys from pexp.models import * num_objects = 1000 def reset_queries(): if django.VERSION < (1, 9): connection.queries = [] else: connection.queries_log.clear() def show_queries(): print print 'QUERIES:', len(connection.queries) pprint(connection.queries) print reset_queries() import time ################################################################################### # benchmark wrappers 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: %.0f ms, %i queries' % ( message, func.func_name, median, len(connection.queries) / len(results) ) sys.stdout.flush() return wrapper def run_vanilla_any_poly(func, iterations=1): f = print_timing(func, ' ', iterations) f(nModelC) f = print_timing(func, 'poly ', iterations) f(ModelC) ################################################################################### # benchmarks def bench_create(model): for i in xrange(num_objects): model.objects.create(field1='abc' + str(i), field2='abcd' + str(i), field3='abcde' + str(i)) # print 'count:',model.objects.count() def bench_load1(model): for o in model.objects.all(): pass def bench_load1_short(model): for i in xrange(num_objects / 100): for o in model.objects.all()[:100]: pass def bench_load2(model): for o in model.objects.all(): f1 = o.field1 f2 = o.field2 f3 = o.field3 def bench_load2_short(model): for i in xrange(num_objects / 100): for o in model.objects.all()[:100]: f1 = o.field1 f2 = o.field2 f3 = o.field3 def bench_delete(model): model.objects.all().delete() ################################################################################### # Command class Command(NoArgsCommand): help = "" def handle_noargs(self, **options): func_list = [ (bench_delete, 1), (bench_create, 1), (bench_load1, 5), (bench_load1_short, 5), (bench_load2, 5), (bench_load2_short, 5) ] for f, iterations in func_list: run_vanilla_any_poly(f, iterations=iterations) print django_polymorphic-0.8.1/example/pexp/management/commands/polymorphic_create_test_data.py000066400000000000000000000015411264051031500322230ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module is a scratchpad for general development, testing & debugging """ from django.core.management.base import NoArgsCommand from django.db import connection from pprint import pprint from pexp.models import * def reset_queries(): connection.queries = [] def show_queries(): print print 'QUERIES:', len(connection.queries) pprint(connection.queries) print connection.queries = [] class Command(NoArgsCommand): help = "" def handle_noargs(self, **options): Project.objects.all().delete() o = Project.objects.create(topic="John's gathering") o = ArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner") o = ResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") print Project.objects.all() print django_polymorphic-0.8.1/example/pexp/migrations/000077500000000000000000000000001264051031500221675ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/migrations/0001_initial.py000066400000000000000000000215561264051031500246430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9b1 on 2015-10-23 22:20 from __future__ import unicode_literals from django.db import migrations, models import django.db.models.deletion import polymorphic.showfields class Migration(migrations.Migration): initial = True dependencies = [ ('contenttypes', '0002_remove_content_type_name'), ] operations = [ migrations.CreateModel( name='Model2A', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('field1', models.CharField(max_length=10)), ], options={ 'abstract': False, }, ), migrations.CreateModel( name='ModelA', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('field1', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name='nModelA', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('field1', models.CharField(max_length=10)), ], ), migrations.CreateModel( name='Project', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('topic', models.CharField(max_length=30)), ], options={ 'abstract': False, }, bases=(polymorphic.showfields.ShowFieldContent, models.Model), ), migrations.CreateModel( name='ProxyBase', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=200)), ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_pexp.proxybase_set+', to='contenttypes.ContentType')), ], options={ 'ordering': ('title',), }, ), migrations.CreateModel( name='UUIDModelA', fields=[ ('uuid_primary_key', models.UUIDField(primary_key=True, serialize=False)), ('field1', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name='ArtProject', fields=[ ('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.Project')), ('artist', models.CharField(max_length=30)), ], options={ 'abstract': False, }, bases=('pexp.project',), ), migrations.CreateModel( name='Model2B', fields=[ ('model2a_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.Model2A')), ('field2', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=('pexp.model2a',), ), migrations.CreateModel( name='ModelB', fields=[ ('modela_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.ModelA')), ('field2', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=('pexp.modela',), ), migrations.CreateModel( name='nModelB', fields=[ ('nmodela_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.nModelA')), ('field2', models.CharField(max_length=10)), ], bases=('pexp.nmodela',), ), migrations.CreateModel( name='ResearchProject', fields=[ ('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.Project')), ('supervisor', models.CharField(max_length=30)), ], options={ 'abstract': False, }, bases=('pexp.project',), ), migrations.CreateModel( name='UUIDModelB', fields=[ ('uuidmodela_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.UUIDModelA')), ('field2', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=('pexp.uuidmodela',), ), migrations.AddField( model_name='uuidmodela', name='polymorphic_ctype', field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_pexp.uuidmodela_set+', to='contenttypes.ContentType'), ), migrations.AddField( model_name='project', name='polymorphic_ctype', field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_pexp.project_set+', to='contenttypes.ContentType'), ), migrations.AddField( model_name='modela', name='polymorphic_ctype', field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_pexp.modela_set+', to='contenttypes.ContentType'), ), migrations.AddField( model_name='model2a', name='polymorphic_ctype', field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_pexp.model2a_set+', to='contenttypes.ContentType'), ), migrations.CreateModel( name='ProxyA', fields=[ ], options={ 'proxy': True, }, bases=('pexp.proxybase',), ), migrations.CreateModel( name='ProxyB', fields=[ ], options={ 'proxy': True, }, bases=('pexp.proxybase',), ), migrations.CreateModel( name='Model2C', fields=[ ('model2b_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.Model2B')), ('field3', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=('pexp.model2b',), ), migrations.CreateModel( name='ModelC', fields=[ ('modelb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.ModelB')), ('field3', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=('pexp.modelb',), ), migrations.CreateModel( name='nModelC', fields=[ ('nmodelb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.nModelB')), ('field3', models.CharField(max_length=10)), ], bases=('pexp.nmodelb',), ), migrations.CreateModel( name='UUIDModelC', fields=[ ('uuidmodelb_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pexp.UUIDModelB')), ('field3', models.CharField(max_length=10)), ], options={ 'abstract': False, }, bases=('pexp.uuidmodelb',), ), ] django_polymorphic-0.8.1/example/pexp/migrations/0002_modelc_field4.py000066400000000000000000000007251264051031500257000ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Generated by Django 1.9b1 on 2015-10-24 01:42 from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('pexp', '0001_initial'), ] operations = [ migrations.AddField( model_name='modelc', name='field4', field=models.ManyToManyField(related_name='related_c', to='pexp.ModelB'), ), ] django_polymorphic-0.8.1/example/pexp/migrations/__init__.py000066400000000000000000000000001264051031500242660ustar00rootroot00000000000000django_polymorphic-0.8.1/example/pexp/models.py000066400000000000000000000042211264051031500216470ustar00rootroot00000000000000# -*- coding: utf-8 -*- import django from django.db import models from polymorphic.models import PolymorphicModel from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent class Project(ShowFieldContent, 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) class ModelA(ShowFieldTypeAndContent, 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) field4 = models.ManyToManyField(ModelB, related_name='related_c') class nModelA(models.Model): field1 = models.CharField(max_length=10) class nModelB(nModelA): field2 = models.CharField(max_length=10) class nModelC(nModelB): field3 = models.CharField(max_length=10) class Model2A(PolymorphicModel): field1 = models.CharField(max_length=10) class Model2B(Model2A): field2 = models.CharField(max_length=10) class Model2C(Model2B): field3 = models.CharField(max_length=10) if django.VERSION < (1, 8): from polymorphic.tools_for_tests import UUIDField else: from django.db.models import UUIDField class UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel): uuid_primary_key = UUIDField(primary_key=True) field1 = models.CharField(max_length=10) class UUIDModelB(UUIDModelA): field2 = models.CharField(max_length=10) class UUIDModelC(UUIDModelB): field3 = models.CharField(max_length=10) class ProxyBase(PolymorphicModel): title = models.CharField(max_length=200) def __unicode__(self): return u"".format(self.polymorphic_ctype, self.title) class Meta: ordering = ('title',) class ProxyA(ProxyBase): class Meta: proxy = True def __unicode__(self): return u"".format(self.title) class ProxyB(ProxyBase): class Meta: proxy = True def __unicode__(self): return u"".format(self.title) django_polymorphic-0.8.1/polymorphic/000077500000000000000000000000001264051031500177515ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/__init__.py000066400000000000000000000024221264051031500220620ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Seamless Polymorphic Inheritance for Django Models Copyright: This code and affiliated files are (C) by Bert Constantin and individual contributors. Please see LICENSE and AUTHORS for more information. """ # See PEP 440 (https://www.python.org/dev/peps/pep-0440/) __version__ = "0.8.1" # Monkey-patch Django < 1.5 to allow ContentTypes for proxy models. import django if django.VERSION[:2] < (1, 5): from django.contrib.contenttypes.models import ContentTypeManager from django.utils.encoding import smart_text def get_for_model(self, model, for_concrete_model=True): if for_concrete_model: model = model._meta.concrete_model elif model._deferred: model = model._meta.proxy_for_model opts = model._meta try: ct = self._get_from_cache(opts) except KeyError: ct, created = self.get_or_create( app_label=opts.app_label, model=opts.object_name.lower(), defaults={'name': smart_text(opts.verbose_name_raw)}, ) self._add_to_cache(self.db, ct) return ct ContentTypeManager.get_for_model__original = ContentTypeManager.get_for_model ContentTypeManager.get_for_model = get_for_model django_polymorphic-0.8.1/polymorphic/admin.py000066400000000000000000000552721264051031500214260ustar00rootroot00000000000000""" ModelAdmin code to display polymorphic models. """ import sys from django import forms from django.conf.urls import url from django.contrib import admin from django.contrib.admin.helpers import AdminForm, AdminErrorList from django.contrib.admin.widgets import AdminRadioSelect from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.urlresolvers import RegexURLResolver from django.http import Http404, HttpResponseRedirect from django.shortcuts import render_to_response from django.template.context import RequestContext from django.utils import six from django.utils.encoding import force_text from django.utils.http import urlencode from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ import django try: # Django 1.6 implements this from django.contrib.admin.templatetags.admin_urls import add_preserved_filters except ImportError: def add_preserved_filters(context, form_url): return form_url if sys.version_info[0] >= 3: long = int __all__ = ( 'PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin', 'PolymorphicChildModelAdmin', 'PolymorphicChildModelFilter' ) class RegistrationClosed(RuntimeError): "The admin model can't be registered anymore at this point." pass class ChildAdminNotRegistered(RuntimeError): "The admin site for the model is not registered." pass class PolymorphicModelChoiceForm(forms.Form): """ The default form for the ``add_type_form``. Can be overwritten and replaced. """ #: Define the label for the radiofield type_label = _('Type') ct_id = forms.ChoiceField(label=type_label, widget=AdminRadioSelect(attrs={'class': 'radiolist'})) def __init__(self, *args, **kwargs): # Allow to easily redefine the label (a commonly expected usecase) super(PolymorphicModelChoiceForm, self).__init__(*args, **kwargs) self.fields['ct_id'].label = self.type_label class PolymorphicChildModelFilter(admin.SimpleListFilter): """ An admin list filter for the PolymorphicParentModelAdmin which enables filtering by its child models. """ title = _('Type') parameter_name = 'polymorphic_ctype' def lookups(self, request, model_admin): return model_admin.get_child_type_choices(request, 'change') def queryset(self, request, queryset): try: value = int(self.value()) except TypeError: value = None if value: # ensure the content type is allowed for choice_value, _ in self.lookup_choices: if choice_value == value: return queryset.filter(polymorphic_ctype_id=choice_value) raise PermissionDenied( 'Invalid ContentType "{0}". It must be registered as child model.'.format(value)) return queryset class PolymorphicParentModelAdmin(admin.ModelAdmin): """ A admin interface that can displays different change/delete pages, depending on the polymorphic model. To use this class, two variables need to be defined: * :attr:`base_model` should * :attr:`child_models` should be a list of (Model, Admin) tuples Alternatively, the following methods can be implemented: * :func:`get_child_models` should return a list of (Model, ModelAdmin) tuples * optionally, :func:`get_child_type_choices` can be overwritten to refine the choices for the add dialog. This class needs to be inherited by the model admin base class that is registered in the site. The derived models should *not* register the ModelAdmin, but instead it should be returned by :func:`get_child_models`. """ #: The base model that the class uses base_model = None #: The child models that should be displayed child_models = None #: Whether the list should be polymorphic too, leave to ``False`` to optimize polymorphic_list = False add_type_template = None add_type_form = PolymorphicModelChoiceForm #: The regular expression to filter the primary key in the URL. #: This accepts only numbers as defensive measure against catch-all URLs. #: If your primary key consists of string values, update this regular expression. pk_regex = '(\d+|__fk__)' def __init__(self, model, admin_site, *args, **kwargs): super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs) self._child_admin_site = self.admin_site.__class__(name=self.admin_site.name) self._is_setup = False def _lazy_setup(self): if self._is_setup: return # By not having this in __init__() there is less stress on import dependencies as well, # considering an advanced use cases where a plugin system scans for the child models. child_models = self.get_child_models() for Model, Admin in child_models: self.register_child(Model, Admin) self._child_models = dict(child_models) # This is needed to deal with the improved ForeignKeyRawIdWidget in Django 1.4 and perhaps other widgets too. # The ForeignKeyRawIdWidget checks whether the referenced model is registered in the admin, otherwise it displays itself as a textfield. # As simple solution, just make sure all parent admin models are also know in the child admin site. # This should be done after all parent models are registered off course. complete_registry = self.admin_site._registry.copy() complete_registry.update(self._child_admin_site._registry) self._child_admin_site._registry = complete_registry self._is_setup = True def register_child(self, model, model_admin): """ Register a model with admin to display. """ # After the get_urls() is called, the URLs of the child model can't be exposed anymore to the Django URLconf, # which also means that a "Save and continue editing" button won't work. if self._is_setup: raise RegistrationClosed("The admin model can't be registered anymore at this point.") if not issubclass(model, self.base_model): raise TypeError("{0} should be a subclass of {1}".format(model.__name__, self.base_model.__name__)) if not issubclass(model_admin, admin.ModelAdmin): raise TypeError("{0} should be a subclass of {1}".format(model_admin.__name__, admin.ModelAdmin.__name__)) self._child_admin_site.register(model, model_admin) def get_child_models(self): """ Return the derived model classes which this admin should handle. This should return a list of tuples, exactly like :attr:`child_models` is. The model classes can be retrieved as ``base_model.__subclasses__()``, a setting in a config file, or a query of a plugin registration system at your option """ if self.child_models is None: raise NotImplementedError("Implement get_child_models() or child_models") return self.child_models def get_child_type_choices(self, request, action): """ Return a list of polymorphic types for which the user has the permission to perform the given action. """ choices = [] for model, _ in self.get_child_models(): perm_function_name = 'has_{0}_permission'.format(action) model_admin = self._get_real_admin_by_model(model) perm_function = getattr(model_admin, perm_function_name) if not perm_function(request): continue ct = ContentType.objects.get_for_model(model, for_concrete_model=False) choices.append((ct.id, model._meta.verbose_name)) return choices def _get_real_admin(self, object_id): try: obj = self.model.objects.non_polymorphic() \ .values('polymorphic_ctype').get(pk=object_id) except self.model.DoesNotExist: raise Http404 return self._get_real_admin_by_ct(obj['polymorphic_ctype']) def _get_real_admin_by_ct(self, ct_id): try: ct = ContentType.objects.get_for_id(ct_id) except ContentType.DoesNotExist as e: raise Http404(e) # Handle invalid GET parameters model_class = ct.model_class() if not model_class: raise Http404("No model found for '{0}.{1}'.".format(*ct.natural_key())) # Handle model deletion return self._get_real_admin_by_model(model_class) def _get_real_admin_by_model(self, model_class): # In case of a ?ct_id=### parameter, the view is already checked for permissions. # Hence, make sure this is a derived object, or risk exposing other admin interfaces. if model_class not in self._child_models: raise PermissionDenied("Invalid model '{0}', it must be registered as child model.".format(model_class)) try: # HACK: the only way to get the instance of an model admin, # is to read the registry of the AdminSite. return self._child_admin_site._registry[model_class] except KeyError: raise ChildAdminNotRegistered("No child admin site was registered for a '{0}' model.".format(model_class)) def get_queryset(self, request): # optimize the list display. qs = super(PolymorphicParentModelAdmin, self).get_queryset(request) if not self.polymorphic_list: qs = qs.non_polymorphic() return qs # For Django 1.5: def queryset(self, request): qs = super(PolymorphicParentModelAdmin, self).queryset(request) if not self.polymorphic_list: qs = qs.non_polymorphic() return qs def add_view(self, request, form_url='', extra_context=None): """Redirect the add view to the real admin.""" ct_id = int(request.GET.get('ct_id', 0)) if not ct_id: # Display choices return self.add_type_view(request) else: real_admin = self._get_real_admin_by_ct(ct_id) # rebuild form_url, otherwise libraries below will override it. form_url = add_preserved_filters({ 'preserved_filters': urlencode({'ct_id': ct_id}), 'opts': self.model._meta}, form_url ) return real_admin.add_view(request, form_url, extra_context) def change_view(self, request, object_id, *args, **kwargs): """Redirect the change view to the real admin.""" real_admin = self._get_real_admin(object_id) return real_admin.change_view(request, object_id, *args, **kwargs) def delete_view(self, request, object_id, extra_context=None): """Redirect the delete view to the real admin.""" real_admin = self._get_real_admin(object_id) return real_admin.delete_view(request, object_id, extra_context) def get_preserved_filters(self, request): if '_changelist_filters' in request.GET: request.GET = request.GET.copy() filters = request.GET.get('_changelist_filters') f = filters.split("&") for x in f: c = x.split('=') request.GET[c[0]] = c[1] del request.GET['_changelist_filters'] return super(PolymorphicParentModelAdmin, self).get_preserved_filters(request) def get_urls(self): """ Expose the custom URLs for the subclasses and the URL resolver. """ urls = super(PolymorphicParentModelAdmin, self).get_urls() info = _get_opt(self.model) # Patch the change view URL so it's not a big catch-all; allowing all # custom URLs to be added to the end. This is done by adding '/$' to the # end of the regex. The url needs to be recreated, patching url.regex # is not an option Django 1.4's LocaleRegexProvider changed it. if django.VERSION < (1, 9): # On Django 1.9, the change view URL has been changed from # //// to ////change/, which is # why we can skip this workaround for Django >= 1.9. new_change_url = url( r'^{0}/$'.format(self.pk_regex), self.admin_site.admin_view(self.change_view), name='{0}_{1}_change'.format(*info) ) for i, oldurl in enumerate(urls): if oldurl.name == new_change_url.name: urls[i] = new_change_url # Define the catch-all for custom views custom_urls = [ url(r'^(?P.+)$', self.admin_site.admin_view(self.subclass_view)) ] # At this point. all admin code needs to be known. self._lazy_setup() # Add reverse names for all polymorphic models, so the delete button and "save and add" just work. # These definitions are masked by the definition above, since it needs special handling (and a ct_id parameter). dummy_urls = [] for model, _ in self.get_child_models(): admin = self._get_real_admin_by_model(model) dummy_urls += admin.get_urls() return urls + custom_urls + dummy_urls def subclass_view(self, request, path): """ Forward any request to a custom view of the real admin. """ ct_id = int(request.GET.get('ct_id', 0)) if not ct_id: # See if the path started with an ID. try: pos = path.find('/') if pos == -1: object_id = long(path) else: object_id = long(path[0:pos]) except ValueError: raise Http404("No ct_id parameter, unable to find admin subclass for path '{0}'.".format(path)) ct_id = self.model.objects.values_list('polymorphic_ctype_id', flat=True).get(pk=object_id) real_admin = self._get_real_admin_by_ct(ct_id) resolver = RegexURLResolver('^', real_admin.urls) resolvermatch = resolver.resolve(path) # May raise Resolver404 if not resolvermatch: raise Http404("No match for path '{0}' in admin subclass.".format(path)) return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs) def add_type_view(self, request, form_url=''): """ Display a choice form to select which page type to add. """ if not self.has_add_permission(request): raise PermissionDenied extra_qs = '' if request.META['QUERY_STRING']: extra_qs = '&' + request.META['QUERY_STRING'] choices = self.get_child_type_choices(request, 'add') if len(choices) == 1: return HttpResponseRedirect('?ct_id={0}{1}'.format(choices[0][0], extra_qs)) # Create form form = self.add_type_form( data=request.POST if request.method == 'POST' else None, initial={'ct_id': choices[0][0]} ) form.fields['ct_id'].choices = choices if form.is_valid(): return HttpResponseRedirect('?ct_id={0}{1}'.format(form.cleaned_data['ct_id'], extra_qs)) # Wrap in all admin layout fieldsets = ((None, {'fields': ('ct_id',)}),) adminForm = AdminForm(form, fieldsets, {}, model_admin=self) media = self.media + adminForm.media opts = self.model._meta context = { 'title': _('Add %s') % force_text(opts.verbose_name), 'adminform': adminForm, 'is_popup': ("_popup" in request.POST or "_popup" in request.GET), 'media': mark_safe(media), 'errors': AdminErrorList(form, ()), 'app_label': opts.app_label, } return self.render_add_type_form(request, context, form_url) def render_add_type_form(self, request, context, form_url=''): """ Render the page type choice form. """ opts = self.model._meta app_label = opts.app_label context.update({ 'has_change_permission': self.has_change_permission(request), 'form_url': mark_safe(form_url), 'opts': opts, 'add': True, 'save_on_top': self.save_on_top, }) if hasattr(self.admin_site, 'root_path'): context['root_path'] = self.admin_site.root_path # Django < 1.4 context_instance = RequestContext(request, current_app=self.admin_site.name) return render_to_response(self.add_type_template or [ "admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()), "admin/%s/add_type_form.html" % app_label, "admin/polymorphic/add_type_form.html", # added default here "admin/add_type_form.html" ], context, context_instance=context_instance) @property def change_list_template(self): opts = self.model._meta app_label = opts.app_label # Pass the base options base_opts = self.base_model._meta base_app_label = base_opts.app_label return [ "admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()), "admin/%s/change_list.html" % app_label, # Added base class: "admin/%s/%s/change_list.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/change_list.html" % base_app_label, "admin/change_list.html" ] class PolymorphicChildModelAdmin(admin.ModelAdmin): """ The *optional* base class for the admin interface of derived models. This base class defines some convenience behavior for the admin interface: * It corrects the breadcrumbs in the admin pages. * It adds the base model to the template lookup paths. * 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 ``base_model`` attribute must be set. """ base_model = None base_form = None base_fieldsets = None extra_fieldset_title = _("Contents") # Default title for extra fieldset def get_form(self, request, obj=None, **kwargs): # The django admin validation requires the form to have a 'class Meta: model = ..' # attribute, or it will complain that the fields are missing. # However, this enforces all derived ModelAdmin classes to redefine the model as well, # because they need to explicitly set the model again - it will stick with the base model. # # Instead, pass the form unchecked here, because the standard ModelForm will just work. # If the derived class sets the model explicitly, respect that setting. kwargs.setdefault('form', self.base_form or self.form) # prevent infinite recursion in django 1.6+ if not getattr(self, 'declared_fieldsets', None): kwargs.setdefault('fields', None) return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs) @property def change_form_template(self): opts = self.model._meta app_label = opts.app_label # Pass the base options base_opts = self.base_model._meta base_app_label = base_opts.app_label return [ "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), "admin/%s/change_form.html" % app_label, # Added: "admin/%s/%s/change_form.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/change_form.html" % base_app_label, "admin/polymorphic/change_form.html", "admin/change_form.html" ] @property def delete_confirmation_template(self): opts = self.model._meta app_label = opts.app_label # Pass the base options base_opts = self.base_model._meta base_app_label = base_opts.app_label return [ "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), "admin/%s/delete_confirmation.html" % app_label, # Added: "admin/%s/%s/delete_confirmation.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/delete_confirmation.html" % base_app_label, "admin/polymorphic/delete_confirmation.html", "admin/delete_confirmation.html" ] def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): context.update({ 'base_opts': self.base_model._meta, }) return super(PolymorphicChildModelAdmin, self).render_change_form(request, context, add=add, change=change, form_url=form_url, obj=obj) def delete_view(self, request, object_id, context=None): extra_context = { 'base_opts': self.base_model._meta, } return super(PolymorphicChildModelAdmin, self).delete_view(request, object_id, extra_context) # ---- Extra: improving the form/fieldset default display ---- def get_fieldsets(self, request, obj=None): # If subclass declares fieldsets, this is respected if (hasattr(self, 'declared_fieldset') and self.declared_fieldsets) \ or not self.base_fieldsets: return super(PolymorphicChildModelAdmin, self).get_fieldsets(request, obj) # Have a reasonable default fieldsets, # where the subclass fields are automatically included. other_fields = self.get_subclass_fields(request, obj) if other_fields: return ( self.base_fieldsets[0], (self.extra_fieldset_title, {'fields': other_fields}), ) + self.base_fieldsets[1:] else: return self.base_fieldsets def get_subclass_fields(self, request, obj=None): # Find out how many fields would really be on the form, # if it weren't restricted by declared fields. exclude = list(self.exclude or []) exclude.extend(self.get_readonly_fields(request, obj)) # By not declaring the fields/form in the base class, # get_form() will populate the form with all available fields. form = self.get_form(request, obj, exclude=exclude) subclass_fields = list(six.iterkeys(form.base_fields)) + list(self.get_readonly_fields(request, obj)) # Find which fields are not part of the common fields. for fieldset in self.base_fieldsets: for field in fieldset[1]['fields']: try: subclass_fields.remove(field) except ValueError: pass # field not found in form, Django will raise exception later. return subclass_fields def _get_opt(model): try: return model._meta.app_label, model._meta.model_name # Django 1.7 format except AttributeError: return model._meta.app_label, model._meta.module_name django_polymorphic-0.8.1/polymorphic/base.py000066400000000000000000000305161264051031500212420ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ PolymorphicModel Meta Class Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ """ from __future__ import absolute_import import sys import inspect import django from django.db import models from django.db.models.base import ModelBase from django.db.models.manager import ManagerDescriptor from .managers import PolymorphicManager from .query import PolymorphicQuerySet # PolymorphicQuerySet Q objects (and filter()) support these additional key words. # These are forbidden as field names (a descriptive exception is raised) POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of'] try: from django.db.models.manager import AbstractManagerDescriptor # Django 1.5 except ImportError: AbstractManagerDescriptor = None ################################################################################### # PolymorphicModel meta class class PolymorphicModelBase(ModelBase): """ Manager inheritance is a pretty complex topic which may need more thought regarding how this should be handled for polymorphic models. In any case, we probably should propagate 'objects' and 'base_objects' from PolymorphicModel to every subclass. We also want to somehow inherit/propagate _default_manager as well, as it needs to be polymorphic. The current implementation below is an experiment to solve this problem with a very simplistic approach: We unconditionally inherit/propagate any and all managers (using _copy_to_model), as long as they are defined on polymorphic models (the others are left alone). Like Django ModelBase, we special-case _default_manager: if there are any user-defined managers, it is set to the first of these. We also require that _default_manager as well as any user defined polymorphic managers produce querysets that are derived from PolymorphicQuerySet. """ def __new__(self, model_name, bases, attrs): # print; print '###', model_name, '- bases:', bases # Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses: if not attrs and model_name == 'NewBase': if django.VERSION < (1, 5): # Let Django fully ignore the class which is inserted in between. # Django 1.5 fixed this, see https://code.djangoproject.com/ticket/19688 attrs['__module__'] = 'django.utils.six' attrs['Meta'] = type('Meta', (), {'abstract': True}) return super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs) # create new model new_class = self.call_superclass_new_method(model_name, bases, attrs) # check if the model fields are all allowed self.validate_model_fields(new_class) # create list of all managers to be inherited from the base classes inherited_managers = new_class.get_inherited_managers(attrs) # add the managers to the new model for source_name, mgr_name, manager in inherited_managers: # print '** add inherited manager from model %s, manager %s, %s' % (source_name, mgr_name, manager.__class__.__name__) new_manager = manager._copy_to_model(new_class) if mgr_name == '_default_manager': new_class._default_manager = new_manager else: new_class.add_to_class(mgr_name, new_manager) # get first user defined manager; if there is one, make it the _default_manager # this value is used by the related objects, restoring access to custom queryset methods on related objects. user_manager = self.get_first_user_defined_manager(new_class) if user_manager: # print '## add default manager', type(def_mgr) new_class._default_manager = user_manager._copy_to_model(new_class) new_class._default_manager._inherited = False # the default mgr was defined by the user, not inherited # validate resulting default manager self.validate_model_manager(new_class._default_manager, model_name, '_default_manager') # for __init__ function of this class (monkeypatching inheritance accessors) new_class.polymorphic_super_sub_accessors_replaced = False # determine the name of the primary key field and store it into the class variable # polymorphic_primary_key_name (it is needed by query.py) for f in new_class._meta.fields: if f.primary_key and type(f) != models.OneToOneField: new_class.polymorphic_primary_key_name = f.name break return new_class def get_inherited_managers(self, attrs): """ Return list of all managers to be inherited/propagated from the base classes; use correct mro, only use managers with _inherited==False (they are of no use), skip managers that are overwritten by the user with same-named class attributes (in attrs) """ # print "** ", self.__name__ add_managers = [] add_managers_keys = set() for base in self.__mro__[1:]: if not issubclass(base, models.Model): continue if not getattr(base, 'polymorphic_model_marker', None): continue # leave managers of non-polym. models alone for key, manager in base.__dict__.items(): if type(manager) == models.manager.ManagerDescriptor: manager = manager.manager if AbstractManagerDescriptor is not None: # Django 1.4 unconditionally assigned managers to a model. As of Django 1.5 however, # the abstract models don't get any managers, only a AbstractManagerDescriptor as substitute. # Pretend that the manager is still there, so all code works like it used to. if type(manager) == AbstractManagerDescriptor and base.__name__ == 'PolymorphicModel': model = manager.model if key == 'objects': manager = PolymorphicManager() manager.model = model elif key == 'base_objects': manager = models.Manager() manager.model = model if not isinstance(manager, models.Manager): continue if key == '_base_manager': continue # let Django handle _base_manager if key in attrs: continue if key in add_managers_keys: continue # manager with that name already added, skip if manager._inherited: continue # inherited managers (on the bases) have no significance, they are just copies # print '## {0} {1}'.format(self.__name__, key) if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers self.validate_model_manager(manager, self.__name__, key) add_managers.append((base.__name__, key, manager)) add_managers_keys.add(key) # The ordering in the base.__dict__ may randomly change depending on which method is added. # Make sure base_objects is on top, and 'objects' and '_default_manager' follow afterwards. # This makes sure that the _base_manager is also assigned properly. add_managers = sorted(add_managers, key=lambda item: (item[1].startswith('_'), item[1])) return add_managers @classmethod def get_first_user_defined_manager(mcs, new_class): # See if there is a manager attribute directly stored at this inheritance level. mgr_list = [] for key, val in new_class.__dict__.items(): if isinstance(val, ManagerDescriptor): val = val.manager if not isinstance(val, PolymorphicManager) or type(val) is PolymorphicManager: continue mgr_list.append((val.creation_counter, key, val)) # if there are user defined managers, use first one as _default_manager if mgr_list: _, manager_name, manager = sorted(mgr_list)[0] # sys.stderr.write( '\n# first user defined manager for model "{model}":\n# "{mgrname}": {mgr}\n# manager model: {mgrmodel}\n\n' # .format( model=self.__name__, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) ) return manager return None @classmethod def call_superclass_new_method(self, model_name, bases, attrs): """call __new__ method of super class and return the newly created class. Also work around a limitation in Django's ModelBase.""" # There seems to be a general limitation in Django's app_label handling # regarding abstract models (in ModelBase). See issue 1 on github - TODO: propose patch for Django # We run into this problem if polymorphic.py is located in a top-level directory # which is directly in the python path. To work around this we temporarily set # app_label here for PolymorphicModel. meta = attrs.get('Meta', None) do_app_label_workaround = (meta and attrs['__module__'] == 'polymorphic' and model_name == 'PolymorphicModel' and getattr(meta, 'app_label', None) is None) if do_app_label_workaround: meta.app_label = 'poly_dummy_app_label' new_class = super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs) if do_app_label_workaround: del(meta.app_label) return new_class @classmethod def validate_model_fields(self, new_class): "check if all fields names are allowed (i.e. not in POLYMORPHIC_SPECIAL_Q_KWORDS)" for f in new_class._meta.fields: if f.name in POLYMORPHIC_SPECIAL_Q_KWORDS: e = 'PolymorphicModel: "%s" - field name "%s" is not allowed in polymorphic models' raise AssertionError(e % (new_class.__name__, f.name)) @classmethod def validate_model_manager(self, manager, model_name, manager_name): """check if the manager is derived from PolymorphicManager and its querysets from PolymorphicQuerySet - throw AssertionError if not""" if not issubclass(type(manager), PolymorphicManager): e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" manager is of type "' + type(manager).__name__ e += '", but must be a subclass of PolymorphicManager' raise AssertionError(e) if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet): e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" (PolymorphicManager) has been instantiated with a queryset class which is' e += ' not a subclass of PolymorphicQuerySet (which is required)' raise AssertionError(e) return manager # hack: a small patch to Django would be a better solution. # Django's management command 'dumpdata' relies on non-polymorphic # behaviour of the _default_manager. Therefore, we catch any access to _default_manager # here and return the non-polymorphic default manager instead if we are called from 'dumpdata.py' # Otherwise, the base objects will be upcasted to polymorphic models, and be outputted as such. # (non-polymorphic default manager is 'base_objects' for polymorphic models). # This way we don't need to patch django.core.management.commands.dumpdata # for all supported Django versions. if len(sys.argv) > 1 and sys.argv[1] == 'dumpdata': # manage.py dumpdata is running def __getattribute__(self, name): if name == '_default_manager': frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name if 'django/core/management/commands/dumpdata.py' in frm[1]: return self.base_objects # caller_mod_name = inspect.getmodule(frm[0]).__name__ # does not work with python 2.4 # if caller_mod_name == 'django.core.management.commands.dumpdata': return super(PolymorphicModelBase, self).__getattribute__(name) # TODO: investigate Django how this can be avoided django_polymorphic-0.8.1/polymorphic/locale/000077500000000000000000000000001264051031500212105ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/locale/en/000077500000000000000000000000001264051031500216125ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/locale/en/LC_MESSAGES/000077500000000000000000000000001264051031500233775ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/locale/en/LC_MESSAGES/django.po000066400000000000000000000014521264051031500252030ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-29 18:12+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: admin.py:41 msgid "Type" msgstr "" #: admin.py:56 msgid "Content type" msgstr "" # This is already translated in Django #: admin.py:333 #, python-format #msgid "Add %s" #msgstr "" #: admin.py:403 msgid "Contents" msgstr "" django_polymorphic-0.8.1/polymorphic/locale/es/000077500000000000000000000000001264051031500216175ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/locale/es/LC_MESSAGES/000077500000000000000000000000001264051031500234045ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/locale/es/LC_MESSAGES/django.po000066400000000000000000000013621264051031500252100ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Gonzalo Bustos, 2015. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-29 18:12+0100\n" "PO-Revision-Date: 2015-10-12 11:42-0300\n" "Last-Translator: Gonzalo Bustos\n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\n" #: admin.py:41 msgid "Type" msgstr "Tipo" #: admin.py:56 msgid "Content type" msgstr "Tipo de contenido" #: admin.py:333 admin.py:403 #, python-format msgid "Contents" msgstr "Contenidos" django_polymorphic-0.8.1/polymorphic/locale/fr/000077500000000000000000000000001264051031500216175ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/locale/fr/LC_MESSAGES/000077500000000000000000000000001264051031500234045ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/locale/fr/LC_MESSAGES/django.po000066400000000000000000000015631264051031500252130ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-11-29 18:12+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: admin.py:41 msgid "Type" msgstr "Type" #: admin.py:56 msgid "Content type" msgstr "Type de contenu" # This is already translated in Django #: admin.py:333 #, python-format #msgid "Add %s" #msgstr "" #: admin.py:403 msgid "Contents" msgstr "Contenus" django_polymorphic-0.8.1/polymorphic/manager.py000066400000000000000000000001501264051031500217310ustar00rootroot00000000000000# For compatibility with pre 0.8 versions from .managers import PolymorphicQuerySet, PolymorphicManager django_polymorphic-0.8.1/polymorphic/managers.py000066400000000000000000000041071264051031500221220ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ PolymorphicManager Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ """ from __future__ import unicode_literals import warnings import django from django.db import models from polymorphic.query import PolymorphicQuerySet class PolymorphicManager(models.Manager): """ Manager for PolymorphicModel Usually not explicitly needed, except if a custom manager or a custom queryset class is to be used. """ # Tell Django that related fields also need to use this manager: use_for_related_fields = True queryset_class = PolymorphicQuerySet def __init__(self, queryset_class=None, *args, **kwrags): # Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__. # However, this doesn't work for related managers which instantiate a new version of this class. # Hence, for custom managers the new default is using the 'queryset_class' attribute at class level instead. if queryset_class: warnings.warn("Using PolymorphicManager(queryset_class=..) is deprecated; override the queryset_class attribute instead", DeprecationWarning) # For backwards compatibility, still allow the parameter: self.queryset_class = queryset_class super(PolymorphicManager, self).__init__(*args, **kwrags) def get_queryset(self): return self.queryset_class(self.model, using=self._db) # For Django 1.5 if django.VERSION < (1, 7): get_query_set = get_queryset def __unicode__(self): return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__) # Proxied methods def non_polymorphic(self): return self.all().non_polymorphic() def instance_of(self, *args): return self.all().instance_of(*args) def not_instance_of(self, *args): return self.all().not_instance_of(*args) def get_real_instances(self, base_result_objects=None): return self.all().get_real_instances(base_result_objects=base_result_objects) django_polymorphic-0.8.1/polymorphic/models.py000066400000000000000000000247731264051031500216230ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Seamless Polymorphic Inheritance for Django Models ================================================== Please see README.rst and DOCS.rst for further information. Or on the Web: http://chrisglass.github.com/django_polymorphic/ http://github.com/chrisglass/django_polymorphic Copyright: This code and affiliated files are (C) by Bert Constantin and individual contributors. Please see LICENSE and AUTHORS for more information. """ from __future__ import absolute_import from django.db import models from django.contrib.contenttypes.models import ContentType from django.utils import six from .base import PolymorphicModelBase from .managers import PolymorphicManager from .query_translate import translate_polymorphic_Q_object ################################################################################### # PolymorphicModel class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): """ Abstract base class that provides polymorphic behaviour for any model directly or indirectly derived from it. For usage instructions & examples please see documentation. PolymorphicModel declares one field for internal use (polymorphic_ctype) and provides a polymorphic manager as the default manager (and as 'objects'). PolymorphicModel overrides the save() and __init__ methods. If your derived class overrides any of these methods as well, then you need to take care that you correctly call the method of the superclass, like: super(YourClass,self).save(*args,**kwargs) """ # for PolymorphicModelBase, so it can tell which models are polymorphic and which are not (duck typing) polymorphic_model_marker = True # for PolymorphicQuery, True => an overloaded __repr__ with nicer multi-line output is used by PolymorphicQuery polymorphic_query_multiline_output = False class Meta: abstract = True # avoid ContentType related field accessor clash (an error emitted by model validation) polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False, related_name='polymorphic_%(app_label)s.%(class)s_set+') # some applications want to know the name of the fields that are added to its models polymorphic_internal_model_fields = ['polymorphic_ctype'] # Note that Django 1.5 removes these managers because the model is abstract. # They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers() objects = PolymorphicManager() base_objects = models.Manager() @classmethod def translate_polymorphic_Q_object(self_class, q): return translate_polymorphic_Q_object(self_class, q) def pre_save_polymorphic(self): """Normally not needed. This function may be called manually in special use-cases. When the object is saved for the first time, we store its real class in polymorphic_ctype. When the object later is retrieved by PolymorphicQuerySet, it uses this field to figure out the real class of this object (used by PolymorphicQuerySet._get_real_instances) """ if not self.polymorphic_ctype_id: self.polymorphic_ctype = ContentType.objects.get_for_model(self, for_concrete_model=False) pre_save_polymorphic.alters_data = True def save(self, *args, **kwargs): """Overridden model save function which supports the polymorphism functionality (through pre_save_polymorphic).""" self.pre_save_polymorphic() return super(PolymorphicModel, self).save(*args, **kwargs) save.alters_data = True def get_real_instance_class(self): """ Normally not needed. If a non-polymorphic manager (like base_objects) has been used to retrieve objects, then the real class/type of these objects may be determined using this method. """ # the following line would be the easiest way to do this, but it produces sql queries # return self.polymorphic_ctype.model_class() # so we use the following version, which uses the ContentType manager cache. # Note that model_class() can return None for stale content types; # when the content type record still exists but no longer refers to an existing model. try: model = ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class() except AttributeError: # Django <1.6 workaround return None # Protect against bad imports (dumpdata without --natural) or other # issues missing with the ContentType models. if model is not None \ and not issubclass(model, self.__class__) \ and not issubclass(model, self.__class__._meta.proxy_for_model): raise RuntimeError("ContentType {0} for {1} #{2} does not point to a subclass!".format( self.polymorphic_ctype_id, model, self.pk, )) return model def get_real_concrete_instance_class_id(self): model_class = self.get_real_instance_class() if model_class is None: return None return ContentType.objects.get_for_model(model_class, for_concrete_model=True).pk def get_real_concrete_instance_class(self): model_class = self.get_real_instance_class() if model_class is None: return None return ContentType.objects.get_for_model(model_class, for_concrete_model=True).model_class() def get_real_instance(self): """Normally not needed. If a non-polymorphic manager (like base_objects) has been used to retrieve objects, then the complete object with it's real class/type and all fields may be retrieved with this method. Each method call executes one db query (if necessary).""" real_model = self.get_real_instance_class() if real_model == self.__class__: return self return real_model.objects.get(pk=self.pk) def __init__(self, * args, ** kwargs): """Replace Django's inheritance accessor member functions for our model (self.__class__) with our own versions. We monkey patch them until a patch can be added to Django (which would probably be very small and make all of this obsolete). If we have inheritance of the form ModelA -> ModelB ->ModelC then Django creates accessors like this: - ModelA: modelb - ModelB: modela_ptr, modelb, modelc - ModelC: modela_ptr, modelb, modelb_ptr, modelc These accessors allow Django (and everyone else) to travel up and down the inheritance tree for the db object at hand. The original Django accessors use our polymorphic manager. But they should not. So we replace them with our own accessors that use our appropriate base_objects manager. """ super(PolymorphicModel, self).__init__(*args, ** kwargs) if self.__class__.polymorphic_super_sub_accessors_replaced: return self.__class__.polymorphic_super_sub_accessors_replaced = True def create_accessor_function_for_model(model, accessor_name): def accessor_function(self): attr = model.base_objects.get(pk=self.pk) return attr return accessor_function subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models() try: from django.db.models.fields.related import ReverseOneToOneDescriptor, ForwardManyToOneDescriptor except ImportError: # django < 1.9 from django.db.models.fields.related import ( SingleRelatedObjectDescriptor as ReverseOneToOneDescriptor, ReverseSingleRelatedObjectDescriptor as ForwardManyToOneDescriptor, ) for name, model in subclasses_and_superclasses_accessors.items(): orig_accessor = getattr(self.__class__, name, None) if type(orig_accessor) in [ReverseOneToOneDescriptor, ForwardManyToOneDescriptor]: #print >>sys.stderr, '---------- replacing', name, orig_accessor, '->', model setattr(self.__class__, name, property(create_accessor_function_for_model(model, name))) def _get_inheritance_relation_fields_and_models(self): """helper function for __init__: determine names of all Django inheritance accessor member functions for type(self)""" def add_model(model, field_name, result): result[field_name] = model def add_model_if_regular(model, field_name, result): if (issubclass(model, models.Model) and model != models.Model and model != self.__class__ and model != PolymorphicModel): add_model(model, field_name, result) def add_all_super_models(model, result): for super_cls, field_to_super in model._meta.parents.items(): if field_to_super is not None: # if not a link to a proxy model field_name = field_to_super.name # the field on model can have a different name to super_cls._meta.module_name, if the field is created manually using 'parent_link' add_model_if_regular(super_cls, field_name, result) add_all_super_models(super_cls, result) def add_all_sub_models(super_cls, result): for sub_cls in super_cls.__subclasses__(): # go through all subclasses of model if super_cls in sub_cls._meta.parents: # super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model field_to_super = sub_cls._meta.parents[super_cls] # get the field that links sub_cls to super_cls if field_to_super is not None: # if filed_to_super is not a link to a proxy model super_to_sub_related_field = field_to_super.rel if super_to_sub_related_field.related_name is None: # if related name is None the related field is the name of the subclass to_subclass_fieldname = sub_cls.__name__.lower() else: # otherwise use the given related name to_subclass_fieldname = super_to_sub_related_field.related_name add_model_if_regular(sub_cls, to_subclass_fieldname, result) result = {} add_all_super_models(self.__class__, result) add_all_sub_models(self.__class__, result) return result django_polymorphic-0.8.1/polymorphic/query.py000066400000000000000000000377071264051031500215060ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ QuerySet for PolymorphicModel Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ """ from __future__ import absolute_import from collections import defaultdict import django from django.db.models.query import QuerySet from django.contrib.contenttypes.models import ContentType from django.utils import six from .query_translate import translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args from .query_translate import translate_polymorphic_field_path # chunk-size: maximum number of objects requested per db-request # by the polymorphic queryset.iterator() implementation; we use the same chunk size as Django try: from django.db.models.query import CHUNK_SIZE # this is 100 for Django 1.1/1.2 except ImportError: # CHUNK_SIZE was removed in Django 1.6 CHUNK_SIZE = 100 Polymorphic_QuerySet_objects_per_request = CHUNK_SIZE def transmogrify(cls, obj): """ Upcast a class to a different type without asking questions. """ if not '__init__' in obj.__dict__: # Just assign __class__ to a different value. new = obj new.__class__ = cls else: # Run constructor, reassign values new = cls() for k, v in obj.__dict__.items(): new.__dict__[k] = v return new ################################################################################### # PolymorphicQuerySet def _query_annotations(query): try: return query.annotations except AttributeError: # Django < 1.8 return query.aggregates class PolymorphicQuerySet(QuerySet): """ QuerySet for PolymorphicModel Contains the core functionality for PolymorphicModel Usually not explicitly needed, except if a custom queryset class is to be used. """ def __init__(self, *args, **kwargs): "init our queryset object member variables" self.polymorphic_disabled = False super(PolymorphicQuerySet, self).__init__(*args, **kwargs) def _clone(self, *args, **kwargs): "Django's _clone only copies its own variables, so we need to copy ours here" new = super(PolymorphicQuerySet, self)._clone(*args, **kwargs) new.polymorphic_disabled = self.polymorphic_disabled return new if django.VERSION >= (1, 7): def as_manager(cls): # Make sure the Django 1.7 way of creating managers works. from .managers import PolymorphicManager manager = PolymorphicManager.from_queryset(cls)() manager._built_with_as_manager = True return manager as_manager.queryset_only = True as_manager = classmethod(as_manager) def non_polymorphic(self): """switch off polymorphic behaviour for this query. When the queryset is evaluated, only objects of the type of the base class used for this query are returned.""" qs = self._clone() qs.polymorphic_disabled = True return qs def instance_of(self, *args): """Filter the queryset to only include the classes in args (and their subclasses). Implementation in _translate_polymorphic_filter_defnition.""" return self.filter(instance_of=args) def not_instance_of(self, *args): """Filter the queryset to exclude the classes in args (and their subclasses). Implementation in _translate_polymorphic_filter_defnition.""" return self.filter(not_instance_of=args) def _filter_or_exclude(self, negate, *args, **kwargs): "We override this internal Django functon as it is used for all filter member functions." translate_polymorphic_filter_definitions_in_args(self.model, args) # the Q objects additional_args = translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs) # filter_field='data' return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs) def order_by(self, *args, **kwargs): """translate the field paths in the args, then call vanilla order_by.""" new_args = [translate_polymorphic_field_path(self.model, a) for a in args] return super(PolymorphicQuerySet, self).order_by(*new_args, **kwargs) def _process_aggregate_args(self, args, kwargs): """for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args. Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)""" def patch_lookup(a): if django.VERSION < (1, 8): a.lookup = translate_polymorphic_field_path(self.model, a.lookup) else: # With Django > 1.8, the field on which the aggregate operates is # stored inside a query expression. if hasattr(a, 'source_expressions'): a.source_expressions[0].name = translate_polymorphic_field_path( self.model, a.source_expressions[0].name) get_lookup = lambda a: a.lookup if django.VERSION < (1, 8) else a.source_expressions[0].name for a in args: assert '___' not in get_lookup(a), 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only' for a in six.itervalues(kwargs): patch_lookup(a) def annotate(self, *args, **kwargs): """translate the polymorphic field paths in the kwargs, then call vanilla annotate. _get_real_instances will do the rest of the job after executing the query.""" self._process_aggregate_args(args, kwargs) return super(PolymorphicQuerySet, self).annotate(*args, **kwargs) def aggregate(self, *args, **kwargs): """translate the polymorphic field paths in the kwargs, then call vanilla aggregate. We need no polymorphic object retrieval for aggregate => switch it off.""" self._process_aggregate_args(args, kwargs) qs = self.non_polymorphic() return super(PolymorphicQuerySet, qs).aggregate(*args, **kwargs) if django.VERSION >= (1, 9): # On Django < 1.9, 'qs.values(...)' returned a new special ValuesQuerySet # object, which our polymorphic modifications didn't apply to. # Starting with Django 1.9, the copy returned by 'qs.values(...)' has the # same class as 'qs', so our polymorphic modifications would apply. # We want to leave values queries untouched, so we set 'polymorphic_disabled'. def _values(self, *args, **kwargs): clone = super(PolymorphicQuerySet, self)._values(*args, **kwargs) clone.polymorphic_disabled = True return clone # Since django_polymorphic 'V1.0 beta2', extra() always returns polymorphic results. # The resulting objects are required to have a unique primary key within the result set # (otherwise an error is thrown). # The "polymorphic" keyword argument is not supported anymore. # def extra(self, *args, **kwargs): def _get_real_instances(self, base_result_objects): """ Polymorphic object loader Does the same as: return [ o.get_real_instance() for o in base_result_objects ] but more efficiently. The list base_result_objects contains the objects from the executed base class query. The class of all of them is self.model (our base model). Some, many or all of these objects were not created and stored as class self.model, but as a class derived from self.model. We want to re-fetch these objects from the db as their original class so we can return them just as they were created/saved. We identify these objects by looking at o.polymorphic_ctype, which specifies the real class of these objects (the class at the time they were saved). First, we sort the result objects in base_result_objects for their subclass (from o.polymorphic_ctype), and then we execute one db query per subclass of objects. Here, we handle any annotations from annotate(). Finally we re-sort the resulting objects into the correct order and return them as a list. """ ordered_id_list = [] # list of ids of result-objects in correct order results = {} # polymorphic dict of result-objects, keyed with their id (no order) # dict contains one entry per unique model type occurring in result, # in the format idlist_per_model[modelclass]=[list-of-object-ids] idlist_per_model = defaultdict(list) # django's automatic ".pk" field does not always work correctly for # custom fields in derived objects (unclear yet who to put the blame on). # We get different type(o.pk) in this case. # We work around this by using the real name of the field directly # for accessing the primary key of the the derived objects. # We might assume that self.model._meta.pk.name gives us the name of the primary key field, # but it doesn't. Therefore we use polymorphic_primary_key_name, which we set up in base.py. pk_name = self.model.polymorphic_primary_key_name # - sort base_result_object ids into idlist_per_model lists, depending on their real class; # - also record the correct result order in "ordered_id_list" # - store objects that already have the correct class into "results" base_result_objects_by_id = {} self_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=False).pk self_concrete_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=True).pk for base_object in base_result_objects: ordered_id_list.append(base_object.pk) # check if id of the result object occurres more than once - this can happen e.g. with base_objects.extra(tables=...) if not base_object.pk in base_result_objects_by_id: base_result_objects_by_id[base_object.pk] = base_object if base_object.polymorphic_ctype_id == self_model_class_id: # Real class is exactly the same as base class, go straight to results results[base_object.pk] = base_object else: real_concrete_class = base_object.get_real_instance_class() real_concrete_class_id = base_object.get_real_concrete_instance_class_id() if real_concrete_class_id is None: # Dealing with a stale content type continue elif real_concrete_class_id == self_concrete_model_class_id: # Real and base classes share the same concrete ancestor, # upcast it and put it in the results results[base_object.pk] = transmogrify(real_concrete_class, base_object) else: real_concrete_class = ContentType.objects.get_for_id(real_concrete_class_id).model_class() idlist_per_model[real_concrete_class].append(getattr(base_object, pk_name)) # For each model in "idlist_per_model" request its objects (the real model) # from the db and store them in results[]. # Then we copy the annotate fields from the base objects to the real objects. # Then we copy the extra() select fields from the base objects to the real objects. # TODO: defer(), only(): support for these would be around here for real_concrete_class, idlist in idlist_per_model.items(): real_objects = real_concrete_class.base_objects.filter(**{ ('%s__in' % pk_name): idlist, }) real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs for real_object in real_objects: o_pk = getattr(real_object, pk_name) real_class = real_object.get_real_instance_class() # If the real class is a proxy, upcast it if real_class != real_concrete_class: real_object = transmogrify(real_class, real_object) if _query_annotations(self.query): for anno_field_name in six.iterkeys(_query_annotations(self.query)): attr = getattr(base_result_objects_by_id[o_pk], anno_field_name) setattr(real_object, anno_field_name, attr) if self.query.extra_select: for select_field_name in six.iterkeys(self.query.extra_select): attr = getattr(base_result_objects_by_id[o_pk], select_field_name) setattr(real_object, select_field_name, attr) results[o_pk] = real_object # re-create correct order and return result list resultlist = [results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results] # set polymorphic_annotate_names in all objects (currently just used for debugging/printing) if _query_annotations(self.query): annotate_names = list(six.iterkeys(_query_annotations(self.query))) # get annotate field list for real_object in resultlist: real_object.polymorphic_annotate_names = annotate_names # set polymorphic_extra_select_names in all objects (currently just used for debugging/printing) if self.query.extra_select: extra_select_names = list(six.iterkeys(self.query.extra_select)) # get extra select field list for real_object in resultlist: real_object.polymorphic_extra_select_names = extra_select_names return resultlist def iterator(self): """ This function is used by Django for all object retrieval. By overriding it, we modify the objects that this queryset returns when it is evaluated (or its get method or other object-returning methods are called). Here we do the same as: base_result_objects=list(super(PolymorphicQuerySet, self).iterator()) real_results=self._get_real_instances(base_result_objects) for o in real_results: yield o but it requests the objects in chunks from the database, with Polymorphic_QuerySet_objects_per_request per chunk """ base_iter = super(PolymorphicQuerySet, self).iterator() # disabled => work just like a normal queryset if self.polymorphic_disabled: for o in base_iter: yield o return while True: base_result_objects = [] reached_end = False for i in range(Polymorphic_QuerySet_objects_per_request): try: o = next(base_iter) base_result_objects.append(o) except StopIteration: reached_end = True break real_results = self._get_real_instances(base_result_objects) for o in real_results: yield o if reached_end: return def __repr__(self, *args, **kwargs): if self.model.polymorphic_query_multiline_output: result = [repr(o) for o in self.all()] return '[ ' + ',\n '.join(result) + ' ]' else: return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs) class _p_list_class(list): def __repr__(self, *args, **kwargs): result = [repr(o) for o in self] return '[ ' + ',\n '.join(result) + ' ]' def get_real_instances(self, base_result_objects=None): "same as _get_real_instances, but make sure that __repr__ for ShowField... creates correct output" if not base_result_objects: base_result_objects = self olist = self._get_real_instances(base_result_objects) if not self.model.polymorphic_query_multiline_output: return olist clist = PolymorphicQuerySet._p_list_class(olist) return clist django_polymorphic-0.8.1/polymorphic/query_translate.py000066400000000000000000000252111264051031500235460ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ PolymorphicQuerySet support functions Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ """ from __future__ import absolute_import import django from django.db import models from django.contrib.contenttypes.models import ContentType from django.db.models import Q, FieldDoesNotExist from django.db.models.fields.related import RelatedField if django.VERSION < (1, 6): # There was no common base class in Django 1.5, mention all variants here. from django.db.models.fields.related import RelatedObject, ManyToOneRel, ManyToManyRel REL_FIELD_CLASSES = (RelatedField, RelatedObject, ManyToOneRel, ManyToManyRel) # Leaving GenericRel out. elif django.VERSION < (1, 8): # As of Django 1.6 there is a ForeignObjectRel. from django.db.models.fields.related import ForeignObjectRel, RelatedObject REL_FIELD_CLASSES = (RelatedField, ForeignObjectRel, RelatedObject) else: # As of Django 1.8 the base class serves everything. RelatedObject is gone. from django.db.models.fields.related import ForeignObjectRel REL_FIELD_CLASSES = (RelatedField, ForeignObjectRel) from functools import reduce ################################################################################### # PolymorphicQuerySet support functions # These functions implement the additional filter- and Q-object functionality. # They form a kind of small framework for easily adding more # functionality to filters and Q objects. # Probably a more general queryset enhancement class could be made out of them. def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs): """ Translate the keyword argument list for PolymorphicQuerySet.filter() Any kwargs with special polymorphic functionality are replaced in the kwargs dict with their vanilla django equivalents. For some kwargs a direct replacement is not possible, as a Q object is needed instead to implement the required functionality. In these cases the kwarg is deleted from the kwargs dict and a Q object is added to the return list. Modifies: kwargs dict Returns: a list of non-keyword-arguments (Q objects) to be added to the filter() query. """ additional_args = [] for field_path, val in kwargs.copy().items(): # Python 3 needs copy new_expr = _translate_polymorphic_filter_definition(queryset_model, field_path, val) if type(new_expr) == tuple: # replace kwargs element del(kwargs[field_path]) kwargs[new_expr[0]] = new_expr[1] elif isinstance(new_expr, models.Q): del(kwargs[field_path]) additional_args.append(new_expr) return additional_args def translate_polymorphic_Q_object(queryset_model, potential_q_object): def tree_node_correct_field_specs(my_model, node): " process all children of this Q node " for i in range(len(node.children)): child = node.children[i] if type(child) == tuple: # this Q object child is a tuple => a kwarg like Q( instance_of=ModelB ) key, val = child new_expr = _translate_polymorphic_filter_definition(my_model, key, val) if new_expr: node.children[i] = new_expr else: # this Q object child is another Q object, recursively process this as well tree_node_correct_field_specs(my_model, child) if isinstance(potential_q_object, models.Q): tree_node_correct_field_specs(queryset_model, potential_q_object) return potential_q_object def translate_polymorphic_filter_definitions_in_args(queryset_model, args): """ Translate the non-keyword argument list for PolymorphicQuerySet.filter() In the args list, we replace all kwargs to Q-objects that contain special polymorphic functionality with their vanilla django equivalents. We traverse the Q object tree for this (which is simple). TODO: investigate: we modify the Q-objects ina args in-place. Is this OK? Modifies: args list """ for q in args: translate_polymorphic_Q_object(queryset_model, q) def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val): """ Translate a keyword argument (field_path=field_val), as used for PolymorphicQuerySet.filter()-like functions (and Q objects). A kwarg with special polymorphic functionality is translated into its vanilla django equivalent, which is returned, either as tuple (field_path, field_val) or as Q object. Returns: kwarg tuple or Q object or None (if no change is required) """ # handle instance_of expressions or alternatively, # if this is a normal Django filter expression, return None if field_path == 'instance_of': return _create_model_filter_Q(field_val) elif field_path == 'not_instance_of': return _create_model_filter_Q(field_val, not_instance_of=True) elif not '___' in field_path: return None # no change # filter expression contains '___' (i.e. filter for polymorphic field) # => get the model class specified in the filter expression newpath = translate_polymorphic_field_path(queryset_model, field_path) return (newpath, field_val) def translate_polymorphic_field_path(queryset_model, field_path): """ Translate a field path from a keyword argument, as used for PolymorphicQuerySet.filter()-like functions (and Q objects). Supports leading '-' (for order_by args). E.g.: if queryset_model is ModelA, then "ModelC___field3" is translated into modela__modelb__modelc__field3. Returns: translated path (unchanged, if no translation needed) """ classname, sep, pure_field_path = field_path.partition('___') if not sep: return field_path assert classname, 'PolymorphicModel: %s: bad field specification' % field_path negated = False if classname[0] == '-': negated = True classname = classname.lstrip('-') if '__' in classname: # the user has app label prepended to class name via __ => use Django's get_model function appname, sep, classname = classname.partition('__') model = models.get_model(appname, classname) assert model, 'PolymorphicModel: model %s (in app %s) not found!' % (model.__name__, appname) if not issubclass(model, queryset_model): e = 'PolymorphicModel: queryset filter error: "' + model.__name__ + '" is not derived from "' + queryset_model.__name__ + '"' raise AssertionError(e) else: # the user has only given us the class name via ___ # => select the model from the sub models of the queryset base model # Test whether it's actually a regular relation__ _fieldname (the field starting with an _) # so no tripple ClassName___field was intended. try: if django.VERSION >= (1, 8): # This also retreives M2M relations now (including reverse foreign key relations) field = queryset_model._meta.get_field(classname) else: field = queryset_model._meta.get_field_by_name(classname)[0] if isinstance(field, REL_FIELD_CLASSES): # Can also test whether the field exists in the related object to avoid ambiguity between # class names and field names, but that never happens when your class names are in CamelCase. return field_path # No exception raised, field does exist. except FieldDoesNotExist: pass # function to collect all sub-models, this should be optimized (cached) def add_all_sub_models(model, result): if issubclass(model, models.Model) and model != models.Model: # model name is occurring twice in submodel inheritance tree => Error if model.__name__ in result and model != result[model.__name__]: e = 'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s!\n' e += 'In this case, please use the syntax: applabel__ModelName___field' assert model, e % ( model._meta.app_label, model.__name__, result[model.__name__]._meta.app_label, result[model.__name__].__name__) result[model.__name__] = model for b in model.__subclasses__(): add_all_sub_models(b, result) submodels = {} add_all_sub_models(queryset_model, submodels) model = submodels.get(classname, None) assert model, 'PolymorphicModel: model %s not found (not a subclass of %s)!' % (classname, queryset_model.__name__) # create new field path for expressions, e.g. for baseclass=ModelA, myclass=ModelC # 'modelb__modelc" is returned def _create_base_path(baseclass, myclass): bases = myclass.__bases__ for b in bases: if b == baseclass: return myclass.__name__.lower() path = _create_base_path(baseclass, b) if path: return path + '__' + myclass.__name__.lower() return '' basepath = _create_base_path(queryset_model, model) if negated: newpath = '-' else: newpath = '' newpath += basepath if basepath: newpath += '__' newpath += pure_field_path return newpath def _create_model_filter_Q(modellist, not_instance_of=False): """ Helper function for instance_of / not_instance_of Creates and returns a Q object that filters for the models in modellist, including all subclasses of these models (as we want to do the same as pythons isinstance() ). . We recursively collect all __subclasses__(), create a Q filter for each, and or-combine these Q objects. This could be done much more efficiently however (regarding the resulting sql), should an optimization be needed. """ if not modellist: return None from .models import PolymorphicModel if type(modellist) != list and type(modellist) != tuple: if issubclass(modellist, PolymorphicModel): modellist = [modellist] else: assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model' def q_class_with_subclasses(model): q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model, for_concrete_model=False)) for subclass in model.__subclasses__(): q = q | q_class_with_subclasses(subclass) return q qlist = [q_class_with_subclasses(m) for m in modellist] q_ored = reduce(lambda a, b: a | b, qlist) if not_instance_of: q_ored = ~q_ored return q_ored django_polymorphic-0.8.1/polymorphic/showfields.py000066400000000000000000000134361264051031500225010ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.db import models from django.utils import six class ShowFieldBase(object): """ base class for the ShowField... model mixins, does the work """ polymorphic_query_multiline_output = True # cause nicer multiline PolymorphicQuery output polymorphic_showfield_type = False polymorphic_showfield_content = False # these may be overridden by the user polymorphic_showfield_max_line_width = None polymorphic_showfield_max_field_width = 20 polymorphic_showfield_old_format = False def __repr__(self): return self.__unicode__() def _showfields_get_content(self, field_name, field_type=type(None)): "helper for __unicode__" content = getattr(self, field_name) if self.polymorphic_showfield_old_format: out = ': ' else: out = ' ' if issubclass(field_type, models.ForeignKey): if content is None: out += 'None' else: out += content.__class__.__name__ elif issubclass(field_type, models.ManyToManyField): out += '%d' % content.count() elif isinstance(content, six.integer_types): out += str(content) elif content is None: out += 'None' else: txt = str(content) if len(txt) > self.polymorphic_showfield_max_field_width: txt = txt[:self.polymorphic_showfield_max_field_width - 2] + '..' out += '"' + txt + '"' return out def _showfields_add_regular_fields(self, parts): "helper for __unicode__" done_fields = set() for field in self._meta.fields + self._meta.many_to_many: if field.name in self.polymorphic_internal_model_fields or '_ptr' in field.name: continue if field.name in done_fields: continue # work around django diamond inheritance problem done_fields.add(field.name) out = field.name # if this is the standard primary key named "id", print it as we did with older versions of django_polymorphic if field.primary_key and field.name == 'id' and type(field) == models.AutoField: out += ' ' + str(getattr(self, field.name)) # otherwise, display it just like all other fields (with correct type, shortened content etc.) else: if self.polymorphic_showfield_type: out += ' (' + type(field).__name__ if field.primary_key: out += '/pk' out += ')' if self.polymorphic_showfield_content: out += self._showfields_get_content(field.name, type(field)) parts.append((False, out, ',')) def _showfields_add_dynamic_fields(self, field_list, title, parts): "helper for __unicode__" parts.append((True, '- ' + title, ':')) for field_name in field_list: out = field_name content = getattr(self, field_name) if self.polymorphic_showfield_type: out += ' (' + type(content).__name__ + ')' if self.polymorphic_showfield_content: out += self._showfields_get_content(field_name) parts.append((False, out, ',')) def __unicode__(self): # create list ("parts") containing one tuple for each title/field: # ( bool: new section , item-text , separator to use after item ) # start with model name parts = [(True, self.__class__.__name__, ':')] # add all regular fields self._showfields_add_regular_fields(parts) # add annotate fields if hasattr(self, 'polymorphic_annotate_names'): self._showfields_add_dynamic_fields(self.polymorphic_annotate_names, 'Ann', parts) # add extra() select fields if hasattr(self, 'polymorphic_extra_select_names'): self._showfields_add_dynamic_fields(self.polymorphic_extra_select_names, 'Extra', parts) # format result indent = len(self.__class__.__name__) + 5 indentstr = ''.rjust(indent) out = '' xpos = 0 possible_line_break_pos = None for i in range(len(parts)): new_section, p, separator = parts[i] final = (i == len(parts) - 1) if not final: next_new_section, _, _ = parts[i + 1] if (self.polymorphic_showfield_max_line_width and xpos + len(p) > self.polymorphic_showfield_max_line_width and possible_line_break_pos != None): rest = out[possible_line_break_pos:] out = out[:possible_line_break_pos] out += '\n' + indentstr + rest xpos = indent + len(rest) out += p xpos += len(p) if not final: if not next_new_section: out += separator xpos += len(separator) out += ' ' xpos += 1 if not new_section: possible_line_break_pos = len(out) return '<' + out + '>' class ShowFieldType(ShowFieldBase): """ model mixin that shows the object's class and it's field types """ polymorphic_showfield_type = True class ShowFieldContent(ShowFieldBase): """ model mixin that shows the object's class, it's fields and field contents """ polymorphic_showfield_content = True class ShowFieldTypeAndContent(ShowFieldBase): """ model mixin, like ShowFieldContent, but also show field types """ polymorphic_showfield_type = True polymorphic_showfield_content = True # compatibility with old class names ShowFieldTypes = ShowFieldType ShowFields = ShowFieldContent ShowFieldsAndTypes = ShowFieldTypeAndContent django_polymorphic-0.8.1/polymorphic/templates/000077500000000000000000000000001264051031500217475ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/templates/admin/000077500000000000000000000000001264051031500230375ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/templates/admin/polymorphic/000077500000000000000000000000001264051031500254045ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/templates/admin/polymorphic/add_type_form.html000066400000000000000000000004431264051031500311070ustar00rootroot00000000000000{% extends "admin/change_form.html" %} {% if save_on_top %} {% block submit_buttons_top %} {% include 'admin/submit_line.html' with show_save=1 %} {% endblock %} {% endif %} {% block submit_buttons_bottom %} {% include 'admin/submit_line.html' with show_save=1 %} {% endblock %} django_polymorphic-0.8.1/polymorphic/templates/admin/polymorphic/change_form.html000066400000000000000000000002761264051031500305470ustar00rootroot00000000000000{% extends "admin/change_form.html" %} {% load polymorphic_admin_tags %} {% block breadcrumbs %} {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} django_polymorphic-0.8.1/polymorphic/templates/admin/polymorphic/delete_confirmation.html000066400000000000000000000003061264051031500323030ustar00rootroot00000000000000{% extends "admin/delete_confirmation.html" %} {% load polymorphic_admin_tags %} {% block breadcrumbs %} {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} django_polymorphic-0.8.1/polymorphic/templatetags/000077500000000000000000000000001264051031500224435ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/templatetags/__init__.py000066400000000000000000000000001264051031500245420ustar00rootroot00000000000000django_polymorphic-0.8.1/polymorphic/templatetags/polymorphic_admin_tags.py000066400000000000000000000034131264051031500275510ustar00rootroot00000000000000from django.template import Library, Node, TemplateSyntaxError from django.utils import six register = Library() class BreadcrumbScope(Node): def __init__(self, base_opts, nodelist): self.base_opts = base_opts self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists @classmethod def parse(cls, parser, token): bits = token.split_contents() if len(bits) == 2: (tagname, base_opts) = bits base_opts = parser.compile_filter(base_opts) nodelist = parser.parse(('endbreadcrumb_scope',)) parser.delete_first_token() return cls( base_opts=base_opts, nodelist=nodelist ) else: raise TemplateSyntaxError("{0} tag expects 1 argument".format(token.contents[0])) def render(self, context): # app_label is really hard to overwrite in the standard Django ModelAdmin. # To insert it in the template, the entire render_change_form() and delete_view() have to copied and adjusted. # Instead, have an assignment tag that inserts that in the template. base_opts = self.base_opts.resolve(context) new_vars = {} if base_opts and not isinstance(base_opts, six.string_types): new_vars = { 'app_label': base_opts.app_label, # What this is all about 'opts': base_opts, } new_scope = context.push() new_scope.update(new_vars) html = self.nodelist.render(context) context.pop() return html @register.tag def breadcrumb_scope(parser, token): """ Easily allow the breadcrumb to be generated in the admin change templates. """ return BreadcrumbScope.parse(parser, token) django_polymorphic-0.8.1/polymorphic/tests.py000066400000000000000000001241411264051031500214700ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Test Cases Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ """ from __future__ import print_function import uuid import re import django try: from unittest import skipIf except ImportError: # python<2.7 from django.utils.unittest import skipIf from django.db.models.query import QuerySet from django.test import TestCase from django.db.models import Q, Count from django.db import models from django.contrib.contenttypes.models import ContentType from django.utils import six from polymorphic.models import PolymorphicModel from polymorphic.managers import PolymorphicManager from polymorphic.query import PolymorphicQuerySet from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent try: from django.db.models import UUIDField except ImportError: # django<1.8 from polymorphic.tools_for_tests import UUIDField class PlainA(models.Model): field1 = models.CharField(max_length=10) class PlainB(PlainA): field2 = models.CharField(max_length=10) class PlainC(PlainB): field3 = models.CharField(max_length=10) class Model2A(ShowFieldType, PolymorphicModel): field1 = models.CharField(max_length=10) class Model2B(Model2A): field2 = models.CharField(max_length=10) class Model2C(Model2B): field3 = models.CharField(max_length=10) class Model2D(Model2C): field4 = models.CharField(max_length=10) class ModelExtraA(ShowFieldTypeAndContent, PolymorphicModel): field1 = models.CharField(max_length=10) class ModelExtraB(ModelExtraA): field2 = models.CharField(max_length=10) class ModelExtraC(ModelExtraB): field3 = models.CharField(max_length=10) class ModelExtraExternal(models.Model): topic = models.CharField(max_length=10) class ModelShow1(ShowFieldType, PolymorphicModel): field1 = models.CharField(max_length=10) m2m = models.ManyToManyField('self') class ModelShow2(ShowFieldContent, PolymorphicModel): field1 = models.CharField(max_length=10) m2m = models.ManyToManyField('self') class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel): field1 = models.CharField(max_length=10) m2m = models.ManyToManyField('self') class ModelShow1_plain(PolymorphicModel): field1 = models.CharField(max_length=10) class ModelShow2_plain(ModelShow1_plain): field2 = models.CharField(max_length=10) class Base(ShowFieldType, PolymorphicModel): field_b = models.CharField(max_length=10) class ModelX(Base): field_x = models.CharField(max_length=10) class ModelY(Base): field_y = models.CharField(max_length=10) class Enhance_Plain(models.Model): field_p = models.CharField(max_length=10) class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel): base_id = models.AutoField(primary_key=True) field_b = models.CharField(max_length=10) class Enhance_Inherit(Enhance_Base, Enhance_Plain): field_i = models.CharField(max_length=10) class RelationBase(ShowFieldTypeAndContent, PolymorphicModel): field_base = models.CharField(max_length=10) fk = models.ForeignKey('self', null=True, related_name='relationbase_set') m2m = models.ManyToManyField('self') class RelationA(RelationBase): field_a = models.CharField(max_length=10) class RelationB(RelationBase): field_b = models.CharField(max_length=10) class RelationBC(RelationB): field_c = models.CharField(max_length=10) class RelatingModel(models.Model): many2many = models.ManyToManyField(Model2A) class One2OneRelatingModel(PolymorphicModel): one2one = models.OneToOneField(Model2A) field1 = models.CharField(max_length=10) class One2OneRelatingModelDerived(One2OneRelatingModel): field2 = models.CharField(max_length=10) class ModelUnderRelParent(PolymorphicModel): field1 = models.CharField(max_length=10) _private = models.CharField(max_length=10) class ModelUnderRelChild(PolymorphicModel): parent = models.ForeignKey(ModelUnderRelParent, related_name='children') _private2 = models.CharField(max_length=10) class MyManagerQuerySet(PolymorphicQuerySet): def my_queryset_foo(self): return self.all() # Just a method to prove the existance of the custom queryset. class MyManager(PolymorphicManager): queryset_class = MyManagerQuerySet def get_queryset(self): return super(MyManager, self).get_queryset().order_by('-field1') def my_queryset_foo(self): return self.all().my_queryset_foo() # Django <= 1.5 compatibility get_query_set = get_queryset class ModelWithMyManager(ShowFieldTypeAndContent, Model2A): objects = MyManager() field4 = models.CharField(max_length=10) if django.VERSION >= (1, 7): class ModelWithMyManager2(ShowFieldTypeAndContent, Model2A): objects = MyManagerQuerySet.as_manager() field4 = models.CharField(max_length=10) class MROBase1(ShowFieldType, PolymorphicModel): objects = MyManager() field1 = models.CharField(max_length=10) # needed as MyManager uses it class MROBase2(MROBase1): pass # Django vanilla inheritance does not inherit MyManager as _default_manager here class MROBase3(models.Model): objects = PolymorphicManager() class MRODerived(MROBase2, MROBase3): pass class ParentModelWithManager(PolymorphicModel): pass class ChildModelWithManager(PolymorphicModel): # Also test whether foreign keys receive the manager: fk = models.ForeignKey(ParentModelWithManager, related_name='childmodel_set') objects = MyManager() class PlainMyManagerQuerySet(QuerySet): def my_queryset_foo(self): return self.all() # Just a method to prove the existance of the custom queryset. class PlainMyManager(models.Manager): def my_queryset_foo(self): return self.get_queryset().my_queryset_foo() def get_queryset(self): return PlainMyManagerQuerySet(self.model, using=self._db) # Django <= 1.5 compatibility get_query_set = get_queryset class PlainParentModelWithManager(models.Model): pass class PlainChildModelWithManager(models.Model): fk = models.ForeignKey(PlainParentModelWithManager, related_name='childmodel_set') objects = PlainMyManager() class MgrInheritA(models.Model): mgrA = models.Manager() mgrA2 = models.Manager() field1 = models.CharField(max_length=10) class MgrInheritB(MgrInheritA): mgrB = models.Manager() field2 = models.CharField(max_length=10) class MgrInheritC(ShowFieldTypeAndContent, MgrInheritB): pass class BlogBase(ShowFieldTypeAndContent, PolymorphicModel): name = models.CharField(max_length=10) class BlogA(BlogBase): info = models.CharField(max_length=10) class BlogB(BlogBase): pass class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel): blog = models.ForeignKey(BlogA) text = models.CharField(max_length=10) class BlogEntry_limit_choices_to(ShowFieldTypeAndContent, PolymorphicModel): blog = models.ForeignKey(BlogBase) text = models.CharField(max_length=10) class ModelFieldNameTest(ShowFieldType, PolymorphicModel): modelfieldnametest = models.CharField(max_length=10) class InitTestModel(ShowFieldType, PolymorphicModel): bar = models.CharField(max_length=100) def __init__(self, *args, **kwargs): kwargs['bar'] = self.x() super(InitTestModel, self).__init__(*args, **kwargs) class InitTestModelSubclass(InitTestModel): def x(self): return 'XYZ' # models from github issue class Top(PolymorphicModel): name = models.CharField(max_length=50) class Meta: ordering = ('pk',) class Middle(Top): description = models.TextField() class Bottom(Middle): author = models.CharField(max_length=50) class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel): uuid_primary_key = UUIDField(primary_key=True, default=uuid.uuid1) topic = models.CharField(max_length=30) class UUIDArtProject(UUIDProject): artist = models.CharField(max_length=30) class UUIDResearchProject(UUIDProject): supervisor = models.CharField(max_length=30) class UUIDPlainA(models.Model): uuid_primary_key = UUIDField(primary_key=True, default=uuid.uuid1) field1 = models.CharField(max_length=10) class UUIDPlainB(UUIDPlainA): field2 = models.CharField(max_length=10) class UUIDPlainC(UUIDPlainB): field3 = models.CharField(max_length=10) # base -> proxy class ProxyBase(PolymorphicModel): some_data = models.CharField(max_length=128) class ProxyChild(ProxyBase): class Meta: proxy = True class NonProxyChild(ProxyBase): name = models.CharField(max_length=10) # base -> proxy -> real models class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel): name = models.CharField(max_length=10) class ProxyModelBase(ProxiedBase): class Meta: proxy = True class ProxyModelA(ProxyModelBase): field1 = models.CharField(max_length=10) class ProxyModelB(ProxyModelBase): field2 = models.CharField(max_length=10) # test bad field name # class TestBadFieldModel(ShowFieldType, PolymorphicModel): # instance_of = models.CharField(max_length=10) # validation error: "polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes # with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram) # fixed with related_name class RelatedNameClash(ShowFieldType, PolymorphicModel): ctype = models.ForeignKey(ContentType, null=True, editable=False) # class with a parent_link to superclass, and a related_name back to subclass class TestParentLinkAndRelatedName(ModelShow1_plain): superclass = models.OneToOneField(ModelShow1_plain, parent_link=True, related_name='related_name_subclass') class CustomPkBase(ShowFieldTypeAndContent, PolymorphicModel): b = models.CharField(max_length=1) class CustomPkInherit(CustomPkBase): custom_id = models.AutoField(primary_key=True) i = models.CharField(max_length=1) class PolymorphicTests(TestCase): """ The test suite """ def test_annotate_aggregate_order(self): # create a blog of type BlogA # create two blog entries in BlogA # create some blogs of type BlogB to make the BlogBase table data really polymorphic blog = BlogA.objects.create(name='B1', info='i1') blog.blogentry_set.create(text='bla') BlogEntry.objects.create(blog=blog, text='bla2') BlogB.objects.create(name='Bb1') BlogB.objects.create(name='Bb2') BlogB.objects.create(name='Bb3') qs = BlogBase.objects.annotate(entrycount=Count('BlogA___blogentry')) self.assertEqual(len(qs), 4) for o in qs: if o.name == 'B1': self.assertEqual(o.entrycount, 2) else: self.assertEqual(o.entrycount, 0) x = BlogBase.objects.aggregate(entrycount=Count('BlogA___blogentry')) self.assertEqual(x['entrycount'], 2) # create some more blogs for next test BlogA.objects.create(name='B2', info='i2') BlogA.objects.create(name='B3', info='i3') BlogA.objects.create(name='B4', info='i4') BlogA.objects.create(name='B5', info='i5') # test ordering for field in all entries expected = ''' [ , , , , , , , ]''' x = '\n' + repr(BlogBase.objects.order_by('-name')) self.assertEqual(x, expected) # test ordering for field in one subclass only # MySQL and SQLite return this order expected1 = ''' [ , , , , , , , ]''' # PostgreSQL returns this order expected2 = ''' [ , , , , , , , ]''' x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info')) self.assertTrue(x == expected1 or x == expected2) def test_limit_choices_to(self): """ this is not really a testcase, as limit_choices_to only affects the Django admin """ # create a blog of type BlogA blog_a = BlogA.objects.create(name='aa', info='aa') blog_b = BlogB.objects.create(name='bb') # create two blog entries entry1 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2') entry2 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2') def test_primary_key_custom_field_problem(self): """ object retrieval problem occuring with some custom primary key fields (UUIDField as test case) """ UUIDProject.objects.create(topic="John's gathering") UUIDArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner") UUIDResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter") qs = UUIDProject.objects.all() ol = list(qs) a = qs[0] b = qs[1] c = qs[2] self.assertEqual(len(qs), 3) self.assertIsInstance(a.uuid_primary_key, uuid.UUID) self.assertIsInstance(a.pk, uuid.UUID) res = re.sub(' "(.*?)..", topic', ', topic', repr(qs)) res_exp = """[ , , ]""" self.assertEqual(res, res_exp) a = UUIDPlainA.objects.create(field1='A1') b = UUIDPlainB.objects.create(field1='B1', field2='B2') c = UUIDPlainC.objects.create(field1='C1', field2='C2', field3='C3') qs = UUIDPlainA.objects.all() # Test that primary key values are valid UUIDs self.assertEqual(uuid.UUID("urn:uuid:%s" % a.pk, version=1), a.pk) self.assertEqual(uuid.UUID("urn:uuid:%s" % c.pk, version=1), c.pk) def create_model2abcd(self): """ Create the chain of objects of Model2, this is reused in various tests. """ Model2A.objects.create(field1='A1') Model2B.objects.create(field1='B1', field2='B2') Model2C.objects.create(field1='C1', field2='C2', field3='C3') Model2D.objects.create(field1='D1', field2='D2', field3='D3', field4='D4') def test_simple_inheritance(self): self.create_model2abcd() objects = list(Model2A.objects.all()) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(repr(objects[3]), '') def test_manual_get_real_instance(self): self.create_model2abcd() o = Model2A.objects.non_polymorphic().get(field1='C1') self.assertEqual(repr(o.get_real_instance()), '') def test_non_polymorphic(self): self.create_model2abcd() objects = list(Model2A.objects.all().non_polymorphic()) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(repr(objects[3]), '') def test_get_real_instances(self): self.create_model2abcd() qs = Model2A.objects.all().non_polymorphic() # from queryset objects = qs.get_real_instances() self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(repr(objects[3]), '') # from a manual list objects = Model2A.objects.get_real_instances(list(qs)) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(repr(objects[3]), '') def test_translate_polymorphic_q_object(self): self.create_model2abcd() q = Model2A.translate_polymorphic_Q_object(Q(instance_of=Model2C)) objects = Model2A.objects.filter(q) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') def test_base_manager(self): def show_base_manager(model): return "{0} {1}".format( repr(type(model._base_manager)), repr(model._base_manager.model) ) self.assertEqual(show_base_manager(PlainA), " ") self.assertEqual(show_base_manager(PlainB), " ") self.assertEqual(show_base_manager(PlainC), " ") self.assertEqual(show_base_manager(Model2A), " ") self.assertEqual(show_base_manager(Model2B), " ") self.assertEqual(show_base_manager(Model2C), " ") self.assertEqual(show_base_manager(One2OneRelatingModel), " ") self.assertEqual(show_base_manager(One2OneRelatingModelDerived), " ") def test_instance_default_manager(self): def show_default_manager(instance): return "{0} {1}".format( repr(type(instance._default_manager)), repr(instance._default_manager.model) ) plain_a = PlainA(field1='C1') plain_b = PlainB(field2='C1') plain_c = PlainC(field3='C1') model_2a = Model2A(field1='C1') model_2b = Model2B(field2='C1') model_2c = Model2C(field3='C1') self.assertEqual(show_default_manager(plain_a), " ") self.assertEqual(show_default_manager(plain_b), " ") self.assertEqual(show_default_manager(plain_c), " ") self.assertEqual(show_default_manager(model_2a), " ") self.assertEqual(show_default_manager(model_2b), " ") self.assertEqual(show_default_manager(model_2c), " ") def test_foreignkey_field(self): self.create_model2abcd() object2a = Model2A.base_objects.get(field1='C1') self.assertEqual(repr(object2a.model2b), '') object2b = Model2B.base_objects.get(field1='C1') self.assertEqual(repr(object2b.model2c), '') def test_onetoone_field(self): self.create_model2abcd() a = Model2A.base_objects.get(field1='C1') b = One2OneRelatingModelDerived.objects.create(one2one=a, field1='f1', field2='f2') # this result is basically wrong, probably due to Django cacheing (we used base_objects), but should not be a problem self.assertEqual(repr(b.one2one), '') c = One2OneRelatingModelDerived.objects.get(field1='f1') self.assertEqual(repr(c.one2one), '') self.assertEqual(repr(a.one2onerelatingmodel), '') def test_manytomany_field(self): # Model 1 o = ModelShow1.objects.create(field1='abc') o.m2m.add(o) o.save() self.assertEqual(repr(ModelShow1.objects.all()), '[ ]') # Model 2 o = ModelShow2.objects.create(field1='abc') o.m2m.add(o) o.save() self.assertEqual(repr(ModelShow2.objects.all()), '[ ]') # Model 3 o = ModelShow3.objects.create(field1='abc') o.m2m.add(o) o.save() self.assertEqual(repr(ModelShow3.objects.all()), '[ ]') self.assertEqual(repr(ModelShow1.objects.all().annotate(Count('m2m'))), '[ ]') self.assertEqual(repr(ModelShow2.objects.all().annotate(Count('m2m'))), '[ ]') self.assertEqual(repr(ModelShow3.objects.all().annotate(Count('m2m'))), '[ ]') # no pretty printing ModelShow1_plain.objects.create(field1='abc') ModelShow2_plain.objects.create(field1='abc', field2='def') self.assertEqual(repr(ModelShow1_plain.objects.all()), '[, ]') def test_extra_method(self): self.create_model2abcd() objects = list(Model2A.objects.extra(where=['id IN (2, 3)'])) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') objects = Model2A.objects.extra(select={"select_test": "field1 = 'A1'"}, where=["field1 = 'A1' OR field1 = 'B1'"], order_by=['-id']) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(len(objects), 2) # Placed after the other tests, only verifying whether there are no more additional objects. ModelExtraA.objects.create(field1='A1') ModelExtraB.objects.create(field1='B1', field2='B2') ModelExtraC.objects.create(field1='C1', field2='C2', field3='C3') ModelExtraExternal.objects.create(topic='extra1') ModelExtraExternal.objects.create(topic='extra2') ModelExtraExternal.objects.create(topic='extra3') objects = ModelExtraA.objects.extra(tables=["polymorphic_modelextraexternal"], select={"topic": "polymorphic_modelextraexternal.topic"}, where=["polymorphic_modelextraa.id = polymorphic_modelextraexternal.id"]) if six.PY3: self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') else: self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(len(objects), 3) def test_instance_of_filter(self): self.create_model2abcd() objects = Model2A.objects.instance_of(Model2B) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(len(objects), 3) objects = Model2A.objects.filter(instance_of=Model2B) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(len(objects), 3) objects = Model2A.objects.filter(Q(instance_of=Model2B)) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(len(objects), 3) objects = Model2A.objects.not_instance_of(Model2B) self.assertEqual(repr(objects[0]), '') self.assertEqual(len(objects), 1) def test_polymorphic___filter(self): self.create_model2abcd() objects = Model2A.objects.filter(Q(Model2B___field2='B2') | Q(Model2C___field3='C3')) self.assertEqual(len(objects), 2) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') def test_polymorphic___filter_field(self): p = ModelUnderRelParent.objects.create(_private=True, field1='AA') ModelUnderRelChild.objects.create(parent=p, _private2=True) # The "___" filter should also parse to "parent" -> "_private" as fallback. objects = ModelUnderRelChild.objects.filter(parent___private=True) self.assertEqual(len(objects), 1) def test_polymorphic___filter_reverse_field(self): p = ModelUnderRelParent.objects.create(_private=True, field1='BB') ModelUnderRelChild.objects.create(parent=p, _private2=True) # Also test for reverse relations objects = ModelUnderRelParent.objects.filter(children___private2=True) self.assertEqual(len(objects), 1) def test_delete(self): self.create_model2abcd() oa = Model2A.objects.get(id=2) self.assertEqual(repr(oa), '') self.assertEqual(Model2A.objects.count(), 4) oa.delete() objects = Model2A.objects.all() self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(len(objects), 3) def test_combine_querysets(self): ModelX.objects.create(field_x='x') ModelY.objects.create(field_y='y') qs = Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) self.assertEqual(repr(qs[0]), '') self.assertEqual(repr(qs[1]), '') self.assertEqual(len(qs), 2) def test_multiple_inheritance(self): # multiple inheritance, subclassing third party models (mix PolymorphicModel with models.Model) Enhance_Base.objects.create(field_b='b-base') Enhance_Inherit.objects.create(field_b='b-inherit', field_p='p', field_i='i') qs = Enhance_Base.objects.all() self.assertEqual(len(qs), 2) self.assertEqual(repr(qs[0]), '') self.assertEqual(repr(qs[1]), '') def test_relation_base(self): # ForeignKey, ManyToManyField obase = RelationBase.objects.create(field_base='base') oa = RelationA.objects.create(field_base='A1', field_a='A2', fk=obase) ob = RelationB.objects.create(field_base='B1', field_b='B2', fk=oa) oc = RelationBC.objects.create(field_base='C1', field_b='C2', field_c='C3', fk=oa) oa.m2m.add(oa) oa.m2m.add(ob) objects = RelationBase.objects.all() self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(repr(objects[2]), '') self.assertEqual(repr(objects[3]), '') self.assertEqual(len(objects), 4) oa = RelationBase.objects.get(id=2) self.assertEqual(repr(oa.fk), '') objects = oa.relationbase_set.all() self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(len(objects), 2) ob = RelationBase.objects.get(id=3) self.assertEqual(repr(ob.fk), '') oa = RelationA.objects.get() objects = oa.m2m.all() self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(len(objects), 2) def test_user_defined_manager(self): self.create_model2abcd() ModelWithMyManager.objects.create(field1='D1a', field4='D4a') ModelWithMyManager.objects.create(field1='D1b', field4='D4b') objects = ModelWithMyManager.objects.all() # MyManager should reverse the sorting of field1 self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(len(objects), 2) self.assertIs(type(ModelWithMyManager.objects), MyManager) self.assertIs(type(ModelWithMyManager._default_manager), MyManager) self.assertIs(type(ModelWithMyManager.base_objects), models.Manager) @skipIf(django.VERSION < (1, 7), "This test needs Django 1.7+") def test_user_defined_queryset_as_manager(self): self.create_model2abcd() ModelWithMyManager2.objects.create(field1='D1a', field4='D4a') ModelWithMyManager2.objects.create(field1='D1b', field4='D4b') objects = ModelWithMyManager2.objects.all() self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertEqual(len(objects), 2) self.assertEqual(type(ModelWithMyManager2.objects).__name__, 'PolymorphicManagerFromMyManagerQuerySet') self.assertEqual(type(ModelWithMyManager2._default_manager).__name__, 'PolymorphicManagerFromMyManagerQuerySet') self.assertIs(type(ModelWithMyManager2.base_objects), models.Manager) def test_manager_inheritance(self): # by choice of MRO, should be MyManager from MROBase1. self.assertIs(type(MRODerived.objects), MyManager) # check for correct default manager self.assertIs(type(MROBase1._default_manager), MyManager) # Django vanilla inheritance does not inherit MyManager as _default_manager here self.assertIs(type(MROBase2._default_manager), MyManager) def test_queryset_assignment(self): # This is just a consistency check for now, testing standard Django behavior. parent = PlainParentModelWithManager.objects.create() child = PlainChildModelWithManager.objects.create(fk=parent) self.assertIs(type(PlainParentModelWithManager._default_manager), models.Manager) self.assertIs(type(PlainChildModelWithManager._default_manager), PlainMyManager) self.assertIs(type(PlainChildModelWithManager.objects), PlainMyManager) self.assertIs(type(PlainChildModelWithManager.objects.all()), PlainMyManagerQuerySet) # A related set is created using the model's _default_manager, so does gain extra methods. self.assertIs(type(parent.childmodel_set.my_queryset_foo()), PlainMyManagerQuerySet) # For polymorphic models, the same should happen. parent = ParentModelWithManager.objects.create() child = ChildModelWithManager.objects.create(fk=parent) self.assertIs(type(ParentModelWithManager._default_manager), PolymorphicManager) self.assertIs(type(ChildModelWithManager._default_manager), MyManager) self.assertIs(type(ChildModelWithManager.objects), MyManager) self.assertIs(type(ChildModelWithManager.objects.my_queryset_foo()), MyManagerQuerySet) # A related set is created using the model's _default_manager, so does gain extra methods. self.assertIs(type(parent.childmodel_set.my_queryset_foo()), MyManagerQuerySet) def test_proxy_models(self): # prepare some data for data in ('bleep bloop', 'I am a', 'computer'): ProxyChild.objects.create(some_data=data) # this caches ContentType queries so they don't interfere with our query counts later list(ProxyBase.objects.all()) # one query per concrete class with self.assertNumQueries(1): items = list(ProxyBase.objects.all()) self.assertIsInstance(items[0], ProxyChild) def test_proxy_get_real_instance_class(self): """ The call to ``get_real_instance()`` also checks whether the returned model is of the correct type. This unit test guards that this check is working properly. For instance, proxy child models need to be handled separately. """ name = "Item1" nonproxychild = NonProxyChild.objects.create(name=name) pb = ProxyBase.objects.get(id=1) self.assertEqual(pb.get_real_instance_class(), NonProxyChild) self.assertEqual(pb.get_real_instance(), nonproxychild) self.assertEqual(pb.name, name) pbm = ProxyChild.objects.get(id=1) self.assertEqual(pbm.get_real_instance_class(), NonProxyChild) self.assertEqual(pbm.get_real_instance(), nonproxychild) self.assertEqual(pbm.name, name) def test_content_types_for_proxy_models(self): """Checks if ContentType is capable of returning proxy models.""" from django.db.models import Model from django.contrib.contenttypes.models import ContentType ct = ContentType.objects.get_for_model(ProxyChild, for_concrete_model=False) self.assertEqual(ProxyChild, ct.model_class()) def test_proxy_model_inheritance(self): """ Polymorphic abilities should also work when the base model is a proxy object. """ # The managers should point to the proper objects. # otherwise, the whole excersise is pointless. self.assertEqual(ProxiedBase.objects.model, ProxiedBase) self.assertEqual(ProxyModelBase.objects.model, ProxyModelBase) self.assertEqual(ProxyModelA.objects.model, ProxyModelA) self.assertEqual(ProxyModelB.objects.model, ProxyModelB) # Create objects ProxyModelA.objects.create(name="object1") ProxyModelB.objects.create(name="object2", field2="bb") # Getting single objects object1 = ProxyModelBase.objects.get(name='object1') object2 = ProxyModelBase.objects.get(name='object2') self.assertEqual(repr(object1), '') self.assertEqual(repr(object2), '') self.assertIsInstance(object1, ProxyModelA) self.assertIsInstance(object2, ProxyModelB) # Same for lists objects = list(ProxyModelBase.objects.all().order_by('name')) self.assertEqual(repr(objects[0]), '') self.assertEqual(repr(objects[1]), '') self.assertIsInstance(objects[0], ProxyModelA) self.assertIsInstance(objects[1], ProxyModelB) def test_custom_pk(self): CustomPkBase.objects.create(b='b') CustomPkInherit.objects.create(b='b', i='i') qs = CustomPkBase.objects.all() self.assertEqual(len(qs), 2) self.assertEqual(repr(qs[0]), '') self.assertEqual(repr(qs[1]), '') def test_fix_getattribute(self): # fixed issue in PolymorphicModel.__getattribute__: field name same as model name o = ModelFieldNameTest.objects.create(modelfieldnametest='1') self.assertEqual(repr(o), '') # if subclass defined __init__ and accessed class members, # __getattribute__ had a problem: "...has no attribute 'sub_and_superclass_dict'" o = InitTestModelSubclass.objects.create() self.assertEqual(o.bar, 'XYZ') def test_parent_link_and_related_name(self): t = TestParentLinkAndRelatedName(field1="TestParentLinkAndRelatedName") t.save() p = ModelShow1_plain.objects.get(field1="TestParentLinkAndRelatedName") # check that p is equal to the self.assertIsInstance(p, TestParentLinkAndRelatedName) self.assertEqual(p, t) # check that the accessors to parent and sublass work correctly and return the right object p = ModelShow1_plain.objects.non_polymorphic().get(field1="TestParentLinkAndRelatedName") self.assertNotEqual(p, t) # p should be Plain1 and t TestParentLinkAndRelatedName, so not equal self.assertEqual(p, t.superclass) self.assertEqual(p.related_name_subclass, t) # test that we can delete the object t.delete() class RegressionTests(TestCase): def test_for_query_result_incomplete_with_inheritance(self): """ https://github.com/bconstantin/django_polymorphic/issues/15 """ top = Top() top.save() middle = Middle() middle.save() bottom = Bottom() bottom.save() expected_queryset = [top, middle, bottom] self.assertQuerysetEqual(Top.objects.all(), [repr(r) for r in expected_queryset]) expected_queryset = [middle, bottom] self.assertQuerysetEqual(Middle.objects.all(), [repr(r) for r in expected_queryset]) expected_queryset = [bottom] self.assertQuerysetEqual(Bottom.objects.all(), [repr(r) for r in expected_queryset]) django_polymorphic-0.8.1/polymorphic/tools_for_tests.py000066400000000000000000000122551264051031500235600ustar00rootroot00000000000000# Compatibility module for Django < 1.8 import uuid from django import forms from django.db import models from django.utils.encoding import smart_text from django.utils import six class UUIDVersionError(Exception): pass class UUIDField(six.with_metaclass(models.SubfieldBase, models.CharField)): """Encode and stores a Python uuid.UUID in a manner that is appropriate for the given datatabase that we are using. For sqlite3 or MySQL we save it as a 36-character string value For PostgreSQL we save it as a uuid field This class supports type 1, 2, 4, and 5 UUID's. """ _CREATE_COLUMN_TYPES = { 'postgresql_psycopg2': 'uuid', 'postgresql': 'uuid' } def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs): """Contruct a UUIDField. @param verbose_name: Optional verbose name to use in place of what Django would assign. @param name: Override Django's name assignment @param auto: If True, create a UUID value if one is not specified. @param version: By default we create a version 1 UUID. @param node: Used for version 1 UUID's. If not supplied, then the uuid.getnode() function is called to obtain it. This can be slow. @param clock_seq: Used for version 1 UUID's. If not supplied a random 14-bit sequence number is chosen @param namespace: Required for version 3 and version 5 UUID's. @param name: Required for version4 and version 5 UUID's. See Also: - Python Library Reference, section 18.16 for more information. - RFC 4122, "A Universally Unique IDentifier (UUID) URN Namespace" If you want to use one of these as a primary key for a Django model, do this:: id = UUIDField(primary_key=True) This will currently I{not} work with Jython because PostgreSQL support in Jython is not working for uuid column types. """ self.max_length = 36 kwargs['max_length'] = self.max_length if auto: kwargs['blank'] = True kwargs.setdefault('editable', False) self.auto = auto self.version = version if version == 1: self.node, self.clock_seq = node, clock_seq elif version == 3 or version == 5: self.namespace, self.name = namespace, name super(UUIDField, self).__init__(verbose_name=verbose_name, name=name, **kwargs) def create_uuid(self): if not self.version or self.version == 4: return uuid.uuid4() elif self.version == 1: return uuid.uuid1(self.node, self.clock_seq) elif self.version == 2: raise UUIDVersionError("UUID version 2 is not supported.") elif self.version == 3: return uuid.uuid3(self.namespace, self.name) elif self.version == 5: return uuid.uuid5(self.namespace, self.name) else: raise UUIDVersionError("UUID version %s is not valid." % self.version) def db_type(self, connection): from django.conf import settings full_database_type = settings.DATABASES['default']['ENGINE'] database_type = full_database_type.split('.')[-1] return UUIDField._CREATE_COLUMN_TYPES.get(database_type, "char(%s)" % self.max_length) def to_python(self, value): """Return a uuid.UUID instance from the value returned by the database.""" # # This is the proper way... But this doesn't work correctly when # working with an inherited model # if not value: return None if isinstance(value, uuid.UUID): return value # attempt to parse a UUID return uuid.UUID(smart_text(value)) # # If I do the following (returning a String instead of a UUID # instance), everything works. # # if not value: # return None # if isinstance(value, uuid.UUID): # return smart_text(value) # else: # return value def pre_save(self, model_instance, add): if self.auto and add: value = self.create_uuid() setattr(model_instance, self.attname, value) else: value = super(UUIDField, self).pre_save(model_instance, add) if self.auto and not value: value = self.create_uuid() setattr(model_instance, self.attname, value) return value def get_db_prep_value(self, value, connection, prepared): """Casts uuid.UUID values into the format expected by the back end for use in queries""" if isinstance(value, uuid.UUID): return smart_text(value) return value def value_to_string(self, obj): val = self._get_val_from_obj(obj) if val is None: data = '' else: data = smart_text(val) return data def formfield(self, **kwargs): defaults = { 'form_class': forms.CharField, 'max_length': self.max_length } defaults.update(kwargs) return super(UUIDField, self).formfield(**defaults) django_polymorphic-0.8.1/runtests.py000077500000000000000000000040411264051031500176470ustar00rootroot00000000000000#!/usr/bin/env python import django import os import sys from django.conf import settings from django.core.management import execute_from_command_line from django.conf import settings, global_settings as default_settings from django.core.management import call_command from os.path import dirname, realpath # Give feedback on used versions sys.stderr.write('Using Python version {0} from {1}\n'.format(sys.version[:5], sys.executable)) sys.stderr.write('Using Django version {0} from {1}\n'.format( django.get_version(), os.path.dirname(os.path.abspath(django.__file__))) ) if not settings.configured: settings.configure( DEBUG=True, TEMPLATE_DEBUG=True, DATABASES={ 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' } }, TEMPLATE_LOADERS=( 'django.template.loaders.app_directories.Loader', ), TEMPLATE_CONTEXT_PROCESSORS=( # list() is only needed for older versions of django where this is # a tuple: list(default_settings.TEMPLATE_CONTEXT_PROCESSORS) + [ 'django.core.context_processors.request', ] ), TEST_RUNNER = 'django.test.runner.DiscoverRunner' if django.VERSION >= (1, 7) else 'django.test.simple.DjangoTestSuiteRunner', INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.messages', 'django.contrib.sites', 'django.contrib.admin', 'polymorphic', ), MIDDLEWARE_CLASSES = (), SITE_ID = 3, ) DEFAULT_TEST_APPS = [ 'polymorphic', ] def runtests(): other_args = list(filter(lambda arg: arg.startswith('-'), sys.argv[1:])) test_apps = list(filter(lambda arg: not arg.startswith('-'), sys.argv[1:])) or DEFAULT_TEST_APPS argv = sys.argv[:1] + ['test', '--traceback'] + other_args + test_apps execute_from_command_line(argv) if __name__ == '__main__': runtests() django_polymorphic-0.8.1/setup.cfg000066400000000000000000000000761264051031500172300ustar00rootroot00000000000000[wheel] # create "py2.py3-none-any.whl" package universal = 1 django_polymorphic-0.8.1/setup.py000077500000000000000000000036011264051031500171210ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup, find_packages from os import path import codecs import os import re import sys def read(*parts): file_path = path.join(path.dirname(__file__), *parts) return codecs.open(file_path, encoding='utf-8').read() def find_version(*parts): version_file = read(*parts) version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return str(version_match.group(1)) raise RuntimeError("Unable to find version string.") setup( name='django_polymorphic', version=find_version('polymorphic', '__init__.py'), license='BSD', description='Seamless Polymorphic Inheritance for Django Models', long_description=read('README.rst'), url='https://github.com/chrisglass/django_polymorphic', author='Bert Constantin', author_email='bert.constantin@gmx.de', maintainer='Christopher Glass', maintainer_email='tribaal@gmail.com', packages=find_packages(), package_data={ 'polymorphic': [ 'templates/admin/polymorphic/*.html', ], }, install_requires=['setuptools'], test_suite='runtests', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.3', 'Framework :: Django', 'Framework :: Django :: 1.4', 'Framework :: Django :: 1.5', 'Framework :: Django :: 1.6', 'Framework :: Django :: 1.7', 'Framework :: Django :: 1.8', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) django_polymorphic-0.8.1/tox.ini000066400000000000000000000012441264051031500167200ustar00rootroot00000000000000[tox] envlist= py26-django{14,15,16}, py27-django{14,15,16,17,18,19}, py32-django{15,16,17,18}, py33-django{15,16,17,18}, py34-django{15,16,17,18,19}, py35-django{18,19} # py33-django-dev, docs, [testenv] deps = django14: Django >= 1.4, < 1.5 django15: Django >= 1.5, < 1.6 django16: Django >= 1.6, < 1.7 django17: Django >= 1.7, < 1.8 django18: Django >= 1.8, < 1.9 django19: Django >= 1.9, < 1.10 django-dev: https://github.com/django/django/tarball/master commands= python runtests.py [testenv:docs] deps=Sphinx changedir = docs commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html