pax_global_header00006660000000000000000000000064135131573110014512gustar00rootroot0000000000000052 comment=ad4e6e90c82f897300c1c135bd7a95e4b2d802a3 django-polymorphic-2.1.2/000077500000000000000000000000001351315731100153215ustar00rootroot00000000000000django-polymorphic-2.1.2/.gitignore000066400000000000000000000002531351315731100173110ustar00rootroot00000000000000*.pyc *.pyo *.mo *.db *.egg-info/ *.egg/ .coverage coverage.xml .project .idea/ .pydevproject .idea/workspace.xml .tox/ .DS_Store build/ dist/ docs/_build/ htmlcov/ venv/ django-polymorphic-2.1.2/.travis.yml000066400000000000000000000035071351315731100174370ustar00rootroot00000000000000# https://travis-ci.org/django-polymorphic/django-polymorphic dist: xenial sudo: false language: python services: - postgres addons: postgresql: "9.6" matrix: fast_finish: true include: # Django 1.11: Python 2.7, 3.5, or 3.6 - { env: TOXENV=py27-django111, python: 2.7 } - { env: TOXENV=py35-django111, python: 3.5 } - { env: TOXENV=py36-django111, python: 3.6 } - { env: TOXENV=py36-django111-postgres DB=postgres, python: 3.6 } # Django 2.0: Python 3.5, or 3.6 - { env: TOXENV=py35-django20, python: 3.5 } - { env: TOXENV=py36-django20, python: 3.6 } - { env: TOXENV=py36-django20-postgres DB=postgres, python: 3.6 } # Django 2.1: Python 3.6, or 3.7 - { env: TOXENV=py36-django21, python: 3.6 } - { env: TOXENV=py37-django21, python: 3.7 } - { env: TOXENV=py37-django21-postgres DB=postgres, python: 3.7 } # Django 2.2: Python 3.6, or 3.7 - { env: TOXENV=py36-django22, python: 3.6 } - { env: TOXENV=py37-django22, python: 3.7 } - { env: TOXENV=py37-django22-postgres DB=postgres, python: 3.7 } # Django development master (direct from GitHub source): - { env: TOXENV=py36-djangomaster, python: 3.6 } - { env: TOXENV=py37-djangomaster, python: 3.7 } - { env: TOXENV=py37-djangomaster-postgres DB=postgres, python: 3.7 } allow_failures: - env: TOXENV=py36-djangomaster - env: TOXENV=py37-djangomaster - env: TOXENV=py37-djangomaster-postgres DB=postgres cache: directories: - $HOME/.cache/pip - $TRAVIS_BUILD_DIR/.tox before_install: - psql -c 'CREATE DATABASE default;' -U postgres || true - psql -c 'CREATE DATABASE secondary;' -U postgres || true install: - pip install --upgrade pip wheel setuptools - pip install codecov coverage tox script: - tox after_success: - coverage xml -i - codecov branches: only: - master django-polymorphic-2.1.2/AUTHORS.rst000066400000000000000000000024461351315731100172060ustar00rootroot00000000000000Main authors (commit rights to the main repository) =================================================== * Chris Glass * Diederik van der Boor * Charlie Denton * Jerome Leclanche Contributors ============= * Abel Daniel * Adam Chainz * Adam Wentz * Andrew Ingram (contributed setup.py) * Al Johri * Alex Alvarez * Andrew Dodd * Angel Velasquez * Austin Matsick * Ben Konrath * Bert Constantin * Bertrand Bordage * Chad Shryock * Charles Leifer (python 2.4 compatibility) * Chris Barna * Chris Brantley * Christopher Glass * David Sanders * Éric Araujo * Evan Borgstrom * Frankie Dintino * Gavin Wahl * Germán M. Bravo * Gonzalo Bustos * Gregory Avery-Weir * Hugo Osvaldo Barrera * Jacob Rief * James Murty * Jedediah Smith (proxy models support) * John Furr * Jonas Haag * Jonas Obrist * Julian Wachholz * Kamil Bar * Kelsey Gilmore-Innis * Kevin Armenat * Krzysztof Gromadzki * Krzysztof Nazarewski * Luis Zárate * Marius Lueck * Martin Brochhaus * Martin Maillard * Michael Fladischer * Nick Ward * Oleg Myltsyn * Omer Strumpf * Paweł Adamczak * Petr Dlouhý * Sander van Leeuwen * Sobolev Nikita * Tadas Dailyda * Tai Lee * Tomas Peterka * Tony Narlock * Vail Gold Former authors / maintainers ============================ * Bert Constantin 2009/2010 (Original author, disappeared from the internet :( ) django-polymorphic-2.1.2/LICENSE000066400000000000000000000030431351315731100163260ustar00rootroot00000000000000Copyright (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-2.1.2/MANIFEST.in000066400000000000000000000002471351315731100170620ustar00rootroot00000000000000include README.rst include LICENSE include DOCS.rst include CHANGES.rst recursive-include polymorphic/static *.js *.css recursive-include polymorphic/templates *.html django-polymorphic-2.1.2/README.rst000066400000000000000000000063241351315731100170150ustar00rootroot00000000000000.. image:: https://travis-ci.org/django-polymorphic/django-polymorphic.svg?branch=master :target: http://travis-ci.org/django-polymorphic/django-polymorphic .. image:: https://img.shields.io/pypi/v/django-polymorphic.svg :target: https://pypi.python.org/pypi/django-polymorphic/ .. image:: https://img.shields.io/codecov/c/github/django-polymorphic/django-polymorphic/master.svg :target: https://codecov.io/github/django-polymorphic/django-polymorphic?branch=master .. image:: https://readthedocs.org/projects/django-polymorphic/badge/?version=stable :target: https://django-polymorphic.readthedocs.io/en/stable/ 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... .. code-block:: python >>> 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! .. code-block:: python >>> Project.objects.all() [ , , ] Using vanilla Django, we get the base class objects, which is rarely what we wanted: .. code-block:: python >>> 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! The current release of *django-polymorphic* supports Django 1.11, 2.0, 2.1, 2.2 and Python 2.7 and 3.5+ is supported. For older Django versions, install *django-polymorphic==1.3*. For more information, see the `documentation at Read the Docs `_. Installation ------------ Install using ``pip``\ ... .. code:: bash $ pip install django-polymorphic License ======= Django-polymorphic uses the same license as Django (BSD-like). django-polymorphic-2.1.2/docs/000077500000000000000000000000001351315731100162515ustar00rootroot00000000000000django-polymorphic-2.1.2/docs/Makefile000066400000000000000000000127541351315731100177220ustar00rootroot00000000000000# 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-2.1.2/docs/_ext/000077500000000000000000000000001351315731100172105ustar00rootroot00000000000000django-polymorphic-2.1.2/docs/_ext/djangodummy/000077500000000000000000000000001351315731100215265ustar00rootroot00000000000000django-polymorphic-2.1.2/docs/_ext/djangodummy/__init__.py000066400000000000000000000000001351315731100236250ustar00rootroot00000000000000django-polymorphic-2.1.2/docs/_ext/djangodummy/requirements.txt000066400000000000000000000002201351315731100250040ustar00rootroot00000000000000# for readthedocs # Remaining requirements are picked up from setup.py Django == 2.2.3 django-extra-views == 0.12.0 sphinxcontrib-django == 0.4 django-polymorphic-2.1.2/docs/_ext/djangodummy/settings.py000066400000000000000000000006071351315731100237430ustar00rootroot00000000000000# Settings file to allow parsing API documentation of Django modules, # and provide defaults to use in the documentation. # # This file is placed in a subdirectory, # so the docs root won't be detected by find_packages() # Display sane URLs in the docs: STATIC_URL = "/static/" # Avoid error for missing the secret key SECRET_KEY = "docs" INSTALLED_APPS = ["django.contrib.contenttypes"] django-polymorphic-2.1.2/docs/admin.rst000066400000000000000000000235401351315731100200770ustar00rootroot00000000000000Django admin integration ======================== Of 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. Setup ----- Both the parent model and child model need to have a ``ModelAdmin`` class. The shared base model should use the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` as base class. * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable of Model classes. The admin class for every child model should inherit from :class:`~polymorphic.admin.PolymorphicChildModelAdmin` * :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_model` should be set. Although the child models are registered too, they won't be shown in the admin index page. This only happens when :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set to ``True``. Fieldset configuration ~~~~~~~~~~~~~~~~~~~~~~ The parent admin is only used for the list display of models, and for the edit/delete view of non-subclassed models. All other model types are redirected to the edit/delete/history view of the child model admin. Hence, the fieldset configuration should be placed on the child admin. .. tip:: When the child admin is used as base class for various derived classes, avoid using the standard ``ModelAdmin`` attributes ``form`` and ``fieldsets``. Instead, use the ``base_form`` and ``base_fieldsets`` attributes. This allows the :class:`~polymorphic.admin.PolymorphicChildModelAdmin` class to detect any additional fields in case the child model is overwritten. .. versionchanged:: 1.0 It's now needed to register the child model classes too. In *django-polymorphic* 0.9 and below, the ``child_models`` was a tuple of a ``(Model, ChildModelAdmin)``. The admin classes were registered in an internal class, and kept away from the main admin site. This caused various subtle problems with the ``ManyToManyField`` and related field wrappers, which are fixed by registering the child admin classes too. Note that they are hidden from the main view, unless :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` is set. .. _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, PolymorphicChildModelFilter from .models import ModelA, ModelB, ModelC, StandardModel class ModelAChildAdmin(PolymorphicChildModelAdmin): """ Base admin class for all child models """ base_model = ModelA # Optional, explicitly set here. # 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 = ( ... ) @admin.register(ModelB) class ModelBAdmin(ModelAChildAdmin): base_model = ModelB # Explicitly set here! # define custom features here @admin.register(ModelC) class ModelCAdmin(ModelBAdmin): base_model = ModelC # Explicitly set here! show_in_index = True # makes child model admin visible in main admin site # define custom features here @admin.register(ModelA) class ModelAParentAdmin(PolymorphicParentModelAdmin): """ The parent model admin """ base_model = ModelA # Optional, explicitly set here. child_models = (ModelB, ModelC) list_filter = (PolymorphicChildModelFilter,) # This is optional. Filtering child types --------------------- Child model types can be filtered by adding a :class:`~polymorphic.admin.PolymorphicChildModelFilter` to the ``list_filter`` attribute. See the example above. Inline models ------------- .. versionadded:: 1.0 Inline models are handled via a special :class:`~polymorphic.admin.StackedPolymorphicInline` class. For models with a generic foreign key, there is a :class:`~polymorphic.admin.GenericStackedPolymorphicInline` class available. When the inline is included to a normal :class:`~django.contrib.admin.ModelAdmin`, make sure the :class:`~polymorphic.admin.PolymorphicInlineSupportMixin` is included. This is not needed when the admin inherits from the :class:`~polymorphic.admin.PolymorphicParentModelAdmin` / :class:`~polymorphic.admin.PolymorphicChildModelAdmin` classes. In the following example, the ``PaymentInline`` supports several types. These are defined as separate inline classes. The child classes can be nested for clarity, but this is not a requirement. .. code-block:: python from django.contrib import admin from polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline from .models import Order, Payment, CreditCardPayment, BankPayment, SepaPayment class PaymentInline(StackedPolymorphicInline): """ An inline for a polymorphic model. The actual form appearance of each row is determined by the child inline that corresponds with the actual model type. """ class CreditCardPaymentInline(StackedPolymorphicInline.Child): model = CreditCardPayment class BankPaymentInline(StackedPolymorphicInline.Child): model = BankPayment class SepaPaymentInline(StackedPolymorphicInline.Child): model = SepaPayment model = Payment child_inlines = ( CreditCardPaymentInline, BankPaymentInline, SepaPaymentInline, ) @admin.register(Order) class OrderAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin): """ Admin for orders. The inline is polymorphic. To make sure the inlines are properly handled, the ``PolymorphicInlineSupportMixin`` is needed to """ inlines = (PaymentInline,) Using polymorphic models in standard 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". .. code-block:: python from django.contrib import admin from .models import StandardModel class ModelBInline(admin.StackedInline): model = ModelB fk_name = 'modelb' readonly_fields = ['modela_ptr'] @admin.register(StandardModel) class StandardModelAdmin(admin.ModelAdmin): inlines = [ModelBInline] Internal details ---------------- 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 redirects the *edit* and *delete* views to the ``ModelAdmin`` of the derived child model. The *list* page is still implemented by the parent model admin. The parent model ~~~~~~~~~~~~~~~~ The parent model needs to inherit :class:`~polymorphic.admin.PolymorphicParentModelAdmin`, and implement the following: * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.base_model` should be set * :attr:`~polymorphic.admin.PolymorphicParentModelAdmin.child_models` or :meth:`~polymorphic.admin.PolymorphicParentModelAdmin.get_child_models` should return an iterable of Model classes. 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. If you use other applications such as django-reversion_ or django-mptt_, please check +:ref:`third-party`. Note: If you are using non-integer primary keys in your model, you have to edit ``pk_regex``, for example ``pk_regex = '([\w-]+)'`` if you use UUIDs. Otherwise you cannot change model entries. The child models ~~~~~~~~~~~~~~~~ The admin interface of the derived models should inherit from :class:`~polymorphic.admin.PolymorphicChildModelAdmin`. Again, :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.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 :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_form` so the derived class will automatically include other fields in the form. * It allows to set :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.base_fieldsets` so the derived class will automatically display any extra fields. * Although it must be registered with admin site, by default it's hidden from admin site index page. This can be overriden by adding :attr:`~polymorphic.admin.PolymorphicChildModelAdmin.show_in_index` = ``True`` in admin class. .. _django-reversion: https://github.com/etianen/django-reversion .. _django-mptt: https://github.com/django-mptt/django-mptt django-polymorphic-2.1.2/docs/advanced.rst000066400000000000000000000271141351315731100205550ustar00rootroot00000000000000.. _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() [ , , ] Copying Polymorphic objects --------------------------- When creating a copy of a polymorphic object, both the ``.id`` and the ``.pk`` of the object need to be set to ``None`` before saving so that both the base table and the derived table will be updated to the new object:: >>> o = ModelB.objects.first() >>> o.field1 = 'new val' # leave field2 unchanged >>> o.pk = None >>> o.id = None >>> o.save() 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()`` work as expected. On Django 1.5+ they support the ``ModelX___field`` syntax, but on Django 1.4 it is only possible to pass fields on the base model into these methods. 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.models import PolymorphicModel from polymorphic.showfields import 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()``, and ``select_related()`` 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-2.1.2/docs/api/000077500000000000000000000000001351315731100170225ustar00rootroot00000000000000django-polymorphic-2.1.2/docs/api/index.rst000066400000000000000000000004021351315731100206570ustar00rootroot00000000000000API Documentation ================= .. toctree:: polymorphic.admin polymorphic.contrib.extra_views polymorphic.contrib.guardian polymorphic.formsets polymorphic.managers polymorphic.models polymorphic.templatetags polymorphic.utils django-polymorphic-2.1.2/docs/api/polymorphic.admin.rst000066400000000000000000000036401351315731100232130ustar00rootroot00000000000000polymorphic.admin ================= ModelAdmin classes ------------------ The ``PolymorphicParentModelAdmin`` class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: polymorphic.admin.PolymorphicParentModelAdmin :members: :undoc-members: :show-inheritance: The ``PolymorphicChildModelAdmin`` class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: polymorphic.admin.PolymorphicChildModelAdmin :members: :undoc-members: :show-inheritance: List filtering -------------- The ``PolymorphicChildModelFilter`` class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: polymorphic.admin.PolymorphicChildModelFilter :show-inheritance: Inlines support --------------- The ``StackedPolymorphicInline`` class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: polymorphic.admin.StackedPolymorphicInline :show-inheritance: The ``GenericStackedPolymorphicInline`` class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: polymorphic.admin.GenericStackedPolymorphicInline :members: :undoc-members: :show-inheritance: The ``PolymorphicInlineSupportMixin`` class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: polymorphic.admin.PolymorphicInlineSupportMixin :members: :undoc-members: :show-inheritance: Low-level classes ----------------- These classes are useful when existing parts of the admin classes. .. autoclass:: polymorphic.admin.PolymorphicModelChoiceForm :members: :undoc-members: :show-inheritance: .. autoclass:: polymorphic.admin.PolymorphicInlineModelAdmin :members: :undoc-members: :show-inheritance: .. autoclass:: polymorphic.admin.GenericPolymorphicInlineModelAdmin :members: :undoc-members: :show-inheritance: .. autoclass:: polymorphic.admin.PolymorphicInlineAdminForm :show-inheritance: .. autoclass:: polymorphic.admin.PolymorphicInlineAdminFormSet :show-inheritance: django-polymorphic-2.1.2/docs/api/polymorphic.contrib.extra_views.rst000066400000000000000000000002521351315731100261160ustar00rootroot00000000000000polymorphic.contrib.extra_views =============================== .. automodule:: polymorphic.contrib.extra_views :members: :undoc-members: :show-inheritance: django-polymorphic-2.1.2/docs/api/polymorphic.contrib.guardian.rst000066400000000000000000000002411351315731100253460ustar00rootroot00000000000000polymorphic.contrib.guardian ============================ .. automodule:: polymorphic.contrib.guardian :members: :undoc-members: :show-inheritance: django-polymorphic-2.1.2/docs/api/polymorphic.formsets.rst000066400000000000000000000016531351315731100237670ustar00rootroot00000000000000polymorphic.formsets ==================== .. automodule:: polymorphic.formsets Model formsets -------------- .. autofunction:: polymorphic.formsets.polymorphic_modelformset_factory .. autoclass:: polymorphic.formsets.PolymorphicFormSetChild Inline formsets --------------- .. autofunction:: polymorphic.formsets.polymorphic_inlineformset_factory Generic formsets ---------------- .. autofunction:: polymorphic.formsets.generic_polymorphic_inlineformset_factory Low-level features ------------------ The internal machinery can be used to extend the formset classes. This includes: .. autofunction:: polymorphic.formsets.polymorphic_child_forms_factory .. autoclass:: polymorphic.formsets.BasePolymorphicModelFormSet :show-inheritance: .. autoclass:: polymorphic.formsets.BasePolymorphicInlineFormSet :show-inheritance: .. autoclass:: polymorphic.formsets.BaseGenericPolymorphicInlineFormSet :show-inheritance: django-polymorphic-2.1.2/docs/api/polymorphic.managers.rst000066400000000000000000000006261351315731100237210ustar00rootroot00000000000000polymorphic.managers ==================== .. automodule:: polymorphic.managers The ``PolymorphicManager`` class -------------------------------- .. autoclass:: polymorphic.managers.PolymorphicManager :members: :show-inheritance: The ``PolymorphicQuerySet`` class --------------------------------- .. autoclass:: polymorphic.managers.PolymorphicQuerySet :members: :show-inheritance: django-polymorphic-2.1.2/docs/api/polymorphic.models.rst000066400000000000000000000002431351315731100234020ustar00rootroot00000000000000polymorphic.models ================== .. automodule:: polymorphic.models .. autoclass:: polymorphic.models.PolymorphicModel :members: :show-inheritance: django-polymorphic-2.1.2/docs/api/polymorphic.templatetags.rst000066400000000000000000000002121351315731100246050ustar00rootroot00000000000000polymorphic.templatetags.polymorphic_admin_tags =============================================== .. automodule:: polymorphic.templatetags django-polymorphic-2.1.2/docs/api/polymorphic.utils.rst000066400000000000000000000001251351315731100232560ustar00rootroot00000000000000polymorphic.utils ================= .. automodule:: polymorphic.utils :members: django-polymorphic-2.1.2/docs/changelog.rst000066400000000000000000000403141351315731100207340ustar00rootroot00000000000000Changelog ========= Changes in 2.1.2 (2019-17-15) ----------------------------- * Fix ``PolymorphicInlineModelAdmin`` media jQuery include for Django 2.0+ Changes in 2.1.1 (2019-07-15) ----------------------------- * Fixed admin import error due to ``isort`` changes. Changes in 2.1 (2019-07-15) --------------------------- * Added Django 2.2 support. * Changed ``.non_polymorphic()``, to use a different iterable class that completely cirvumvent polymorphic. * Changed SQL for ``instance_of`` filter: use ``IN`` statement instead of ``OR`` clauses. * Changed queryset iteration to implement ``prefetch_related()`` support. * Fixed Django 3.0 alpha compatibility. * Fixed compatibility with current django-extra-views_ in ``polymorphic.contrib.extra_views``. * Fixed ``prefetch_related()`` support on polymorphic M2M relations. * Fixed model subclass ``___`` selector for abstract/proxy models. * Fixed model subclass ``___`` selector for models with a custom ``OneToOneField(parent_link=True)``. * Fixed unwanted results on calling ``queryset.get_real_instances([])``. * Fixed unwanted ``TypeError`` exception when ``PolymorphicTypeInvalid`` should have raised. * Fixed hiding the add-button of polymorphic lines in the Django admin. * Reformatted all files with black Changes in 2.0.3 (2018-08-24) ----------------------------- * Fixed admin crash for Django 2.1 with missing ``use_required_attribute``. Changes in 2.0.2 (2018-02-05) ----------------------------- * Fixed manager inheritance behavior for Django 1.11, by automatically enabling ``Meta.manager_inheritance_from_future`` if it's not defined. This restores the manager inheritance behavior that *django-polymorphic 1.3* provided for Django 1.x projects. * Fixed internal ``base_objects`` usage. Changes in 2.0.1 (2018-02-05) ----------------------------- * Fixed manager inheritance detection for Django 1.11. It's recommended to use ``Meta.manager_inheritance_from_future`` so Django 1.x code also inherit the ``PolymorphicManager`` in all subclasses. Django 2.0 already does this by default. * Deprecated the ``base_objects`` manager. Use ``objects.non_polymorphic()`` instead. * Optimized detection for dumpdata behavior, avoiding the performance hit of ``__getattribute__()``. * Fixed test management commands Changes in 2.0 (2018-01-22) --------------------------- * **BACKWARDS INCOMPATIBILITY:** Dropped Django 1.8 and 1.10 support. * **BACKWARDS INCOMPATIBILITY:** Removed old deprecated code from 1.0, thus: * Import managers from ``polymorphic.managers`` (plural), not ``polymorphic.manager``. * Register child models to the admin as well using ``@admin.register()`` or ``admin.site.register()``, as this is no longer done automatically. * Added Django 2.0 support. Also backported into 1.3.1: * Added ``PolymorphicTypeUndefined`` exception for incomplete imported models. When a data migration or import creates an polymorphic model, the ``polymorphic_ctype_id`` field should be filled in manually too. The ``polymorphic.utils.reset_polymorphic_ctype`` function can be used for that. * Added ``PolymorphicTypeInvalid`` exception when database was incorrectly imported. * Added ``polymorphic.utils.get_base_polymorphic_model()`` to find the base model for types. * Using ``base_model`` on the polymorphic admins is no longer required, as this can be autodetected. * Fixed manager errors for swappable models. * Fixed ``deleteText`` of ``|as_script_options`` template filter. * Fixed ``.filter(applabel__ModelName___field=...)`` lookups. * Fixed proxy model support in formsets. * Fixed error with .defer and child models that use the same parent. * Fixed error message when ``polymorphic_ctype_id`` is null. * Fixed fieldsets recursion in the admin. * Improved ``polymorphic.utils.reset_polymorphic_ctype()`` to accept models in random ordering. * Fix fieldsets handling in the admin (``declared_fieldsets`` is removed since Django 1.9) Version 1.3.1 (2018-04-16) -------------------------- Backported various fixes from 2.x to support older Django versions: * Added ``PolymorphicTypeUndefined`` exception for incomplete imported models. When a data migration or import creates an polymorphic model, the ``polymorphic_ctype_id`` field should be filled in manually too. The ``polymorphic.utils.reset_polymorphic_ctype`` function can be used for that. * Added ``PolymorphicTypeInvalid`` exception when database was incorrectly imported. * Added ``polymorphic.utils.get_base_polymorphic_model()`` to find the base model for types. * Using ``base_model`` on the polymorphic admins is no longer required, as this can be autodetected. * Fixed manager errors for swappable models. * Fixed ``deleteText`` of ``|as_script_options`` template filter. * Fixed ``.filter(applabel__ModelName___field=...)`` lookups. * Fixed proxy model support in formsets. * Fixed error with .defer and child models that use the same parent. * Fixed error message when ``polymorphic_ctype_id`` is null. * Fixed fieldsets recursion in the admin. * Improved ``polymorphic.utils.reset_polymorphic_ctype()`` to accept models in random ordering. * Fix fieldsets handling in the admin (``declared_fieldsets`` is removed since Django 1.9) Version 1.3 (2017-08-01) ------------------------ * **BACKWARDS INCOMPATIBILITY:** Dropped Django 1.4, 1.5, 1.6, 1.7, 1.9 and Python 2.6 support. Only official Django releases (1.8, 1.10, 1.11) are supported now. * Allow expressions to pass unchanged in ``.order_by()`` * Fixed Django 1.11 accessor checks (to support subclasses of ``ForwardManyToOneDescriptor``, like ``ForwardOneToOneDescriptor``) * Fixed polib syntax error messages in translations. Version 1.2 (2017-05-01) ------------------------ * Django 1.11 support. * Fixed ``PolymorphicInlineModelAdmin`` to explictly exclude ``polymorphic_ctype``. * Fixed Python 3 TypeError in the admin when preserving the query string. * Fixed Python 3 issue due to ``force_unicode()`` usage instead of ``force_text()``. * Fixed ``z-index`` attribute for admin menu appearance. Version 1.1 (2017-02-03) ------------------------ * Added class based formset views in ``polymorphic/contrib/extra_views``. * Added helper function ``polymorphic.utils.reset_polymorphic_ctype()``. This eases the migration old existing models to polymorphic. * Fixed Python 2.6 issue. * Fixed Django 1.6 support. Version 1.0.2 (2016-10-14) -------------------------- * Added helper function for django-guardian_; add ``GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'`` to the project settings to let guardian handles inherited models properly. * Fixed ``polymorphic_modelformset_factory()`` usage. * Fixed Python 3 bug for inline formsets. * Fixed CSS for Grappelli, so model choice menu properly overlaps. * Fixed ``ParentAdminNotRegistered`` exception for models that are registered via a proxy model instead of the real base model. Version 1.0.1 (2016-09-11) -------------------------- * Fixed compatibility with manager changes in Django 1.10.1 Version 1.0 (2016-09-02) ------------------------ * Added Django 1.10 support. * Added **admin inline** support for polymorphic models. * Added **formset** support for polymorphic models. * Added support for polymorphic queryset limiting effects on *proxy models*. * Added support for multiple databases with the ``.using()`` method and ``using=..`` keyword argument. * Fixed modifying passed ``Q()`` objects in place. .. note:: This version provides a new method for registering the admin models. While the old method is still supported, we recommend to upgrade your code. The new registration style improves the compatibility in the Django admin. * Register each ``PolymorphicChildModelAdmin`` with the admin site too. * The ``child_models`` attribute of the ``PolymorphicParentModelAdmin`` should be a flat list of all child models. The ``(model, admin)`` tuple is obsolete. Also note that proxy models will now limit the queryset too. Fixed since 1.0b1 (2016-08-10) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Fix formset empty-form display when there are form errors. * Fix formset empty-form hiding for Grappelli_. * Fixed packing ``admin/polymorphic/edit_inline/stacked.html`` in the wheel format. Version 0.9.2 (2016-05-04) -------------------------- * Fix error when using ``date_hierarchy`` field in the admin * Fixed Django 1.10 warning in admin add-type view. Version 0.9.1 (2016-02-18) -------------------------- * Fixed support for ``PolymorphicManager.from_queryset()`` for custom query sets. * Fixed Django 1.7 ``changeform_view()`` redirection to the child admin site. This fixes custom admin code that uses these views, such as django-reversion_'s ``revision_view()`` / ``recover_view()``. * Fixed ``.only('pk')`` field support. * Fixed ``object_history_template`` breadcrumb. **NOTE:** when using django-reversion_ / django-reversion-compare_, make sure to implement a ``admin/polymorphic/object_history.html`` template in your project that extends from ``reversion/object_history.html`` or ``reversion-compare/object_history.html`` respectively. Version 0.9 (2016-02-17) ------------------------ * Added ``.only()`` and ``.defer()`` support. * Added support for Django 1.8 complex expressions in ``.annotate()`` / ``.aggregate()``. * Fix Django 1.9 handling of custom URLs. The new change-URL redirect overlapped any custom URLs defined in the child admin. * Fix Django 1.9 support in the admin. * Fix setting an extra custom manager without overriding the ``_default_manager``. * Fix missing ``history_view()`` redirection to the child admin, which is important for django-reversion_ support. See the documentation for hints for :ref:`django-reversion-compare support `. 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 from polymorphic.models import PolymorphicModel from polymorphic.managers import PolymorphicManager, PolymorphicQuerySet from 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-extra-views: https://github.com/AndrewIngram/django-extra-views .. _django-guardian: https://github.com/django-guardian/django-guardian .. _django-parler: https://github.com/django-parler/django-parler .. _django-reversion: https://github.com/etianen/django-reversion .. _django-reversion-compare: https://github.com/jedie/django-reversion-compare django-polymorphic-2.1.2/docs/changelog_archive.rst000066400000000000000000000261601351315731100224400ustar00rootroot00000000000000: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-2.1.2/docs/conf.py000066400000000000000000000212141351315731100175500ustar00rootroot00000000000000# -*- 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 os import sys import django import sphinx_rtd_theme # 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" django.setup() # -- 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", "sphinxcontrib_django", ] # 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 = "2.1.2" # The full version, including alpha/beta/rc tags. release = "2.1.2" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # 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/stable": "https://docs.djangoproject.com/en/stable/_objects" } # autodoc settings autodoc_member_order = "groupwise" django-polymorphic-2.1.2/docs/contributing.rst000066400000000000000000000027321351315731100215160ustar00rootroot00000000000000Contributing ============ You can contribute to *django-polymorphic* to forking the code on GitHub: https://github.com/django-polymorphic/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-2.1.2/docs/formsets.rst000066400000000000000000000032331351315731100206460ustar00rootroot00000000000000Formsets ======== .. versionadded:: 1.0 Polymorphic models can be used in formsets. The implementation is almost identical to the regular Django formsets. As extra parameter, the factory needs to know how to display the child models. Provide a list of :class:`~polymorphic.formsets.PolymorphicFormSetChild` objects for this. .. code-block:: python from polymorphic.formsets import polymorphic_modelformset_factory, PolymorphicFormSetChild ModelAFormSet = polymorphic_modelformset_factory(ModelA, formset_children=( PolymorphicFormSetChild(ModelB), PolymorphicFormSetChild(ModelC), )) The formset can be used just like all other formsets: .. code-block:: python if request.method == "POST": formset = ModelAFormSet(request.POST, request.FILES, queryset=ModelA.objects.all()) if formset.is_valid(): formset.save() else: formset = ModelAFormSet(queryset=ModelA.objects.all()) Like standard Django formsets, there are 3 factory methods available: * :func:`~polymorphic.formsets.polymorphic_modelformset_factory` - create a regular model formset. * :func:`~polymorphic.formsets.polymorphic_inlineformset_factory` - create a inline model formset. * :func:`~polymorphic.formsets.generic_polymorphic_inlineformset_factory` - create an inline formset for a generic foreign key. Each one uses a different base class: * :class:`~polymorphic.formsets.BasePolymorphicModelFormSet` * :class:`~polymorphic.formsets.BasePolymorphicInlineFormSet` * :class:`~polymorphic.formsets.BaseGenericPolymorphicInlineFormSet` When needed, the base class can be overwritten and provided to the factory via the ``formset`` parameter. django-polymorphic-2.1.2/docs/index.rst000066400000000000000000000042171351315731100201160ustar00rootroot00000000000000Welcome to django-polymorphic's documentation! ============================================== Django-polymorphic builds on top of the standard Django model inheritance. It makes using inherited models easier. 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... .. code-block:: python >>> 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! .. code-block:: python >>> Project.objects.all() [ , , ] Using vanilla Django, we get the base class objects, which is rarely what we wanted: .. code-block:: python >>> 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. * Formset support. * 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 third-party Advanced topics --------------- .. toctree:: :maxdepth: 2 formsets migrating managers advanced changelog contributing api/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` django-polymorphic-2.1.2/docs/make.bat000066400000000000000000000120001351315731100176470ustar00rootroot00000000000000@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-2.1.2/docs/managers.rst000066400000000000000000000075461351315731100206140ustar00rootroot00000000000000Custom 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.managers 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. 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.managers 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. 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.managers import PolymorphicManager from polymorphic.query import PolymorphicQuerySet class MyQuerySet(PolymorphicQuerySet): def my_queryset_method(self): ... class MyModel(PolymorphicModel): my_objects = PolymorphicManager.from_queryset(MyQuerySet)() ... django-polymorphic-2.1.2/docs/migrating.rst000066400000000000000000000050521351315731100207660ustar00rootroot00000000000000Migrating existing models to polymorphic ======================================== Existing models can be migrated to become polymorphic models. During the migrating, the ``polymorphic_ctype`` field needs to be filled in. This can be done in the following steps: #. Inherit your model from :class:`~polymorphic.models.PolymorphicModel`. #. Create a Django migration file to create the ``polymorphic_ctype_id`` database column. #. Make sure the proper :class:`~django.contrib.contenttypes.models.ContentType` value is filled in. Filling the content type value ------------------------------ The following Python code can be used to fill the value of a model: .. code-block:: python from django.contrib.contenttypes.models import ContentType from myapp.models import MyModel new_ct = ContentType.objects.get_for_model(MyModel) MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct) The creation and update of the ``polymorphic_ctype_id`` column can be included in a single Django migration. For example: .. code-block:: python # -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models def forwards_func(apps, schema_editor): MyModel = apps.get_model('myapp', 'MyModel') ContentType = apps.get_model('contenttypes', 'ContentType') new_ct = ContentType.objects.get_for_model(MyModel) MyModel.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct) class Migration(migrations.Migration): dependencies = [ ('contenttypes', '0001_initial'), ('myapp', '0001_initial'), ] operations = [ migrations.AddField( model_name='mymodel', name='polymorphic_ctype', field=models.ForeignKey(related_name='polymorphic_myapp.mymodel_set+', editable=False, to='contenttypes.ContentType', null=True), ), migrations.RunPython(forwards_func, migrations.RunPython.noop), ] It's recommended to let ``makemigrations`` create the migration file, and include the ``RunPython`` manually before running the migration. .. versionadded:: 1.1 When the model is created elsewhere, you can also use the :func:`polymorphic.utils.reset_polymorphic_ctype` function: .. code-block:: python from polymorphic.utils import reset_polymorphic_ctype from myapp.models import Base, Sub1, Sub2 reset_polymorphic_ctype(Base, Sub1, Sub2) reset_polymorphic_ctype(Base, Sub1, Sub2, ignore_existing=True) django-polymorphic-2.1.2/docs/performance.rst000066400000000000000000000046271351315731100213150ustar00rootroot00000000000000.. _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-2.1.2/docs/quickstart.rst000066400000000000000000000062631351315731100212040ustar00rootroot00000000000000Quickstart =========== 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.11, 2.0 and Python 2.7 and 3.5+ is supported. For older Django versions, use *django-polymorphic==1.3*. 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-2.1.2/docs/third-party.rst000066400000000000000000000146761351315731100212700ustar00rootroot00000000000000Third-party applications support ================================ django-guardian support ----------------------- .. versionadded:: 1.0.2 You can configure django-guardian_ to use the base model for object level permissions. Add this option to your settings: .. code-block:: python GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type' This option requires django-guardian_ >= 1.4.6. Details about how this option works are available in the `django-guardian documentation `_. django-rest-framework support ----------------------------- The django-rest-polymorphic_ package provides polymorphic serializers that help you integrate your polymorphic models with `django-rest-framework`. Example ~~~~~~~ Define serializers: .. code-block:: python from rest_framework import serializers from rest_polymorphic.serializers import PolymorphicSerializer from .models import Project, ArtProject, ResearchProject class ProjectSerializer(serializers.ModelSerializer): class Meta: model = Project fields = ('topic', ) class ArtProjectSerializer(serializers.ModelSerializer): class Meta: model = ArtProject fields = ('topic', 'artist') class ResearchProjectSerializer(serializers.ModelSerializer): class Meta: model = ResearchProject fields = ('topic', 'supervisor') class ProjectPolymorphicSerializer(PolymorphicSerializer): model_serializer_mapping = { Project: ProjectSerializer, ArtProject: ArtProjectSerializer, ResearchProject: ResearchProjectSerializer } Create viewset with serializer_class equals to your polymorphic serializer: .. code-block:: python from rest_framework import viewsets from .models import Project from .serializers import ProjectPolymorphicSerializer class ProjectViewSet(viewsets.ModelViewSet): queryset = Project.objects.all() serializer_class = ProjectPolymorphicSerializer django-extra-views ------------------ .. versionadded:: 1.1 The :mod:`polymorphic.contrib.extra_views` package provides classes to display polymorphic formsets using the classes from django-extra-views_. See the documentation of: * :class:`~polymorphic.contrib.extra_views.PolymorphicFormSetView` * :class:`~polymorphic.contrib.extra_views.PolymorphicInlineFormSetView` * :class:`~polymorphic.contrib.extra_views.PolymorphicInlineFormSet` django-mptt support ------------------- Combining polymorphic with django-mptt_ is certainly possible, but not straightforward. It involves combining both managers, querysets, models, meta-classes and admin classes using multiple inheritance. The django-polymorphic-tree_ package provides this out of the box. django-reversion support ------------------------ Support for django-reversion_ works as expected with polymorphic models. However, they require more setup than standard models. That's become: * Manually register the child models with django-reversion_, so their ``follow`` parameter can be set. * Polymorphic models use `multi-table inheritance `_. See the `reversion documentation `_ how to deal with this by adding a ``follow`` field for the primary key. * Both admin classes redefine ``object_history_template``. Example ~~~~~~~ The admin :ref:`admin example ` becomes: .. code-block:: python from django.contrib import admin from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin from reversion.admin import VersionAdmin from reversion import revisions from .models import ModelA, ModelB, ModelC class ModelAChildAdmin(PolymorphicChildModelAdmin, VersionAdmin): base_model = ModelA # optional, explicitly set here. base_form = ... base_fieldsets = ( ... ) class ModelBAdmin(ModelAChildAdmin, VersionAdmin): # define custom features here class ModelCAdmin(ModelBAdmin): # define custom features here class ModelAParentAdmin(VersionAdmin, PolymorphicParentModelAdmin): base_model = ModelA # optional, explicitly set here. child_models = ( (ModelB, ModelBAdmin), (ModelC, ModelCAdmin), ) revisions.register(ModelB, follow=['modela_ptr']) revisions.register(ModelC, follow=['modelb_ptr']) admin.site.register(ModelA, ModelAParentAdmin) Redefine a :file:`admin/polymorphic/object_history.html` template, so it combines both worlds: .. code-block:: html+django {% extends 'reversion/object_history.html' %} {% load polymorphic_admin_tags %} {% block breadcrumbs %} {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} This makes sure both the reversion template is used, and the breadcrumb is corrected for the polymorphic model. .. _django-reversion-compare-support: django-reversion-compare support -------------------------------- The django-reversion-compare_ views work as expected, the admin requires a little tweak. In your parent admin, include the following method: .. code-block:: python def compare_view(self, request, object_id, extra_context=None): """Redirect the reversion-compare view to the child admin.""" real_admin = self._get_real_admin(object_id) return real_admin.compare_view(request, object_id, extra_context=extra_context) As the compare view resolves the the parent admin, it uses it's base model to find revisions. This doesn't work, since it needs to look for revisions of the child model. Using this tweak, the view of the actual child model is used, similar to the way the regular change and delete views are redirected. .. _django-extra-views: https://github.com/AndrewIngram/django-extra-views .. _django-guardian: https://github.com/django-guardian/django-guardian .. _django-mptt: https://github.com/django-mptt/django-mptt .. _django-polymorphic-tree: https://github.com/django-polymorphic/django-polymorphic-tree .. _django-rest-polymorphic: https://github.com/apirobot/django-rest-polymorphic .. _django-reversion-compare: https://github.com/jedie/django-reversion-compare .. _django-reversion: https://github.com/etianen/django-reversion django-polymorphic-2.1.2/example/000077500000000000000000000000001351315731100167545ustar00rootroot00000000000000django-polymorphic-2.1.2/example/example/000077500000000000000000000000001351315731100204075ustar00rootroot00000000000000django-polymorphic-2.1.2/example/example/__init__.py000066400000000000000000000000001351315731100225060ustar00rootroot00000000000000django-polymorphic-2.1.2/example/example/settings.py000066400000000000000000000056511351315731100226300ustar00rootroot00000000000000import os DEBUG = True 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", ) MIDDLEWARE = ( "django.middleware.common.CommonMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ) TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": (), "OPTIONS": { "loaders": ( "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ), "context_processors": ( "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.request", "django.template.context_processors.static", "django.contrib.messages.context_processors.messages", "django.contrib.auth.context_processors.auth", ), }, } ] ROOT_URLCONF = "example.urls" WSGI_APPLICATION = "example.wsgi.application" INSTALLED_APPS = ( "django.contrib.auth", "django.contrib.admin", "django.contrib.contenttypes", "django.contrib.sessions", "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 "orders", ) 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-2.1.2/example/example/urls.py000066400000000000000000000005051351315731100217460ustar00rootroot00000000000000from django.conf.urls import include, url from django.contrib import admin from django.urls import reverse_lazy from django.views.generic import RedirectView admin.autodiscover() urlpatterns = [ url(r"^admin/", admin.site.urls), url(r"^$", RedirectView.as_view(url=reverse_lazy("admin:index"), permanent=False)), ] django-polymorphic-2.1.2/example/example/wsgi.py000066400000000000000000000021621351315731100217330ustar00rootroot00000000000000""" 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 # 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 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) django-polymorphic-2.1.2/example/manage.py000077500000000000000000000006231351315731100205620ustar00rootroot00000000000000#!/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-2.1.2/example/orders/000077500000000000000000000000001351315731100202525ustar00rootroot00000000000000django-polymorphic-2.1.2/example/orders/__init__.py000066400000000000000000000000001351315731100223510ustar00rootroot00000000000000django-polymorphic-2.1.2/example/orders/admin.py000066400000000000000000000021101351315731100217060ustar00rootroot00000000000000from django.contrib import admin from polymorphic.admin import PolymorphicInlineSupportMixin, StackedPolymorphicInline from .models import BankPayment, CreditCardPayment, Order, Payment, SepaPayment class CreditCardPaymentInline(StackedPolymorphicInline.Child): model = CreditCardPayment class BankPaymentInline(StackedPolymorphicInline.Child): model = BankPayment class SepaPaymentInline(StackedPolymorphicInline.Child): model = SepaPayment class PaymentInline(StackedPolymorphicInline): """ An inline for a polymorphic model. The actual form appearance of each row is determined by the child inline that corresponds with the actual model type. """ model = Payment child_inlines = (CreditCardPaymentInline, BankPaymentInline, SepaPaymentInline) @admin.register(Order) class OrderAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin): """ Admin for orders. The inline is polymorphic. To make sure the inlines are properly handled, the ``PolymorphicInlineSupportMixin`` is needed to """ inlines = (PaymentInline,) django-polymorphic-2.1.2/example/orders/migrations/000077500000000000000000000000001351315731100224265ustar00rootroot00000000000000django-polymorphic-2.1.2/example/orders/migrations/0001_initial.py000066400000000000000000000122211351315731100250670ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models class Migration(migrations.Migration): dependencies = [("contenttypes", "0002_remove_content_type_name")] operations = [ migrations.CreateModel( name="Order", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ("title", models.CharField(max_length=200, verbose_name="Title")), ], options={ "ordering": ("title",), "verbose_name": "Organisation", "verbose_name_plural": "Organisations", }, ), migrations.CreateModel( name="Payment", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ("currency", models.CharField(default=b"USD", max_length=3)), ("amount", models.DecimalField(max_digits=10, decimal_places=2)), ], options={"verbose_name": "Payment", "verbose_name_plural": "Payments"}, ), migrations.CreateModel( name="BankPayment", fields=[ ( "payment_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="orders.Payment", ), ), ("bank_name", models.CharField(max_length=100)), ("swift", models.CharField(max_length=20)), ], options={ "verbose_name": "Bank Payment", "verbose_name_plural": "Bank Payments", }, bases=("orders.payment",), ), migrations.CreateModel( name="CreditCardPayment", fields=[ ( "payment_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="orders.Payment", ), ), ("card_type", models.CharField(max_length=10)), ( "expiry_month", models.PositiveSmallIntegerField( choices=[ (1, "jan"), (2, "feb"), (3, "mar"), (4, "apr"), (5, "may"), (6, "jun"), (7, "jul"), (8, "aug"), (9, "sep"), (10, "oct"), (11, "nov"), (12, "dec"), ] ), ), ("expiry_year", models.PositiveIntegerField()), ], options={ "verbose_name": "Credit Card Payment", "verbose_name_plural": "Credit Card Payments", }, bases=("orders.payment",), ), migrations.CreateModel( name="SepaPayment", fields=[ ( "payment_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="orders.Payment", ), ), ("iban", models.CharField(max_length=34)), ("bic", models.CharField(max_length=11)), ], options={ "verbose_name": "Bank Payment", "verbose_name_plural": "Bank Payments", }, bases=("orders.payment",), ), migrations.AddField( model_name="payment", name="order", field=models.ForeignKey(to="orders.Order", on_delete=models.CASCADE), ), migrations.AddField( model_name="payment", name="polymorphic_ctype", field=models.ForeignKey( related_name="polymorphic_orders.payment_set+", editable=False, on_delete=models.CASCADE, to="contenttypes.ContentType", null=True, ), ), ] django-polymorphic-2.1.2/example/orders/migrations/__init__.py000066400000000000000000000000001351315731100245250ustar00rootroot00000000000000django-polymorphic-2.1.2/example/orders/models.py000066400000000000000000000037721351315731100221200ustar00rootroot00000000000000from django.db import models from django.utils.dates import MONTHS_3 from django.utils.six import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from polymorphic.models import PolymorphicModel @python_2_unicode_compatible class Order(models.Model): """ An example order that has polymorphic relations """ title = models.CharField(_("Title"), max_length=200) class Meta: verbose_name = _("Organisation") verbose_name_plural = _("Organisations") ordering = ("title",) def __str__(self): return self.title @python_2_unicode_compatible class Payment(PolymorphicModel): """ A generic payment model. """ order = models.ForeignKey(Order, on_delete=models.CASCADE) currency = models.CharField(default="USD", max_length=3) amount = models.DecimalField(max_digits=10, decimal_places=2) class Meta: verbose_name = _("Payment") verbose_name_plural = _("Payments") def __str__(self): return "{0} {1}".format(self.currency, self.amount) class CreditCardPayment(Payment): """ Credit card """ MONTH_CHOICES = [(i, n) for i, n in sorted(MONTHS_3.items())] card_type = models.CharField(max_length=10) expiry_month = models.PositiveSmallIntegerField(choices=MONTH_CHOICES) expiry_year = models.PositiveIntegerField() class Meta: verbose_name = _("Credit Card Payment") verbose_name_plural = _("Credit Card Payments") class BankPayment(Payment): """ Payment by bank """ bank_name = models.CharField(max_length=100) swift = models.CharField(max_length=20) class Meta: verbose_name = _("Bank Payment") verbose_name_plural = _("Bank Payments") class SepaPayment(Payment): """ Payment by SEPA (EU) """ iban = models.CharField(max_length=34) bic = models.CharField(max_length=11) class Meta: verbose_name = _("SEPA Payment") verbose_name_plural = _("SEPA Payments") django-polymorphic-2.1.2/example/pexp/000077500000000000000000000000001351315731100177305ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/__init__.py000066400000000000000000000000001351315731100220270ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/admin.py000066400000000000000000000030111351315731100213650ustar00rootroot00000000000000from django.contrib import admin from pexp.models import * from polymorphic.admin import ( PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicParentModelAdmin, ) class ProjectAdmin(PolymorphicParentModelAdmin): base_model = Project # Can be set explicitly. list_filter = (PolymorphicChildModelFilter,) child_models = (Project, ArtProject, ResearchProject) class ProjectChildAdmin(PolymorphicChildModelAdmin): base_model = Project # Can be set explicitly. # On purpose, only have the shared fields here. # The fields of the derived model should still be displayed. base_fieldsets = (("Base fields", {"fields": ("topic",)}),) admin.site.register(Project, ProjectAdmin) admin.site.register(ArtProject, ProjectChildAdmin) admin.site.register(ResearchProject, ProjectChildAdmin) class UUIDModelAAdmin(PolymorphicParentModelAdmin): list_filter = (PolymorphicChildModelFilter,) child_models = (UUIDModelA, UUIDModelB) class UUIDModelAChildAdmin(PolymorphicChildModelAdmin): pass admin.site.register(UUIDModelA, UUIDModelAAdmin) admin.site.register(UUIDModelB, UUIDModelAChildAdmin) admin.site.register(UUIDModelC, UUIDModelAChildAdmin) class ProxyAdmin(PolymorphicParentModelAdmin): list_filter = (PolymorphicChildModelFilter,) child_models = (ProxyA, ProxyB) class ProxyChildAdmin(PolymorphicChildModelAdmin): pass admin.site.register(ProxyBase, ProxyAdmin) admin.site.register(ProxyA, ProxyChildAdmin) admin.site.register(ProxyB, ProxyChildAdmin) django-polymorphic-2.1.2/example/pexp/dumpdata_test_correct_output.txt000066400000000000000000000014431351315731100264720ustar00rootroot00000000000000[ { "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-2.1.2/example/pexp/management/000077500000000000000000000000001351315731100220445ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/management/__init__.py000066400000000000000000000000001351315731100241430ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/management/commands/000077500000000000000000000000001351315731100236455ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/management/commands/__init__.py000066400000000000000000000000001351315731100257440ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/management/commands/p2cmd.py000066400000000000000000000066251351315731100252350ustar00rootroot00000000000000# -*- 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 sys import time from pprint import pprint from random import Random from django.core.management import BaseCommand from django.db import connection from pexp.models import * rnd = Random() 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 = [] connection.queries_log.clear() for i in range(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 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(BaseCommand): help = "" def handle_noargs(self, **options): if False: TestModelA.objects.all().delete() a = TestModelA.objects.create(field1="A1") b = TestModelB.objects.create(field1="B1", field2="B2") c = TestModelC.objects.create(field1="C1", field2="C2", field3="C3") connection.queries_log.clear() print(TestModelC.base_objects.all()) show_queries() if False: TestModelA.objects.all().delete() for i in range(1000): a = TestModelA.objects.create(field1=str(i % 100)) b = TestModelB.objects.create(field1=str(i % 100), field2=str(i % 200)) c = TestModelC.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 NormalModelA.objects.all().delete() a = NormalModelA.objects.create(field1="A1") b = NormalModelB.objects.create(field1="B1", field2="B2") c = NormalModelC.objects.create(field1="C1", field2="C2", field3="C3") qs = TestModelA.objects.raw("SELECT * from pexp_testmodela") for o in list(qs): print(o) def poly_sql_query(): cursor = connection.cursor() cursor.execute( """ SELECT id, pexp_testmodela.field1, pexp_testmodelb.field2, pexp_testmodelc.field3 FROM pexp_testmodela LEFT OUTER JOIN pexp_testmodelb ON pexp_testmodela.id = pexp_testmodelb.testmodela_ptr_id LEFT OUTER JOIN pexp_testmodelc ON pexp_testmodelb.testmodela_ptr_id = pexp_testmodelc.testmodelb_ptr_id WHERE pexp_testmodela.field1=%i ORDER BY pexp_testmodela.id """ % rnd.randint(0, 100) ) # row=cursor.fetchone() return def poly_sql_query2(): cursor = connection.cursor() cursor.execute( """ SELECT id, pexp_testmodela.field1 FROM pexp_testmodela WHERE pexp_testmodela.field1=%i ORDER BY pexp_testmodela.id """ % rnd.randint(0, 100) ) # row=cursor.fetchone() return django-polymorphic-2.1.2/example/pexp/management/commands/pcmd.py000066400000000000000000000017351351315731100251500ustar00rootroot00000000000000""" This module is a scratchpad for general development, testing & debugging. """ from django.core.management.base import NoArgsCommand from django.db import connection from pexp.models import * def reset_queries(): 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("") TestModelA.objects.all().delete() a = TestModelA.objects.create(field1="A1") b = TestModelB.objects.create(field1="B1", field2="B2") c = TestModelC.objects.create(field1="C1", field2="C2", field3="C3") print(TestModelA.objects.all()) print("") django-polymorphic-2.1.2/example/pexp/management/commands/polybench.py000066400000000000000000000053131351315731100262040ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module is a scratchpad for general development, testing & debugging """ import sys import time from pprint import pprint from django.core.management import BaseCommand from django.db import connection from pexp.models import * num_objects = 1000 def show_queries(): print() print("QUERIES:", len(connection.queries)) pprint(connection.queries) print() connection.queries_log.clear() ################################################################################### # benchmark wrappers def print_timing(func, message="", iterations=1): def wrapper(*arg): results = [] connection.queries_log.clear() for i in range(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(NormalModelC) f = print_timing(func, "poly ", iterations) f(TestModelC) ################################################################################### # benchmarks def bench_create(model): for i in range(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 range(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 range(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(BaseCommand): 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) django-polymorphic-2.1.2/example/pexp/management/commands/polymorphic_create_test_data.py000066400000000000000000000011401351315731100321330ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ This module is a scratchpad for general development, testing & debugging """ from django.core.management import BaseCommand from pexp.models import * class Command(BaseCommand): 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()) django-polymorphic-2.1.2/example/pexp/migrations/000077500000000000000000000000001351315731100221045ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/migrations/0001_initial.py000066400000000000000000000234521351315731100245550ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import migrations, models import polymorphic.showfields class Migration(migrations.Migration): dependencies = [("contenttypes", "0002_remove_content_type_name")] operations = [ migrations.CreateModel( name="NormalModelA", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ("field1", models.CharField(max_length=10)), ], ), migrations.CreateModel( name="Project", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ("topic", models.CharField(max_length=30)), ], options={"abstract": False}, bases=(polymorphic.showfields.ShowFieldContent, models.Model), ), migrations.CreateModel( name="ProxyBase", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ("title", models.CharField(max_length=200)), ( "polymorphic_ctype", models.ForeignKey( related_name="polymorphic_pexp.proxybase_set+", editable=False, on_delete=models.CASCADE, to="contenttypes.ContentType", null=True, ), ), ], options={"ordering": ("title",)}, ), migrations.CreateModel( name="TestModelA", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="UUIDModelA", fields=[ ( "uuid_primary_key", models.UUIDField(serialize=False, primary_key=True), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="ArtProject", fields=[ ( "project_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="pexp.Project", ), ), ("artist", models.CharField(max_length=30)), ], options={"abstract": False}, bases=("pexp.project",), ), migrations.CreateModel( name="NormalModelB", fields=[ ( "normalmodela_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="pexp.NormalModelA", ), ), ("field2", models.CharField(max_length=10)), ], bases=("pexp.normalmodela",), ), migrations.CreateModel( name="ResearchProject", fields=[ ( "project_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="pexp.Project", ), ), ("supervisor", models.CharField(max_length=30)), ], options={"abstract": False}, bases=("pexp.project",), ), migrations.CreateModel( name="TestModelB", fields=[ ( "testmodela_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="pexp.TestModelA", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False}, bases=("pexp.testmodela",), ), migrations.CreateModel( name="UUIDModelB", fields=[ ( "uuidmodela_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, 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( related_name="polymorphic_pexp.uuidmodela_set+", editable=False, on_delete=models.CASCADE, to="contenttypes.ContentType", null=True, ), ), migrations.AddField( model_name="testmodela", name="polymorphic_ctype", field=models.ForeignKey( related_name="polymorphic_pexp.testmodela_set+", editable=False, on_delete=models.CASCADE, to="contenttypes.ContentType", null=True, ), ), migrations.AddField( model_name="project", name="polymorphic_ctype", field=models.ForeignKey( related_name="polymorphic_pexp.project_set+", editable=False, on_delete=models.CASCADE, to="contenttypes.ContentType", null=True, ), ), 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="NormalModelC", fields=[ ( "normalmodelb_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="pexp.NormalModelB", ), ), ("field3", models.CharField(max_length=10)), ], bases=("pexp.normalmodelb",), ), migrations.CreateModel( name="TestModelC", fields=[ ( "testmodelb_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="pexp.TestModelB", ), ), ("field3", models.CharField(max_length=10)), ( "field4", models.ManyToManyField( related_name="related_c", to="pexp.TestModelB" ), ), ], options={"abstract": False}, bases=("pexp.testmodelb",), ), migrations.CreateModel( name="UUIDModelC", fields=[ ( "uuidmodelb_ptr", models.OneToOneField( parent_link=True, auto_created=True, primary_key=True, serialize=False, on_delete=models.CASCADE, to="pexp.UUIDModelB", ), ), ("field3", models.CharField(max_length=10)), ], options={"abstract": False}, bases=("pexp.uuidmodelb",), ), ] django-polymorphic-2.1.2/example/pexp/migrations/__init__.py000066400000000000000000000000001351315731100242030ustar00rootroot00000000000000django-polymorphic-2.1.2/example/pexp/models.py000066400000000000000000000040721351315731100215700ustar00rootroot00000000000000# -*- coding: utf-8 -*- from django.db import models from polymorphic.models import PolymorphicModel from polymorphic.showfields import ShowFieldContent, ShowFieldTypeAndContent class Project(ShowFieldContent, PolymorphicModel): """Polymorphic model""" 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 UUIDModelA(ShowFieldTypeAndContent, PolymorphicModel): """UUID as primary key example""" uuid_primary_key = models.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): """Proxy model example - a single table with multiple types.""" 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) # Internals for management command tests class TestModelA(ShowFieldTypeAndContent, PolymorphicModel): field1 = models.CharField(max_length=10) class TestModelB(TestModelA): field2 = models.CharField(max_length=10) class TestModelC(TestModelB): field3 = models.CharField(max_length=10) field4 = models.ManyToManyField(TestModelB, related_name="related_c") class NormalModelA(models.Model): """Normal Django inheritance, no polymorphic behavior""" field1 = models.CharField(max_length=10) class NormalModelB(NormalModelA): field2 = models.CharField(max_length=10) class NormalModelC(NormalModelB): field3 = models.CharField(max_length=10) django-polymorphic-2.1.2/polymorphic/000077500000000000000000000000001351315731100176665ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/__init__.py000066400000000000000000000006521351315731100220020ustar00rootroot00000000000000# -*- 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. """ import pkg_resources try: __version__ = pkg_resources.require("django-polymorphic")[0].version except pkg_resources.DistributionNotFound: __version__ = None # for RTD among others django-polymorphic-2.1.2/polymorphic/admin/000077500000000000000000000000001351315731100207565ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/admin/__init__.py000066400000000000000000000026621351315731100230750ustar00rootroot00000000000000""" ModelAdmin code to display polymorphic models. The admin consists of a parent admin (which shows in the admin with a list), and a child admin (which is used internally to show the edit/delete dialog). """ # Admins for the regular models from .parentadmin import PolymorphicParentModelAdmin # noqa from .childadmin import PolymorphicChildModelAdmin from .filters import PolymorphicChildModelFilter # Utils from .forms import PolymorphicModelChoiceForm # Expose generic admin features too. There is no need to split those # as the admin already relies on contenttypes. from .generic import GenericPolymorphicInlineModelAdmin # base class from .generic import GenericStackedPolymorphicInline # stacked inline # Helpers for the inlines from .helpers import PolymorphicInlineSupportMixin # mixin for the regular model admin! from .helpers import PolymorphicInlineAdminForm, PolymorphicInlineAdminFormSet # Inlines from .inlines import PolymorphicInlineModelAdmin # base class from .inlines import StackedPolymorphicInline # stacked inline __all__ = ( "PolymorphicParentModelAdmin", "PolymorphicChildModelAdmin", "PolymorphicModelChoiceForm", "PolymorphicChildModelFilter", "PolymorphicInlineAdminForm", "PolymorphicInlineAdminFormSet", "PolymorphicInlineSupportMixin", "PolymorphicInlineModelAdmin", "StackedPolymorphicInline", "GenericPolymorphicInlineModelAdmin", "GenericStackedPolymorphicInline", ) django-polymorphic-2.1.2/polymorphic/admin/childadmin.py000066400000000000000000000233631351315731100234330ustar00rootroot00000000000000""" The child admin displays the change/delete view of the subclass model. """ import inspect from django.contrib import admin from django.urls import resolve from django.utils.translation import ugettext_lazy as _ from polymorphic.utils import get_base_polymorphic_model from ..admin import PolymorphicParentModelAdmin class ParentAdminNotRegistered(RuntimeError): "The admin site for the model is not registered." 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 that the class uses (auto-detected if not set explicitly) base_model = None #: By setting ``base_form`` instead of ``form``, any subclass fields are automatically added to the form. #: This is useful when your model admin class is inherited by others. base_form = None #: By setting ``base_fieldsets`` instead of ``fieldsets``, #: any subclass fields can be automatically added. #: This is useful when your model admin class is inherited by others. base_fieldsets = None #: Default title for extra fieldset extra_fieldset_title = _("Contents") #: Whether the child admin model should be visible in the admin index page. show_in_index = False def __init__(self, model, admin_site, *args, **kwargs): super(PolymorphicChildModelAdmin, self).__init__( model, admin_site, *args, **kwargs ) if self.base_model is None: self.base_model = get_base_polymorphic_model(model) 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 when this is called from get_subclass_fields if not self.fieldsets and not self.fields: kwargs.setdefault("fields", "__all__") return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs) def get_model_perms(self, request): match = resolve(request.path) if ( not self.show_in_index and match.app_name == "admin" and match.url_name in ("index", "app_list") ): return {"add": False, "change": False, "delete": False} return super(PolymorphicChildModelAdmin, self).get_model_perms(request) @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", ] @property def object_history_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/object_history.html" % (app_label, opts.object_name.lower()), "admin/%s/object_history.html" % app_label, # Added: "admin/%s/%s/object_history.html" % (base_app_label, base_opts.object_name.lower()), "admin/%s/object_history.html" % base_app_label, "admin/polymorphic/object_history.html", "admin/object_history.html", ] def _get_parent_admin(self): # this returns parent admin instance on which to call response_post_save methods parent_model = self.model._meta.get_field("polymorphic_ctype").model if parent_model == self.model: # when parent_model is in among child_models, just return super instance return super(PolymorphicChildModelAdmin, self) try: return self.admin_site._registry[parent_model] except KeyError: # Admin is not registered for polymorphic_ctype model, but perhaps it's registered # for a intermediate proxy model, between the parent_model and this model. for klass in inspect.getmro(self.model): if not issubclass(klass, parent_model): continue # e.g. found a mixin. # Fetch admin instance for model class, see if it's a possible candidate. model_admin = self.admin_site._registry.get(klass) if model_admin is not None and isinstance( model_admin, PolymorphicParentModelAdmin ): return model_admin # Success! # If we get this far without returning there is no admin available raise ParentAdminNotRegistered( "No parent admin was registered for a '{0}' model.".format(parent_model) ) def response_post_save_add(self, request, obj): return self._get_parent_admin().response_post_save_add(request, obj) def response_post_save_change(self, request, obj): return self._get_parent_admin().response_post_save_change(request, obj) 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 ) def history_view(self, request, object_id, extra_context=None): # Make sure the history view can also display polymorphic breadcrumbs context = {"base_opts": self.base_model._meta} if extra_context: context.update(extra_context) return super(PolymorphicChildModelAdmin, self).history_view( request, object_id, extra_context=context ) # ---- Extra: improving the form/fieldset default display ---- def get_base_fieldsets(self, request, obj=None): return self.base_fieldsets def get_fieldsets(self, request, obj=None): base_fieldsets = self.get_base_fieldsets(request, obj) # If subclass declares fieldsets or fields, this is respected if self.fieldsets or self.fields 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 ( base_fieldsets[0], (self.extra_fieldset_title, {"fields": other_fields}), ) + base_fieldsets[1:] else: return 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(form.base_fields.keys()) + list( self.get_readonly_fields(request, obj) ) # Find which fields are not part of the common fields. for fieldset in self.get_base_fieldsets(request, obj): 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 django-polymorphic-2.1.2/polymorphic/admin/filters.py000066400000000000000000000023161351315731100230020ustar00rootroot00000000000000from django.contrib import admin from django.core.exceptions import PermissionDenied from django.utils.translation import ugettext_lazy as _ class PolymorphicChildModelFilter(admin.SimpleListFilter): """ An admin list filter for the PolymorphicParentModelAdmin which enables filtering by its child models. This can be used in the parent admin: .. code-block:: python list_filter = (PolymorphicChildModelFilter,) """ 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 django-polymorphic-2.1.2/polymorphic/admin/forms.py000066400000000000000000000013151351315731100224560ustar00rootroot00000000000000from django import forms from django.contrib.admin.widgets import AdminRadioSelect from django.utils.translation import ugettext_lazy as _ 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 django-polymorphic-2.1.2/polymorphic/admin/generic.py000066400000000000000000000051741351315731100227530ustar00rootroot00000000000000from django.contrib.contenttypes.admin import GenericInlineModelAdmin from django.contrib.contenttypes.models import ContentType from django.utils.functional import cached_property from polymorphic.formsets import ( BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild, polymorphic_child_forms_factory, ) from .inlines import PolymorphicInlineModelAdmin class GenericPolymorphicInlineModelAdmin( PolymorphicInlineModelAdmin, GenericInlineModelAdmin ): """ Base class for variation of inlines based on generic foreign keys. """ #: The formset class formset = BaseGenericPolymorphicInlineFormSet def get_formset(self, request, obj=None, **kwargs): """ Construct the generic inline formset class. """ # Construct the FormSet class. This is almost the same as parent version, # except that a different super is called so generic_inlineformset_factory() is used. # NOTE that generic_inlineformset_factory() also makes sure the GFK fields are excluded in the form. FormSet = GenericInlineModelAdmin.get_formset(self, request, obj=obj, **kwargs) FormSet.child_forms = polymorphic_child_forms_factory( formset_children=self.get_formset_children(request, obj=obj) ) return FormSet class Child(PolymorphicInlineModelAdmin.Child): """ Variation for generic inlines. """ # Make sure that the GFK fields are excluded from the child forms formset_child = GenericPolymorphicFormSetChild ct_field = "content_type" ct_fk_field = "object_id" @cached_property def content_type(self): """ Expose the ContentType that the child relates to. This can be used for the ``polymorphic_ctype`` field. """ return ContentType.objects.get_for_model( self.model, for_concrete_model=False ) def get_formset_child(self, request, obj=None, **kwargs): # Similar to GenericInlineModelAdmin.get_formset(), # make sure the GFK is automatically excluded from the form defaults = {"ct_field": self.ct_field, "fk_field": self.ct_fk_field} defaults.update(kwargs) return super( GenericPolymorphicInlineModelAdmin.Child, self ).get_formset_child(request, obj=obj, **defaults) class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin): """ The stacked layout for generic inlines. """ #: The default template to use. template = "admin/polymorphic/edit_inline/stacked.html" django-polymorphic-2.1.2/polymorphic/admin/helpers.py000066400000000000000000000132041351315731100227720ustar00rootroot00000000000000""" Rendering utils for admin forms; This makes sure that admin fieldsets/layout settings are exported to the template. """ import json from django.contrib.admin.helpers import AdminField, InlineAdminForm, InlineAdminFormSet from django.utils.encoding import force_text from django.utils.text import capfirst from django.utils.translation import ugettext from polymorphic.formsets import BasePolymorphicModelFormSet class PolymorphicInlineAdminForm(InlineAdminForm): """ Expose the admin configuration for a form """ def polymorphic_ctype_field(self): return AdminField(self.form, "polymorphic_ctype", False) @property def is_empty(self): return "__prefix__" in self.form.prefix class PolymorphicInlineAdminFormSet(InlineAdminFormSet): """ Internally used class to expose the formset in the template. """ def __init__(self, *args, **kwargs): # Assigned later via PolymorphicInlineSupportMixin later. self.request = kwargs.pop("request", None) self.obj = kwargs.pop("obj", None) super(PolymorphicInlineAdminFormSet, self).__init__(*args, **kwargs) def __iter__(self): """ Output all forms using the proper subtype settings. """ for form, original in zip( self.formset.initial_forms, self.formset.get_queryset() ): # Output the form model = original.get_real_instance_class() child_inline = self.opts.get_child_inline_instance(model) view_on_site_url = self.opts.get_view_on_site_url(original) yield PolymorphicInlineAdminForm( formset=self.formset, form=form, fieldsets=self.get_child_fieldsets(child_inline), prepopulated_fields=self.get_child_prepopulated_fields(child_inline), original=original, readonly_fields=self.get_child_readonly_fields(child_inline), model_admin=child_inline, view_on_site_url=view_on_site_url, ) # Extra rows, and empty prefixed forms. for form in self.formset.extra_forms + self.formset.empty_forms: model = form._meta.model child_inline = self.opts.get_child_inline_instance(model) yield PolymorphicInlineAdminForm( formset=self.formset, form=form, fieldsets=self.get_child_fieldsets(child_inline), prepopulated_fields=self.get_child_prepopulated_fields(child_inline), original=None, readonly_fields=self.get_child_readonly_fields(child_inline), model_admin=child_inline, ) def get_child_fieldsets(self, child_inline): return list(child_inline.get_fieldsets(self.request, self.obj) or ()) def get_child_readonly_fields(self, child_inline): return list(child_inline.get_readonly_fields(self.request, self.obj)) def get_child_prepopulated_fields(self, child_inline): fields = self.prepopulated_fields.copy() fields.update(child_inline.get_prepopulated_fields(self.request, self.obj)) return fields def inline_formset_data(self): """ A JavaScript data structure for the JavaScript code This overrides the default Django version to add the ``childTypes`` data. """ verbose_name = self.opts.verbose_name return json.dumps( { "name": "#%s" % self.formset.prefix, "options": { "prefix": self.formset.prefix, "addText": ugettext("Add another %(verbose_name)s") % {"verbose_name": capfirst(verbose_name)}, "childTypes": [ { "type": model._meta.model_name, "name": force_text(model._meta.verbose_name), } for model in self.formset.child_forms.keys() ], "deleteText": ugettext("Remove"), }, } ) class PolymorphicInlineSupportMixin(object): """ A Mixin to add to the regular admin, so it can work with our polymorphic inlines. This mixin needs to be included in the admin that hosts the ``inlines``. It makes sure the generated admin forms have different fieldsets/fields depending on the polymorphic type of the form instance. This is achieved by overwriting :func:`get_inline_formsets` to return an :class:`PolymorphicInlineAdminFormSet` instead of a standard Django :class:`~django.contrib.admin.helpers.InlineAdminFormSet` for the polymorphic formsets. """ def get_inline_formsets( self, request, formsets, inline_instances, obj=None, *args, **kwargs ): """ Overwritten version to produce the proper admin wrapping for the polymorphic inline formset. This fixes the media and form appearance of the inline polymorphic models. """ inline_admin_formsets = super( PolymorphicInlineSupportMixin, self ).get_inline_formsets(request, formsets, inline_instances, obj=obj) for admin_formset in inline_admin_formsets: if isinstance(admin_formset.formset, BasePolymorphicModelFormSet): # This is a polymorphic formset, which belongs to our inline. # Downcast the admin wrapper that generates the form fields. admin_formset.__class__ = PolymorphicInlineAdminFormSet admin_formset.request = request admin_formset.obj = obj return inline_admin_formsets django-polymorphic-2.1.2/polymorphic/admin/inlines.py000066400000000000000000000246101351315731100227740ustar00rootroot00000000000000""" Django Admin support for polymorphic inlines. Each row in the inline can correspond with a different subclass. """ from functools import partial from django.conf import settings from django.contrib.admin.options import InlineModelAdmin from django.contrib.admin.utils import flatten_fieldsets from django.core.exceptions import ImproperlyConfigured from django.forms import Media from polymorphic.formsets import ( BasePolymorphicInlineFormSet, PolymorphicFormSetChild, UnsupportedChildType, polymorphic_child_forms_factory, ) from polymorphic.formsets.utils import add_media from .helpers import PolymorphicInlineSupportMixin class PolymorphicInlineModelAdmin(InlineModelAdmin): """ A polymorphic inline, where each formset row can be a different form. Note that: * Permissions are only checked on the base model. * The child inlines can't override the base model fields, only this parent inline can do that. """ formset = BasePolymorphicInlineFormSet #: The extra media to add for the polymorphic inlines effect. #: This can be redefined for subclasses. polymorphic_media = Media( js=( "admin/js/vendor/jquery/jquery{}.js".format( "" if settings.DEBUG else ".min" ), "admin/js/jquery.init.js", "polymorphic/js/polymorphic_inlines.js", ), css={"all": ("polymorphic/css/polymorphic_inlines.css",)}, ) #: The extra forms to show #: By default there are no 'extra' forms as the desired type is unknown. #: Instead, add each new item using JavaScript that first offers a type-selection. extra = 0 #: Inlines for all model sub types that can be displayed in this inline. #: Each row is a :class:`PolymorphicInlineModelAdmin.Child` child_inlines = () def __init__(self, parent_model, admin_site): super(PolymorphicInlineModelAdmin, self).__init__(parent_model, admin_site) # Extra check to avoid confusion # While we could monkeypatch the admin here, better stay explicit. parent_admin = admin_site._registry.get(parent_model, None) if parent_admin is not None: # Can be None during check if not isinstance(parent_admin, PolymorphicInlineSupportMixin): raise ImproperlyConfigured( "To use polymorphic inlines, add the `PolymorphicInlineSupportMixin` mixin " "to the ModelAdmin that hosts the inline." ) # While the inline is created per request, the 'request' object is not known here. # Hence, creating all child inlines unconditionally, without checking permissions. self.child_inline_instances = self.get_child_inline_instances() # Create a lookup table self._child_inlines_lookup = {} for child_inline in self.child_inline_instances: self._child_inlines_lookup[child_inline.model] = child_inline def get_child_inline_instances(self): """ :rtype List[PolymorphicInlineModelAdmin.Child] """ instances = [] for ChildInlineType in self.child_inlines: instances.append(ChildInlineType(parent_inline=self)) return instances def get_child_inline_instance(self, model): """ Find the child inline for a given model. :rtype: PolymorphicInlineModelAdmin.Child """ try: return self._child_inlines_lookup[model] except KeyError: raise UnsupportedChildType( "Model '{0}' not found in child_inlines".format(model.__name__) ) def get_formset(self, request, obj=None, **kwargs): """ Construct the inline formset class. This passes all class attributes to the formset. :rtype: type """ # Construct the FormSet class FormSet = super(PolymorphicInlineModelAdmin, self).get_formset( request, obj=obj, **kwargs ) # Instead of completely redefining super().get_formset(), we use # the regular inlineformset_factory(), and amend that with our extra bits. # This code line is the essence of what polymorphic_inlineformset_factory() does. FormSet.child_forms = polymorphic_child_forms_factory( formset_children=self.get_formset_children(request, obj=obj) ) return FormSet def get_formset_children(self, request, obj=None): """ The formset 'children' provide the details for all child models that are part of this formset. It provides a stripped version of the modelform/formset factory methods. """ formset_children = [] for child_inline in self.child_inline_instances: # TODO: the children can be limited here per request based on permissions. formset_children.append(child_inline.get_formset_child(request, obj=obj)) return formset_children def get_fieldsets(self, request, obj=None): """ Hook for specifying fieldsets. """ if self.fieldsets: return self.fieldsets else: return [] # Avoid exposing fields to the child def get_fields(self, request, obj=None): if self.fields: return self.fields else: return [] # Avoid exposing fields to the child @property def media(self): # The media of the inline focuses on the admin settings, # whether to expose the scripts for filter_horizontal etc.. # The admin helper exposes the inline + formset media. base_media = super(PolymorphicInlineModelAdmin, self).media all_media = Media() add_media(all_media, base_media) # Add all media of the child inline instances for child_instance in self.child_inline_instances: child_media = child_instance.media # Avoid adding the same media object again and again if ( child_media._css != base_media._css and child_media._js != base_media._js ): add_media(all_media, child_media) add_media(all_media, self.polymorphic_media) return all_media class Child(InlineModelAdmin): """ The child inline; which allows configuring the admin options for the child appearance. Note that not all options will be honored by the parent, notably the formset options: * :attr:`extra` * :attr:`min_num` * :attr:`max_num` The model form options however, will all be read. """ formset_child = PolymorphicFormSetChild extra = 0 # TODO: currently unused for the children. def __init__(self, parent_inline): self.parent_inline = parent_inline super(PolymorphicInlineModelAdmin.Child, self).__init__( parent_inline.parent_model, parent_inline.admin_site ) def get_formset(self, request, obj=None, **kwargs): # The child inline is only used to construct the form, # and allow to override the form field attributes. # The formset is created by the parent inline. raise RuntimeError("The child get_formset() is not used.") def get_fields(self, request, obj=None): if self.fields: return self.fields # Standard Django logic, use the form to determine the fields. # The form needs to pass through all factory logic so all 'excludes' are set as well. # Default Django does: form = self.get_formset(request, obj, fields=None).form # Use 'fields=None' avoids recursion in the field autodetection. form = self.get_formset_child(request, obj, fields=None).get_form() return list(form.base_fields) + list(self.get_readonly_fields(request, obj)) def get_formset_child(self, request, obj=None, **kwargs): """ Return the formset child that the parent inline can use to represent us. :rtype: PolymorphicFormSetChild """ # Similar to the normal get_formset(), the caller may pass fields to override the defaults settings # in the inline. In Django's GenericInlineModelAdmin.get_formset() this is also used in the same way, # to make sure the 'exclude' also contains the GFK fields. # # Hence this code is almost identical to InlineModelAdmin.get_formset() # and GenericInlineModelAdmin.get_formset() # # Transfer the local inline attributes to the formset child, # this allows overriding settings. if "fields" in kwargs: fields = kwargs.pop("fields") else: fields = flatten_fieldsets(self.get_fieldsets(request, obj)) if self.exclude is None: exclude = [] else: exclude = list(self.exclude) exclude.extend(self.get_readonly_fields(request, obj)) # Add forcefully, as Django 1.10 doesn't include readonly fields. exclude.append("polymorphic_ctype") if ( self.exclude is None and hasattr(self.form, "_meta") and self.form._meta.exclude ): # Take the custom ModelForm's Meta.exclude into account only if the # InlineModelAdmin doesn't define its own. exclude.extend(self.form._meta.exclude) # can_delete = self.can_delete and self.has_delete_permission(request, obj) defaults = { "form": self.form, "fields": fields, "exclude": exclude or None, "formfield_callback": partial( self.formfield_for_dbfield, request=request ), } defaults.update(kwargs) # This goes through the same logic that get_formset() calls # by passing the inline class attributes to modelform_factory() FormSetChildClass = self.formset_child return FormSetChildClass(self.model, **defaults) class StackedPolymorphicInline(PolymorphicInlineModelAdmin): """ Stacked inline for django-polymorphic models. Since tabular doesn't make much sense with changed fields, just offer this one. """ #: The default template to use. template = "admin/polymorphic/edit_inline/stacked.html" django-polymorphic-2.1.2/polymorphic/admin/parentadmin.py000066400000000000000000000367261351315731100236500ustar00rootroot00000000000000""" The parent admin displays the list view of the base model. """ import sys from django.contrib import admin from django.contrib.admin.helpers import AdminErrorList, AdminForm from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.db import models from django.http import Http404, HttpResponseRedirect from django.template.response import TemplateResponse 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 _ from polymorphic.utils import get_base_polymorphic_model from .forms import PolymorphicModelChoiceForm try: # Django 2.0+ from django.urls import URLResolver except ImportError: # Django < 2.0 from django.urls import RegexURLResolver as URLResolver if sys.version_info[0] >= 3: long = int class RegistrationClosed(RuntimeError): "The admin model can't be registered anymore at this point." class ChildAdminNotRegistered(RuntimeError): "The admin site for the model is not registered." class PolymorphicParentModelAdmin(admin.ModelAdmin): """ A admin interface that can displays different change/delete pages, depending on the polymorphic model. To use this class, one attribute need to be defined: * :attr:`child_models` should be a list models. Alternatively, the following methods can be implemented: * :func:`get_child_models` should return a list of models. * 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 (auto-detected if not set explicitly) 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 = r"(\d+|__fk__)" def __init__(self, model, admin_site, *args, **kwargs): super(PolymorphicParentModelAdmin, self).__init__( model, admin_site, *args, **kwargs ) self._is_setup = False if self.base_model is None: self.base_model = get_base_polymorphic_model(model) def _lazy_setup(self): if self._is_setup: return self._child_models = self.get_child_models() # Make absolutely sure that the child models don't use the old 0.9 format, # as of polymorphic 1.4 this deprecated configuration is no longer supported. # Instead, register the child models in the admin too. if self._child_models and not issubclass(self._child_models[0], models.Model): raise ImproperlyConfigured( "Since django-polymorphic 1.4, the `child_models` attribute " "and `get_child_models()` method should be a list of models only.\n" "The model-admin class should be registered in the regular Django admin." ) self._child_admin_site = self.admin_site 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. """ self._lazy_setup() 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, super_if_self=True): 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"], super_if_self=super_if_self ) def _get_real_admin_by_ct(self, ct_id, super_if_self=True): 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: # Handle model deletion raise Http404("No model found for '{0}.{1}'.".format(*ct.natural_key())) return self._get_real_admin_by_model(model_class, super_if_self=super_if_self) def _get_real_admin_by_model(self, model_class, super_if_self=True): # 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. real_admin = self._child_admin_site._registry[model_class] except KeyError: raise ChildAdminNotRegistered( "No child admin site was registered for a '{0}' model.".format( model_class ) ) if super_if_self and real_admin is self: return super(PolymorphicParentModelAdmin, self) else: return real_admin 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 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 changeform_view(self, request, object_id=None, *args, **kwargs): # The `changeform_view` is available as of Django 1.7, combining the add_view and change_view. # As it's directly called by django-reversion, this method is also overwritten to make sure it # also redirects to the child admin. if object_id: real_admin = self._get_real_admin(object_id) return real_admin.changeform_view(request, object_id, *args, **kwargs) else: # Add view. As it should already be handled via `add_view`, this means something custom is done here! return super(PolymorphicParentModelAdmin, self).changeform_view( request, object_id, *args, **kwargs ) def history_view(self, request, object_id, extra_context=None): """Redirect the history view to the real admin.""" real_admin = self._get_real_admin(object_id) return real_admin.history_view(request, object_id, extra_context=extra_context) 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() # At this point. all admin code needs to be known. self._lazy_setup() return 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 = URLResolver("^", 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"]: # QUERY_STRING is bytes in Python 3, using force_text() to decode it as string. # See QueryDict how Django deals with that. extra_qs = "&{0}".format(force_text(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, } ) templates = 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", ] request.current_app = self.admin_site.name return TemplateResponse(request, templates, context) @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", ] django-polymorphic-2.1.2/polymorphic/base.py000066400000000000000000000217311351315731100211560ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ PolymorphicModel Meta Class """ from __future__ import absolute_import import inspect import os import sys import warnings import django from django.core.exceptions import ImproperlyConfigured 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"] DUMPDATA_COMMAND = os.path.join( "django", "core", "management", "commands", "dumpdata.py" ) class ManagerInheritanceWarning(RuntimeWarning): pass ################################################################################### # 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": return super(PolymorphicModelBase, self).__new__( self, model_name, bases, attrs ) # Make sure that manager_inheritance_from_future is set, since django-polymorphic 1.x already # simulated that behavior on the polymorphic manager to all subclasses behave like polymorphics if django.VERSION < (2, 0): if "Meta" in attrs: if not hasattr(attrs["Meta"], "manager_inheritance_from_future"): attrs["Meta"].manager_inheritance_from_future = True else: attrs["Meta"] = type( "Meta", (object,), {"manager_inheritance_from_future": True} ) # 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) # validate resulting default manager if not new_class._meta.abstract and not new_class._meta.swapped: self.validate_model_manager(new_class.objects, model_name, "objects") # 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 @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): if django.VERSION < (2, 0): extra = "\nConsider using Meta.manager_inheritance_from_future = True for Django 1.x projects" else: extra = "" e = ( 'PolymorphicModel: "{0}.{1}" manager is of type "{2}", but must be a subclass of' " PolymorphicManager.{extra} to support retrieving subclasses".format( model_name, manager_name, type(manager).__name__, extra=extra ) ) warnings.warn(e, ManagerInheritanceWarning, stacklevel=3) return manager if not getattr(manager, "queryset_class", None) or not issubclass( manager.queryset_class, PolymorphicQuerySet ): e = ( 'PolymorphicModel: "{0}.{1}" has been instantiated with a queryset class ' "which is not a subclass of PolymorphicQuerySet (which is required)".format( model_name, manager_name ) ) warnings.warn(e, ManagerInheritanceWarning, stacklevel=3) return manager @property def base_objects(self): warnings.warn( "Using PolymorphicModel.base_objects is deprecated.\n" "Use {0}.objects.non_polymorphic() instead.".format( self.__class__.__name__ ), DeprecationWarning, stacklevel=2, ) return self._base_objects @property def _base_objects(self): # Create a manager so the API works as expected. Just don't register it # anymore in the Model Meta, so it doesn't substitute our polymorphic # manager as default manager for the third level of inheritance when # that third level doesn't define a manager at all. manager = models.Manager() manager.name = "base_objects" manager.model = self return manager @property def _default_manager(self): if len(sys.argv) > 1 and sys.argv[1] == "dumpdata": # TODO: investigate Django how this can be avoided # 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. frm = inspect.stack()[ 1 ] # frm[1] is caller file name, frm[3] is caller function name if DUMPDATA_COMMAND in frm[1]: return self._base_objects manager = super(PolymorphicModelBase, self)._default_manager if not isinstance(manager, PolymorphicManager): warnings.warn( "{0}._default_manager is not a PolymorphicManager".format( self.__class__.__name__ ), ManagerInheritanceWarning, ) return manager django-polymorphic-2.1.2/polymorphic/compat.py000066400000000000000000000023641351315731100215300ustar00rootroot00000000000000"""Compatibility with Python 2 (taken from 'django.utils.six')""" import sys PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 if PY3: string_types = (str,) integer_types = (int,) class_types = (type,) text_type = str binary_type = bytes MAXSIZE = sys.maxsize else: string_types = (basestring,) integer_types = (int, long) def with_metaclass(meta, *bases): class metaclass(type): def __new__(cls, name, this_bases, d): return meta(name, bases, d) return type.__new__(metaclass, "temporary_class", (), {}) def python_2_unicode_compatible(klass): """ A decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method returning text and apply this decorator to the class. """ if PY2: if "__str__" not in klass.__dict__: raise ValueError( "@python_2_unicode_compatible cannot be applied " "to %s because it doesn't define __str__()." % klass.__name__ ) klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode("utf-8") return klass django-polymorphic-2.1.2/polymorphic/contrib/000077500000000000000000000000001351315731100213265ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/contrib/__init__.py000066400000000000000000000000001351315731100234250ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/contrib/extra_views.py000066400000000000000000000100651351315731100242420ustar00rootroot00000000000000""" The ``extra_views.formsets`` provides a simple way to handle formsets. The ``extra_views.advanced`` provides a method to combine that with a create/update form. This package provides classes that support both options for polymorphic formsets. """ from __future__ import absolute_import import extra_views from django.core.exceptions import ImproperlyConfigured from polymorphic.formsets import ( BasePolymorphicInlineFormSet, BasePolymorphicModelFormSet, polymorphic_child_forms_factory, ) __all__ = ( "PolymorphicFormSetView", "PolymorphicInlineFormSetView", "PolymorphicInlineFormSet", ) class PolymorphicFormSetMixin(object): """ Internal Mixin, that provides polymorphic integration with the ``extra_views`` package. """ formset_class = BasePolymorphicModelFormSet #: Default 0 extra forms factory_kwargs = {"extra": 0} #: Define the children # :type: list[PolymorphicFormSetChild] formset_children = None def get_formset_children(self): """ :rtype: list[PolymorphicFormSetChild] """ if not self.formset_children: raise ImproperlyConfigured( "Define 'formset_children' as list of `PolymorphicFormSetChild`" ) return self.formset_children def get_formset_child_kwargs(self): return {} def get_formset(self): """ Returns the formset class from the inline formset factory """ # Implementation detail: # Since `polymorphic_modelformset_factory` and `polymorphic_inlineformset_factory` mainly # reuse the standard factories, and then add `child_forms`, the same can be done here. # This makes sure the base class construction is completely honored. FormSet = super(PolymorphicFormSetMixin, self).get_formset() FormSet.child_forms = polymorphic_child_forms_factory( self.get_formset_children(), **self.get_formset_child_kwargs() ) return FormSet class PolymorphicFormSetView(PolymorphicFormSetMixin, extra_views.ModelFormSetView): """ A view that displays a single polymorphic formset. .. code-block:: python from polymorphic.formsets import PolymorphicFormSetChild class ItemsView(PolymorphicFormSetView): model = Item formset_children = [ PolymorphicFormSetChild(ItemSubclass1), PolymorphicFormSetChild(ItemSubclass2), ] """ formset_class = BasePolymorphicModelFormSet class PolymorphicInlineFormSetView( PolymorphicFormSetMixin, extra_views.InlineFormSetView ): """ A view that displays a single polymorphic formset - with one parent object. This is a variation of the :mod:`extra_views` package classes for django-polymorphic. .. code-block:: python from polymorphic.formsets import PolymorphicFormSetChild class OrderItemsView(PolymorphicInlineFormSetView): model = Order inline_model = Item formset_children = [ PolymorphicFormSetChild(ItemSubclass1), PolymorphicFormSetChild(ItemSubclass2), ] """ formset_class = BasePolymorphicInlineFormSet class PolymorphicInlineFormSet( PolymorphicFormSetMixin, extra_views.InlineFormSetFactory ): """ An inline to add to the ``inlines`` of the :class:`~extra_views.advanced.CreateWithInlinesView` and :class:`~extra_views.advanced.UpdateWithInlinesView` class. .. code-block:: python from polymorphic.formsets import PolymorphicFormSetChild class ItemsInline(PolymorphicInlineFormSet): model = Item formset_children = [ PolymorphicFormSetChild(ItemSubclass1), PolymorphicFormSetChild(ItemSubclass2), ] class OrderCreateView(CreateWithInlinesView): model = Order inlines = [ItemsInline] def get_success_url(self): return self.object.get_absolute_url() """ formset_class = BasePolymorphicInlineFormSet django-polymorphic-2.1.2/polymorphic/contrib/guardian.py000066400000000000000000000024731351315731100235000ustar00rootroot00000000000000from django.contrib.contenttypes.models import ContentType def get_polymorphic_base_content_type(obj): """ Helper function to return the base polymorphic content type id. This should used with django-guardian and the GUARDIAN_GET_CONTENT_TYPE option. See the django-guardian documentation for more information: https://django-guardian.readthedocs.io/en/latest/configuration.html#guardian-get-content-type """ if hasattr(obj, "polymorphic_model_marker"): try: superclasses = list(obj.__class__.mro()) except TypeError: # obj is an object so mro() need to be called with the obj. superclasses = list(obj.__class__.mro(obj)) polymorphic_superclasses = list() for sclass in superclasses: if hasattr(sclass, "polymorphic_model_marker"): polymorphic_superclasses.append(sclass) # PolymorphicMPTT adds an additional class between polymorphic and base class. if hasattr(obj, "can_have_children"): root_polymorphic_class = polymorphic_superclasses[-3] else: root_polymorphic_class = polymorphic_superclasses[-2] ctype = ContentType.objects.get_for_model(root_polymorphic_class) else: ctype = ContentType.objects.get_for_model(obj) return ctype django-polymorphic-2.1.2/polymorphic/formsets/000077500000000000000000000000001351315731100215305ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/formsets/__init__.py000066400000000000000000000025571351315731100236520ustar00rootroot00000000000000""" This allows creating formsets where each row can be a different form type. The logic of the formsets work similar to the standard Django formsets; there are factory methods to construct the classes with the proper form settings. The "parent" formset hosts the entire model and their child model. For every child type, there is an :class:`PolymorphicFormSetChild` instance that describes how to display and construct the child. It's parameters are very similar to the parent's factory method. """ from .generic import ( # Can import generic here, as polymorphic already depends on the 'contenttypes' app. BaseGenericPolymorphicInlineFormSet, GenericPolymorphicFormSetChild, generic_polymorphic_inlineformset_factory, ) from .models import ( BasePolymorphicInlineFormSet, BasePolymorphicModelFormSet, PolymorphicFormSetChild, UnsupportedChildType, polymorphic_child_forms_factory, polymorphic_inlineformset_factory, polymorphic_modelformset_factory, ) __all__ = ( "BasePolymorphicModelFormSet", "BasePolymorphicInlineFormSet", "PolymorphicFormSetChild", "UnsupportedChildType", "polymorphic_modelformset_factory", "polymorphic_inlineformset_factory", "polymorphic_child_forms_factory", "BaseGenericPolymorphicInlineFormSet", "GenericPolymorphicFormSetChild", "generic_polymorphic_inlineformset_factory", ) django-polymorphic-2.1.2/polymorphic/formsets/generic.py000066400000000000000000000103241351315731100235160ustar00rootroot00000000000000from django.contrib.contenttypes.forms import ( BaseGenericInlineFormSet, generic_inlineformset_factory, ) from django.contrib.contenttypes.models import ContentType from django.db import models from django.forms.models import ModelForm from .models import ( BasePolymorphicModelFormSet, PolymorphicFormSetChild, polymorphic_child_forms_factory, ) class GenericPolymorphicFormSetChild(PolymorphicFormSetChild): """ Formset child for generic inlines """ def __init__(self, *args, **kwargs): self.ct_field = kwargs.pop("ct_field", "content_type") self.fk_field = kwargs.pop("fk_field", "object_id") super(GenericPolymorphicFormSetChild, self).__init__(*args, **kwargs) def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs): """ Construct the form class for the formset child. """ exclude = list(self.exclude) extra_exclude = kwargs.pop("extra_exclude", None) if extra_exclude: exclude += list(extra_exclude) # Make sure the GFK fields are excluded by default # This is similar to what generic_inlineformset_factory() does # if there is no field called `ct_field` let the exception propagate opts = self.model._meta ct_field = opts.get_field(self.ct_field) if ( not isinstance(ct_field, models.ForeignKey) or ct_field.remote_field.model != ContentType ): raise Exception( "fk_name '%s' is not a ForeignKey to ContentType" % ct_field ) fk_field = opts.get_field(self.fk_field) # let the exception propagate exclude.extend([ct_field.name, fk_field.name]) kwargs["exclude"] = exclude return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs) class BaseGenericPolymorphicInlineFormSet( BaseGenericInlineFormSet, BasePolymorphicModelFormSet ): """ Polymorphic formset variation for inline generic formsets """ def generic_polymorphic_inlineformset_factory( model, formset_children, form=ModelForm, formset=BaseGenericPolymorphicInlineFormSet, ct_field="content_type", fk_field="object_id", # Base form # TODO: should these fields be removed in favor of creating # the base form as a formset child too? fields=None, exclude=None, extra=1, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, min_num=None, validate_min=False, child_form_kwargs=None, ): """ Construct the class for a generic inline polymorphic formset. All arguments are identical to :func:`~django.contrib.contenttypes.forms.generic_inlineformset_factory`, with the exception of the ``formset_children`` argument. :param formset_children: A list of all child :class:`PolymorphicFormSetChild` objects that tell the inline how to render the child model types. :type formset_children: Iterable[PolymorphicFormSetChild] :rtype: type """ kwargs = { "model": model, "form": form, "formfield_callback": formfield_callback, "formset": formset, "ct_field": ct_field, "fk_field": fk_field, "extra": extra, "can_delete": can_delete, "can_order": can_order, "fields": fields, "exclude": exclude, "min_num": min_num, "max_num": max_num, "validate_min": validate_min, "validate_max": validate_max, "for_concrete_model": for_concrete_model, # 'localized_fields': localized_fields, # 'labels': labels, # 'help_texts': help_texts, # 'error_messages': error_messages, # 'field_classes': field_classes, } if child_form_kwargs is None: child_form_kwargs = {} child_kwargs = { # 'exclude': exclude, "ct_field": ct_field, "fk_field": fk_field, } if child_form_kwargs: child_kwargs.update(child_form_kwargs) FormSet = generic_inlineformset_factory(**kwargs) FormSet.child_forms = polymorphic_child_forms_factory( formset_children, **child_kwargs ) return FormSet django-polymorphic-2.1.2/polymorphic/formsets/models.py000066400000000000000000000362501351315731100233730ustar00rootroot00000000000000from collections import OrderedDict from django import forms from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured, ValidationError from django.forms.models import ( BaseInlineFormSet, BaseModelFormSet, ModelForm, inlineformset_factory, modelform_factory, modelformset_factory, ) from django.utils.functional import cached_property from polymorphic.models import PolymorphicModel from .utils import add_media class UnsupportedChildType(LookupError): pass class PolymorphicFormSetChild(object): """ Metadata to define the inline of a polymorphic child. Provide this information in the :func:'polymorphic_inlineformset_factory' construction. """ def __init__( self, model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None, localized_fields=None, labels=None, help_texts=None, error_messages=None, ): self.model = model # Instead of initializing the form here right away, # the settings are saved so get_form() can receive additional exclude kwargs. # This is mostly needed for the generic inline formsets self._form_base = form self.fields = fields self.exclude = exclude or () self.formfield_callback = formfield_callback self.widgets = widgets self.localized_fields = localized_fields self.labels = labels self.help_texts = help_texts self.error_messages = error_messages @cached_property def content_type(self): """ Expose the ContentType that the child relates to. This can be used for the ''polymorphic_ctype'' field. """ return ContentType.objects.get_for_model(self.model, for_concrete_model=False) def get_form(self, **kwargs): """ Construct the form class for the formset child. """ # Do what modelformset_factory() / inlineformset_factory() does to the 'form' argument; # Construct the form with the given ModelFormOptions values # Fields can be overwritten. To support the global 'polymorphic_child_forms_factory' kwargs, # that doesn't completely replace all 'exclude' settings defined per child type, # we allow to define things like 'extra_...' fields that are amended to the current child settings. exclude = list(self.exclude) extra_exclude = kwargs.pop("extra_exclude", None) if extra_exclude: exclude += list(extra_exclude) defaults = { "form": self._form_base, "formfield_callback": self.formfield_callback, "fields": self.fields, "exclude": exclude, # 'for_concrete_model': for_concrete_model, "localized_fields": self.localized_fields, "labels": self.labels, "help_texts": self.help_texts, "error_messages": self.error_messages, # 'field_classes': field_classes, } defaults.update(kwargs) return modelform_factory(self.model, **defaults) def polymorphic_child_forms_factory(formset_children, **kwargs): """ Construct the forms for the formset children. This is mostly used internally, and rarely needs to be used by external projects. When using the factory methods (:func:'polymorphic_inlineformset_factory'), this feature is called already for you. """ child_forms = OrderedDict() for formset_child in formset_children: child_forms[formset_child.model] = formset_child.get_form(**kwargs) return child_forms class BasePolymorphicModelFormSet(BaseModelFormSet): """ A formset that can produce different forms depending on the object type. Note that the 'add' feature is therefore more complex, as all variations need ot be exposed somewhere. When switching existing formsets to the polymorphic formset, note that the ID field will no longer be named ''model_ptr'', but just appear as ''id''. """ # Assigned by the factory child_forms = OrderedDict() def __init__(self, *args, **kwargs): super(BasePolymorphicModelFormSet, self).__init__(*args, **kwargs) self.queryset_data = self.get_queryset() def _construct_form(self, i, **kwargs): """ Create the form, depending on the model that's behind it. """ # BaseModelFormSet logic if self.is_bound and i < self.initial_form_count(): pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name) pk = self.data[pk_key] pk_field = self.model._meta.pk to_python = self._get_to_python(pk_field) pk = to_python(pk) kwargs["instance"] = self._existing_object(pk) if i < self.initial_form_count() and "instance" not in kwargs: kwargs["instance"] = self.get_queryset()[i] if i >= self.initial_form_count() and self.initial_extra: # Set initial values for extra forms try: kwargs["initial"] = self.initial_extra[i - self.initial_form_count()] except IndexError: pass # BaseFormSet logic, with custom formset_class defaults = { "auto_id": self.auto_id, "prefix": self.add_prefix(i), "error_class": self.error_class, } if self.is_bound: defaults["data"] = self.data defaults["files"] = self.files if self.initial and "initial" not in kwargs: try: defaults["initial"] = self.initial[i] except IndexError: pass # Allow extra forms to be empty, unless they're part of # the minimum forms. if i >= self.initial_form_count() and i >= self.min_num: defaults["empty_permitted"] = True defaults["use_required_attribute"] = False defaults.update(kwargs) # Need to find the model that will be displayed in this form. # Hence, peeking in the self.queryset_data beforehand. if self.is_bound: if "instance" in defaults: # Object is already bound to a model, won't change the content type model = defaults[ "instance" ].get_real_instance_class() # allow proxy models else: # Extra or empty form, use the provided type. # Note this completely tru prefix = defaults["prefix"] try: ct_id = int(self.data["{0}-polymorphic_ctype".format(prefix)]) except (KeyError, ValueError): raise ValidationError( "Formset row {0} has no 'polymorphic_ctype' defined!".format( prefix ) ) model = ContentType.objects.get_for_id(ct_id).model_class() if model not in self.child_forms: # Perform basic validation, as we skip the ChoiceField here. raise UnsupportedChildType( "Child model type {0} is not part of the formset".format(model) ) else: if "instance" in defaults: model = defaults[ "instance" ].get_real_instance_class() # allow proxy models elif "polymorphic_ctype" in defaults.get("initial", {}): model = defaults["initial"]["polymorphic_ctype"].model_class() elif i < len(self.queryset_data): model = self.queryset_data[i].__class__ else: # Extra forms, cycle between all types # TODO: take the 'extra' value of each child formset into account. total_known = len(self.queryset_data) child_models = list(self.child_forms.keys()) model = child_models[(i - total_known) % len(child_models)] form_class = self.get_form_class(model) form = form_class(**defaults) self.add_fields(form, i) return form def add_fields(self, form, index): """Add a hidden field for the content type.""" ct = ContentType.objects.get_for_model( form._meta.model, for_concrete_model=False ) choices = [(ct.pk, ct)] # Single choice, existing forms can't change the value. form.fields["polymorphic_ctype"] = forms.ChoiceField( choices=choices, initial=ct.pk, required=False, widget=forms.HiddenInput ) super(BasePolymorphicModelFormSet, self).add_fields(form, index) def get_form_class(self, model): """ Return the proper form class for the given model. """ if not self.child_forms: raise ImproperlyConfigured( "No 'child_forms' defined in {0}".format(self.__class__.__name__) ) if not issubclass(model, PolymorphicModel): raise TypeError("Expect polymorphic model type, not {0}".format(model)) try: return self.child_forms[model] except KeyError: # This may happen when the query returns objects of a type that was not handled by the formset. raise UnsupportedChildType( "The '{0}' found a '{1}' model in the queryset, " "but no form class is registered to display it.".format( self.__class__.__name__, model.__name__ ) ) def is_multipart(self): """ Returns True if the formset needs to be multipart, i.e. it has FileInput. Otherwise, False. """ return any(f.is_multipart() for f in self.empty_forms) @property def media(self): # Include the media of all form types. # The form media includes all form widget media media = forms.Media() for form in self.empty_forms: add_media(media, form.media) return media @cached_property def empty_forms(self): """ Return all possible empty forms """ forms = [] for model, form_class in self.child_forms.items(): kwargs = self.get_form_kwargs(None) form = form_class( auto_id=self.auto_id, prefix=self.add_prefix("__prefix__"), empty_permitted=True, use_required_attribute=False, **kwargs ) self.add_fields(form, None) forms.append(form) return forms @property def empty_form(self): # TODO: make an exception when can_add_base is defined? raise RuntimeError( "'empty_form' is not used in polymorphic formsets, use 'empty_forms' instead." ) def polymorphic_modelformset_factory( model, formset_children, formset=BasePolymorphicModelFormSet, # Base field # TODO: should these fields be removed in favor of creating # the base form as a formset child too? form=ModelForm, fields=None, exclude=None, extra=1, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False, localized_fields=None, labels=None, help_texts=None, error_messages=None, min_num=None, validate_min=False, field_classes=None, child_form_kwargs=None, ): """ Construct the class for an polymorphic model formset. All arguments are identical to :func:'~django.forms.models.modelformset_factory', with the exception of the ''formset_children'' argument. :param formset_children: A list of all child :class:'PolymorphicFormSetChild' objects that tell the inline how to render the child model types. :type formset_children: Iterable[PolymorphicFormSetChild] :rtype: type """ kwargs = { "model": model, "form": form, "formfield_callback": formfield_callback, "formset": formset, "extra": extra, "can_delete": can_delete, "can_order": can_order, "fields": fields, "exclude": exclude, "min_num": min_num, "max_num": max_num, "widgets": widgets, "validate_min": validate_min, "validate_max": validate_max, "localized_fields": localized_fields, "labels": labels, "help_texts": help_texts, "error_messages": error_messages, "field_classes": field_classes, } FormSet = modelformset_factory(**kwargs) child_kwargs = { # 'exclude': exclude, } if child_form_kwargs: child_kwargs.update(child_form_kwargs) FormSet.child_forms = polymorphic_child_forms_factory( formset_children, **child_kwargs ) return FormSet class BasePolymorphicInlineFormSet(BaseInlineFormSet, BasePolymorphicModelFormSet): """ Polymorphic formset variation for inline formsets """ def _construct_form(self, i, **kwargs): return super(BasePolymorphicInlineFormSet, self)._construct_form(i, **kwargs) def polymorphic_inlineformset_factory( parent_model, model, formset_children, formset=BasePolymorphicInlineFormSet, fk_name=None, # Base field # TODO: should these fields be removed in favor of creating # the base form as a formset child too? form=ModelForm, fields=None, exclude=None, extra=1, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False, localized_fields=None, labels=None, help_texts=None, error_messages=None, min_num=None, validate_min=False, field_classes=None, child_form_kwargs=None, ): """ Construct the class for an inline polymorphic formset. All arguments are identical to :func:'~django.forms.models.inlineformset_factory', with the exception of the ''formset_children'' argument. :param formset_children: A list of all child :class:'PolymorphicFormSetChild' objects that tell the inline how to render the child model types. :type formset_children: Iterable[PolymorphicFormSetChild] :rtype: type """ kwargs = { "parent_model": parent_model, "model": model, "form": form, "formfield_callback": formfield_callback, "formset": formset, "fk_name": fk_name, "extra": extra, "can_delete": can_delete, "can_order": can_order, "fields": fields, "exclude": exclude, "min_num": min_num, "max_num": max_num, "widgets": widgets, "validate_min": validate_min, "validate_max": validate_max, "localized_fields": localized_fields, "labels": labels, "help_texts": help_texts, "error_messages": error_messages, "field_classes": field_classes, } FormSet = inlineformset_factory(**kwargs) child_kwargs = { # 'exclude': exclude, } if child_form_kwargs: child_kwargs.update(child_form_kwargs) FormSet.child_forms = polymorphic_child_forms_factory( formset_children, **child_kwargs ) return FormSet django-polymorphic-2.1.2/polymorphic/formsets/utils.py000066400000000000000000000007721351315731100232500ustar00rootroot00000000000000""" Internal utils """ import django def add_media(dest, media): """ Optimized version of django.forms.Media.__add__() that doesn't create new objects. """ if django.VERSION >= (2, 2): dest._css_lists.extend(media._css_lists) dest._js_lists.extend(media._js_lists) elif django.VERSION >= (2, 0): combined = dest + media dest._css = combined._css dest._js = combined._js else: dest.add_css(media._css) dest.add_js(media._js) django-polymorphic-2.1.2/polymorphic/locale/000077500000000000000000000000001351315731100211255ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/locale/en/000077500000000000000000000000001351315731100215275ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/locale/en/LC_MESSAGES/000077500000000000000000000000001351315731100233145ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/locale/en/LC_MESSAGES/django.po000066400000000000000000000013071351315731100251170ustar00rootroot00000000000000# 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 "" #: admin.py:403 msgid "Contents" msgstr "" django-polymorphic-2.1.2/polymorphic/locale/es/000077500000000000000000000000001351315731100215345ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/locale/es/LC_MESSAGES/000077500000000000000000000000001351315731100233215ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/locale/es/LC_MESSAGES/django.po000066400000000000000000000013621351315731100251250ustar00rootroot00000000000000# 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-2.1.2/polymorphic/locale/fr/000077500000000000000000000000001351315731100215345ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/locale/fr/LC_MESSAGES/000077500000000000000000000000001351315731100233215ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/locale/fr/LC_MESSAGES/django.po000066400000000000000000000015711351315731100251270ustar00rootroot00000000000000# 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-2.1.2/polymorphic/managers.py000066400000000000000000000032471351315731100220430ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ The manager class for use in the models. """ from __future__ import unicode_literals from django.db import models from polymorphic.compat import python_2_unicode_compatible from polymorphic.query import PolymorphicQuerySet __all__ = ("PolymorphicManager", "PolymorphicQuerySet") @python_2_unicode_compatible 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. """ queryset_class = PolymorphicQuerySet @classmethod def from_queryset(cls, queryset_class, class_name=None): manager = super(PolymorphicManager, cls).from_queryset( queryset_class, class_name=class_name ) # also set our version, Django uses _queryset_class manager.queryset_class = queryset_class return manager def get_queryset(self): qs = self.queryset_class(self.model, using=self._db, hints=self._hints) if self.model._meta.proxy: qs = qs.instance_of(self.model) return qs def __str__(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-2.1.2/polymorphic/models.py000066400000000000000000000264631351315731100215360ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Seamless Polymorphic Inheritance for Django Models """ from __future__ import absolute_import from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models.fields.related import ( ForwardManyToOneDescriptor, ReverseOneToOneDescriptor, ) from django.db.utils import DEFAULT_DB_ALIAS from polymorphic.compat import with_metaclass from .base import PolymorphicModelBase from .managers import PolymorphicManager from .query_translate import translate_polymorphic_Q_object ################################################################################### # PolymorphicModel class PolymorphicTypeUndefined(LookupError): pass class PolymorphicTypeInvalid(RuntimeError): pass class PolymorphicModel(with_metaclass(PolymorphicModelBase, models.Model)): """ Abstract base class that provides polymorphic behaviour for any model directly or indirectly derived from it. PolymorphicModel declares one field for internal use (:attr:`polymorphic_ctype`) and provides a polymorphic manager as the default manager (and as 'objects'). """ # 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 # avoid ContentType related field accessor clash (an error emitted by model validation) #: The model field that stores the :class:`~django.contrib.contenttypes.models.ContentType` reference to the actual class. polymorphic_ctype = models.ForeignKey( ContentType, null=True, editable=False, on_delete=models.CASCADE, 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() class Meta: abstract = True base_manager_name = "objects" @classmethod def translate_polymorphic_Q_object(cls, q): return translate_polymorphic_Q_object(cls, q) def pre_save_polymorphic(self, using=DEFAULT_DB_ALIAS): """ Make sure the ``polymorphic_ctype`` value is correctly set on this model. """ # 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.db_manager( using ).get_for_model(self, for_concrete_model=False) pre_save_polymorphic.alters_data = True def save(self, *args, **kwargs): """Calls :meth:`pre_save_polymorphic` and saves the model.""" using = kwargs.get("using", self._state.db or DEFAULT_DB_ALIAS) self.pre_save_polymorphic(using=using) return super(PolymorphicModel, self).save(*args, **kwargs) save.alters_data = True def get_real_instance_class(self): """ Return the actual model type of the object. 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. """ if self.polymorphic_ctype_id is None: raise PolymorphicTypeUndefined( ( "The model {}#{} does not have a `polymorphic_ctype_id` value defined.\n" "If you created models outside polymorphic, e.g. through an import or migration, " "make sure the `polymorphic_ctype_id` field points to the ContentType ID of the model subclass." ).format(self.__class__.__name__, self.pk) ) # 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. model = ( ContentType.objects.db_manager(self._state.db) .get_for_id(self.polymorphic_ctype_id) .model_class() ) # 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 ( self.__class__._meta.proxy_for_model is None or not issubclass(model, self.__class__._meta.proxy_for_model) ) ): raise PolymorphicTypeInvalid( "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.db_manager(self._state.db) .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.db_manager(self._state.db) .get_for_model(model_class, for_concrete_model=True) .model_class() ) def get_real_instance(self): """ Upcast an object to it's actual type. 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. .. note:: Each method call executes one db query (if necessary). Use the :meth:`~polymorphic.managers.PolymorphicQuerySet.get_real_instances` to upcast a complete list in a single efficient query. """ real_model = self.get_real_instance_class() if real_model == self.__class__: return self return real_model.objects.db_manager(self._state.db).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() ) for name, model in subclasses_and_superclasses_accessors.items(): # Here be dragons. orig_accessor = getattr(self.__class__, name, None) if issubclass( type(orig_accessor), (ReverseOneToOneDescriptor, ForwardManyToOneDescriptor), ): 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, the field on model can have # a different name to super_cls._meta.module_name, when the field # is created manually using 'parent_link' field_name = field_to_super.name add_model_if_regular(super_cls, field_name, result) add_all_super_models(super_cls, result) def add_all_sub_models(super_cls, result): # go through all subclasses of model for sub_cls in super_cls.__subclasses__(): # super_cls may not be in sub_cls._meta.parents if super_cls is a proxy model if super_cls in sub_cls._meta.parents: # get the field that links sub_cls to super_cls field_to_super = sub_cls._meta.parents[super_cls] # if filed_to_super is not a link to a proxy model if field_to_super is not None: super_to_sub_related_field = field_to_super.remote_field 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-2.1.2/polymorphic/query.py000066400000000000000000000541721351315731100214160ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ QuerySet for PolymorphicModel """ from __future__ import absolute_import import copy from collections import defaultdict from django.contrib.contenttypes.models import ContentType from django.db.models import FieldDoesNotExist from django.db.models.query import ModelIterable, Q, QuerySet from . import compat from .query_translate import ( translate_polymorphic_field_path, translate_polymorphic_filter_definitions_in_args, translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_Q_object, ) # chunk-size: maximum number of objects requested per db-request # by the polymorphic queryset.iterator() implementation Polymorphic_QuerySet_objects_per_request = 100 class PolymorphicModelIterable(ModelIterable): """ ModelIterable for PolymorphicModel Yields real instances if qs.polymorphic_disabled is False, otherwise acts like a regular ModelIterable. """ def __iter__(self): base_iter = super(PolymorphicModelIterable, self).__iter__() if self.queryset.polymorphic_disabled: return base_iter return self._polymorphic_iterator(base_iter) def _polymorphic_iterator(self, base_iter): """ Here we do the same as:: real_results = queryset._get_real_instances(list(base_iter)) 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 """ while True: base_result_objects = [] reached_end = False # Make sure the base iterator is read in chunks instead of # reading it completely, in case our caller read only a few objects. 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.queryset._get_real_instances(base_result_objects) for o in real_results: yield o if reached_end: return def transmogrify(cls, obj): """ Upcast a class to a different type without asking questions. """ if "__init__" not 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 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): super(PolymorphicQuerySet, self).__init__(*args, **kwargs) self._iterable_class = PolymorphicModelIterable self.polymorphic_disabled = False # A parallel structure to django.db.models.query.Query.deferred_loading, # which we maintain with the untranslated field names passed to # .defer() and .only() in order to be able to retranslate them when # retrieving the real instance (so that the deferred fields apply # to that queryset as well). self.polymorphic_deferred_loading = (set([]), True) 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 new.polymorphic_deferred_loading = ( copy.copy(self.polymorphic_deferred_loading[0]), self.polymorphic_deferred_loading[1], ) return new def as_manager(cls): 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 bulk_create(self, objs, batch_size=None): objs = list(objs) for obj in objs: obj.pre_save_polymorphic() return super(PolymorphicQuerySet, self).bulk_create(objs, batch_size) 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 if issubclass(qs._iterable_class, PolymorphicModelIterable): qs._iterable_class = ModelIterable 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. q_objects = translate_polymorphic_filter_definitions_in_args( self.model, args, using=self.db ) # filter_field='data' additional_args = translate_polymorphic_filter_definitions_in_kwargs( self.model, kwargs, using=self.db ) return super(PolymorphicQuerySet, self)._filter_or_exclude( negate, *(list(q_objects) + additional_args), **kwargs ) def order_by(self, *field_names): """translate the field paths in the args, then call vanilla order_by.""" field_names = [ translate_polymorphic_field_path(self.model, a) if isinstance(a, compat.string_types) else a # allow expressions to pass unchanged for a in field_names ] return super(PolymorphicQuerySet, self).order_by(*field_names) def defer(self, *fields): """ Translate the field paths in the args, then call vanilla defer. Also retain a copy of the original fields passed, which we'll need when we're retrieving the real instance (since we'll need to translate them again, as the model will have changed). """ new_fields = [translate_polymorphic_field_path(self.model, a) for a in fields] clone = super(PolymorphicQuerySet, self).defer(*new_fields) clone._polymorphic_add_deferred_loading(fields) return clone def only(self, *fields): """ Translate the field paths in the args, then call vanilla only. Also retain a copy of the original fields passed, which we'll need when we're retrieving the real instance (since we'll need to translate them again, as the model will have changed). """ new_fields = [translate_polymorphic_field_path(self.model, a) for a in fields] clone = super(PolymorphicQuerySet, self).only(*new_fields) clone._polymorphic_add_immediate_loading(fields) return clone def _polymorphic_add_deferred_loading(self, field_names): """ Follows the logic of django.db.models.query.Query.add_deferred_loading(), but for the non-translated field names that were passed to self.defer(). """ existing, defer = self.polymorphic_deferred_loading if defer: # Add to existing deferred names. self.polymorphic_deferred_loading = existing.union(field_names), True else: # Remove names from the set of any existing "immediate load" names. self.polymorphic_deferred_loading = existing.difference(field_names), False def _polymorphic_add_immediate_loading(self, field_names): """ Follows the logic of django.db.models.query.Query.add_immediate_loading(), but for the non-translated field names that were passed to self.only() """ existing, defer = self.polymorphic_deferred_loading field_names = set(field_names) if "pk" in field_names: field_names.remove("pk") field_names.add(self.model._meta.pk.name) if defer: # Remove any existing deferred names from the current set before # setting the new names. self.polymorphic_deferred_loading = field_names.difference(existing), False else: # Replace any existing "immediate load" field names. self.polymorphic_deferred_loading = field_names, False 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)""" ___lookup_assert_msg = "PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only" def patch_lookup(a): # The field on which the aggregate operates is # stored inside a complex query expression. if isinstance(a, Q): translate_polymorphic_Q_object(self.model, a) elif hasattr(a, "get_source_expressions"): for source_expression in a.get_source_expressions(): if source_expression is not None: patch_lookup(source_expression) else: a.name = translate_polymorphic_field_path(self.model, a.name) def test___lookup(a): """ *args might be complex expressions too in django 1.8 so the testing for a '___' is rather complex on this one """ if isinstance(a, Q): def tree_node_test___lookup(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 ) assert "___" not in child[0], ___lookup_assert_msg else: # this Q object child is another Q object, recursively process this as well tree_node_test___lookup(my_model, child) tree_node_test___lookup(self.model, a) elif hasattr(a, "get_source_expressions"): for source_expression in a.get_source_expressions(): test___lookup(source_expression) else: assert "___" not in a.name, ___lookup_assert_msg for a in args: test___lookup(a) for a in kwargs.values(): 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) # 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. """ resultlist = [] # polymorphic list of result-objects # 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) indexlist_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; # - store objects that already have the correct class into "results" content_type_manager = ContentType.objects.db_manager(self.db) self_model_class_id = content_type_manager.get_for_model( self.model, for_concrete_model=False ).pk self_concrete_model_class_id = content_type_manager.get_for_model( self.model, for_concrete_model=True ).pk for i, base_object in enumerate(base_result_objects): if base_object.polymorphic_ctype_id == self_model_class_id: # Real class is exactly the same as base class, go straight to results resultlist.append(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 resultlist.append(transmogrify(real_concrete_class, base_object)) else: # This model has a concrete derived class, track it for bulk retrieval. real_concrete_class = content_type_manager.get_for_id( real_concrete_class_id ).model_class() idlist_per_model[real_concrete_class].append( getattr(base_object, pk_name) ) indexlist_per_model[real_concrete_class].append( (i, len(resultlist)) ) resultlist.append(None) # 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(): indices = indexlist_per_model[real_concrete_class] real_objects = real_concrete_class._base_objects.db_manager(self.db).filter( **{("%s__in" % pk_name): idlist} ) # copy select related configuration to new qs real_objects.query.select_related = self.query.select_related # Copy deferred fields configuration to the new queryset deferred_loading_fields = [] existing_fields = self.polymorphic_deferred_loading[0] for field in existing_fields: try: translated_field_name = translate_polymorphic_field_path( real_concrete_class, field ) except AssertionError: if "___" in field: # The originally passed argument to .defer() or .only() # was in the form Model2B___field2, where Model2B is # now a superclass of real_concrete_class. Thus it's # sufficient to just use the field name. translated_field_name = field.rpartition("___")[-1] # Check if the field does exist. # Ignore deferred fields that don't exist in this subclass type. try: real_concrete_class._meta.get_field(translated_field_name) except FieldDoesNotExist: continue else: raise deferred_loading_fields.append(translated_field_name) real_objects.query.deferred_loading = ( set(deferred_loading_fields), self.query.deferred_loading[1], ) real_objects_dict = { getattr(real_object, pk_name): real_object for real_object in real_objects } for i, j in indices: base_object = base_result_objects[i] o_pk = getattr(base_object, pk_name) real_object = real_objects_dict.get(o_pk) if real_object is None: continue # need shallow copy to avoid duplication in caches (see PR #353) real_object = copy.copy(real_object) 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 self.query.annotations: for anno_field_name in self.query.annotations.keys(): attr = getattr(base_object, anno_field_name) setattr(real_object, anno_field_name, attr) if self.query.extra_select: for select_field_name in self.query.extra_select.keys(): attr = getattr(base_object, select_field_name) setattr(real_object, select_field_name, attr) resultlist[j] = real_object resultlist = [i for i in resultlist if i] # set polymorphic_annotate_names in all objects (currently just used for debugging/printing) if self.query.annotations: # get annotate field list annotate_names = list(self.query.annotations.keys()) 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: # get extra select field list extra_select_names = list(self.query.extra_select.keys()) for real_object in resultlist: real_object.polymorphic_extra_select_names = extra_select_names return resultlist 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): """ Cast a list of objects to their actual classes. This does roughly the same as:: return [ o.get_real_instance() for o in base_result_objects ] but more efficiently. :rtype: PolymorphicQuerySet """ "same as _get_real_instances, but make sure that __repr__ for ShowField... creates correct output" if base_result_objects is None: 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-2.1.2/polymorphic/query_translate.py000066400000000000000000000261451351315731100234720ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ PolymorphicQuerySet support functions """ from __future__ import absolute_import import copy from collections import deque from django.apps import apps from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError from django.db import models from django.db.models import Q from django.db.models.fields.related import ForeignObjectRel, RelatedField from django.db.utils import DEFAULT_DB_ALIAS # 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. from polymorphic import compat ################################################################################### # PolymorphicQuerySet support functions def translate_polymorphic_filter_definitions_in_kwargs( queryset_model, kwargs, using=DEFAULT_DB_ALIAS ): """ 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, using=using ) 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, using=DEFAULT_DB_ALIAS ): 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, using=using ) 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, using=DEFAULT_DB_ALIAS ): """ Translate the non-keyword argument list for PolymorphicQuerySet.filter() In the args list, we return 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). Returns: modified Q objects """ return [ translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using) for q in args ] def _translate_polymorphic_filter_definition( queryset_model, field_path, field_val, using=DEFAULT_DB_ALIAS ): """ 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_instanceof_q(field_val, using=using) elif field_path == "not_instance_of": return create_instanceof_q(field_val, not_instance_of=True, using=using) 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) """ if not isinstance(field_path, compat.string_types): raise ValueError("Expected field name as string: {0}".format(field_path)) 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 = apps.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: # This also retreives M2M relations now (including reverse foreign key relations) field = queryset_model._meta.get_field(classname) if isinstance(field, (RelatedField, ForeignObjectRel)): # 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 models.FieldDoesNotExist: pass submodels = _get_all_sub_models(queryset_model) model = submodels.get(classname, None) assert model, "PolymorphicModel: model %s not found (not a subclass of %s)!" % ( classname, queryset_model.__name__, ) basepath = _create_base_path(queryset_model, model) if negated: newpath = "-" else: newpath = "" newpath += basepath if basepath: newpath += "__" newpath += pure_field_path return newpath def _get_all_sub_models(base_model): """#Collect all sub-models, this should be optimized (cached)""" result = {} queue = deque([base_model]) while queue: model = queue.popleft() 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__]: raise FieldError( "PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s match!\n" "In this case, please use the syntax: applabel__ModelName___field" % ( model._meta.app_label, model.__name__, result[model.__name__]._meta.app_label, result[model.__name__].__name__, ) ) result[model.__name__] = model queue.extend(model.__subclasses__()) return result def _create_base_path(baseclass, myclass): # create new field path for expressions, e.g. for baseclass=ModelA, myclass=ModelC # 'modelb__modelc" is returned for b in myclass.__bases__: if b == baseclass: return _get_query_related_name(myclass) path = _create_base_path(baseclass, b) if path: if b._meta.abstract or b._meta.proxy: return _get_query_related_name(myclass) else: return path + "__" + _get_query_related_name(myclass) return "" def _get_query_related_name(myclass): for f in myclass._meta.local_fields: if isinstance(f, models.OneToOneField) and f.remote_field.parent_link: return f.related_query_name() # Fallback to undetected name, # this happens on proxy models (e.g. SubclassSelectorProxyModel) return myclass.__name__.lower() def create_instanceof_q(modellist, not_instance_of=False, using=DEFAULT_DB_ALIAS): """ 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 if not isinstance(modellist, (list, tuple)): from .models import PolymorphicModel if issubclass(modellist, PolymorphicModel): modellist = [modellist] else: raise TypeError( "PolymorphicModel: instance_of expects a list of (polymorphic) " "models or a single (polymorphic) model" ) contenttype_ids = _get_mro_content_type_ids(modellist, using) q = Q(polymorphic_ctype__in=sorted(contenttype_ids)) if not_instance_of: q = ~q return q def _get_mro_content_type_ids(models, using): contenttype_ids = set() for model in models: ct = ContentType.objects.db_manager(using).get_for_model( model, for_concrete_model=False ) contenttype_ids.add(ct.pk) subclasses = model.__subclasses__() if subclasses: contenttype_ids.update(_get_mro_content_type_ids(subclasses, using)) return contenttype_ids django-polymorphic-2.1.2/polymorphic/showfields.py000066400000000000000000000144071351315731100224150ustar00rootroot00000000000000# -*- coding: utf-8 -*- import re from django.db import models from . import compat from .compat import python_2_unicode_compatible RE_DEFERRED = re.compile("_Deferred_.*") @python_2_unicode_compatible class ShowFieldBase(object): """ base class for the ShowField... model mixins, does the work """ # cause nicer multiline PolymorphicQuery output polymorphic_query_multiline_output = True polymorphic_showfield_type = False polymorphic_showfield_content = False polymorphic_showfield_deferred = 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.__str__() 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, compat.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 __str__(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, RE_DEFERRED.sub("", 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 ) if self.polymorphic_showfield_deferred: fields = self.get_deferred_fields() if fields: parts.append( (False, "deferred[{0}]".format(",".join(sorted(fields))), "") ) # 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 is not 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 django-polymorphic-2.1.2/polymorphic/static/000077500000000000000000000000001351315731100211555ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/static/polymorphic/000077500000000000000000000000001351315731100235225ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/static/polymorphic/css/000077500000000000000000000000001351315731100243125ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/static/polymorphic/css/polymorphic_inlines.css000066400000000000000000000010601351315731100311070ustar00rootroot00000000000000.polymorphic-add-choice { position: relative; clear: left; } .polymorphic-add-choice a:focus { text-decoration: none; } .polymorphic-type-menu { position: absolute; top: 2.2em; left: 0.5em; border: 1px solid #ccc; border-radius: 4px; padding: 2px; background-color: #fff; z-index: 1000; } .polymorphic-type-menu ul { padding: 2px; margin: 0; } .polymorphic-type-menu li { list-style: none inside none; padding: 4px 8px; } .inline-related.empty-form { /* needed for grapelli, which uses grp-empty-form */ display: none; } django-polymorphic-2.1.2/polymorphic/static/polymorphic/js/000077500000000000000000000000001351315731100241365ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/static/polymorphic/js/polymorphic_inlines.js000066400000000000000000000362411351315731100305700ustar00rootroot00000000000000/*global DateTimeShortcuts, SelectFilter*/ // This is a slightly adapted version of Django's inlines.js // Forked for polymorphic by Diederik van der Boor /** * Django admin inlines * * Based on jQuery Formset 1.1 * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com) * @requires jQuery 1.2.6 or later * * Copyright (c) 2009, Stanislaus Madueke * All rights reserved. * * Spiced up with Code from Zain Memon's GSoC project 2009 * and modified for Django by Jannis Leidel, Travis Swicegood and Julien Phalip. * * Licensed under the New BSD License * See: http://www.opensource.org/licenses/bsd-license.php */ (function($) { 'use strict'; $.fn.polymorphicFormset = function(opts) { var options = $.extend({}, $.fn.polymorphicFormset.defaults, opts); var $this = $(this); var $parent = $this.parent(); var updateElementIndex = function(el, prefix, ndx) { var id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))"); var replacement = prefix + "-" + ndx; if ($(el).prop("for")) { $(el).prop("for", $(el).prop("for").replace(id_regex, replacement)); } if (el.id) { el.id = el.id.replace(id_regex, replacement); } if (el.name) { el.name = el.name.replace(id_regex, replacement); } }; var totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off"); var nextIndex = parseInt(totalForms.val(), 10); var maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off"); // only show the add button if we are allowed to add more items, // note that max_num = None translates to a blank string. var showAddButton = maxForms.val() === '' || (maxForms.val() - totalForms.val()) > 0; $this.each(function(i) { $(this).not("." + options.emptyCssClass).addClass(options.formCssClass); }); if ($this.length && showAddButton) { var addContainer; var menuButton; var addButtons; if(options.childTypes == null) { throw Error("The polymorphic fieldset options.childTypes is not defined!"); } // For Polymorphic inlines, the add button opens a menu. var menu = ''; if ($this.prop("tagName") === "TR") { // If forms are laid out as table rows, insert the // "add" button in a new table row: var numCols = this.eq(-1).children().length; $parent.append('' + options.addText + "" + menu + ""); addContainer = $parent.find("tr:last > td"); menuButton = addContainer.children('a'); addButtons = addContainer.find("li a"); } else { // Otherwise, insert it immediately after the last form: $this.filter(":last").after('"); addContainer = $this.filter(":last").next(); menuButton = addContainer.children('a'); addButtons = addContainer.find("li a"); } menuButton.click(function(event) { event.preventDefault(); event.stopPropagation(); // for menu hide var $menu = $(event.target).next('.polymorphic-type-menu'); if(! $menu.is(':visible')) { var hideMenu = function() { $menu.slideUp(50); $(document).unbind('click', hideMenu); }; $(document).click(hideMenu); } $menu.slideToggle(50); }); addButtons.click(function(event) { event.preventDefault(); var polymorphicType = $(event.target).attr('data-type'); // Select polymorphic type. var template = $("#" + polymorphicType + "-empty"); var row = template.clone(true); row.removeClass(options.emptyCssClass) .addClass(options.formCssClass) .attr("id", options.prefix + "-" + nextIndex); if (row.is("tr")) { // If the forms are laid out in table rows, insert // the remove button into the last table cell: row.children(":last").append('"); } else if (row.is("ul") || row.is("ol")) { // If they're laid out as an ordered/unordered list, // insert an
  • after the last list item: row.append('
  • ' + options.deleteText + "
  • "); } else { // Otherwise, just insert the remove button as the // last child element of the form's container: row.children(":first").append('' + options.deleteText + ""); } row.find("*").each(function() { updateElementIndex(this, options.prefix, totalForms.val()); }); // Insert the new form when it has been fully edited row.insertBefore($(template)); // Update number of total forms $(totalForms).val(parseInt(totalForms.val(), 10) + 1); nextIndex += 1; // Hide add button in case we've hit the max, except we want to add infinitely if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) { addContainer.hide(); } // The delete button of each row triggers a bunch of other things row.find("a." + options.deleteCssClass).click(function(e1) { e1.preventDefault(); // Remove the parent form containing this button: row.remove(); nextIndex -= 1; // If a post-delete callback was provided, call it with the deleted form: if (options.removed) { options.removed(row); } $(document).trigger('formset:removed', [row, options.prefix]); // Update the TOTAL_FORMS form count. var forms = $("." + options.formCssClass); $("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length); // Show add button again once we drop below max if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) { addContainer.show(); } // Also, update names and ids for all remaining form controls // so they remain in sequence: var i, formCount; var updateElementCallback = function() { updateElementIndex(this, options.prefix, i); }; for (i = 0, formCount = forms.length; i < formCount; i++) { updateElementIndex($(forms).get(i), options.prefix, i); $(forms.get(i)).find("*").each(updateElementCallback); } }); // If a post-add callback was supplied, call it with the added form: if (options.added) { options.added(row); } $(document).trigger('formset:added', [row, options.prefix]); }); } return this; }; /* Setup plugin defaults */ $.fn.polymorphicFormset.defaults = { prefix: "form", // The form prefix for your django formset addText: "add another", // Text for the add link childTypes: null, // defined by the client. deleteText: "remove", // Text for the delete link addCssClass: "add-row", // CSS class applied to the add link deleteCssClass: "delete-row", // CSS class applied to the delete link emptyCssClass: "empty-row", // CSS class applied to the empty row formCssClass: "dynamic-form", // CSS class applied to each form in a formset added: null, // Function called each time a new form is added removed: null, // Function called each time a form is deleted addButton: null // Existing add button to use }; // Tabular inlines --------------------------------------------------------- $.fn.tabularPolymorphicFormset = function(options) { var $rows = $(this); var alternatingRows = function(row) { $($rows.selector).not(".add-row").removeClass("row1 row2") .filter(":even").addClass("row1").end() .filter(":odd").addClass("row2"); }; var reinitDateTimeShortCuts = function() { // Reinitialize the calendar and clock widgets by force if (typeof DateTimeShortcuts !== "undefined") { $(".datetimeshortcuts").remove(); DateTimeShortcuts.init(); } }; var updateSelectFilter = function() { // If any SelectFilter widgets are a part of the new form, // instantiate a new SelectFilter instance for it. if (typeof SelectFilter !== 'undefined') { $('.selectfilter').each(function(index, value) { var namearr = value.name.split('-'); SelectFilter.init(value.id, namearr[namearr.length - 1], false); }); $('.selectfilterstacked').each(function(index, value) { var namearr = value.name.split('-'); SelectFilter.init(value.id, namearr[namearr.length - 1], true); }); } }; var initPrepopulatedFields = function(row) { row.find('.prepopulated_field').each(function() { var field = $(this), input = field.find('input, select, textarea'), dependency_list = input.data('dependency_list') || [], dependencies = []; $.each(dependency_list, function(i, field_name) { dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id')); }); if (dependencies.length) { input.prepopulate(dependencies, input.attr('maxlength')); } }); }; $rows.polymorphicFormset({ prefix: options.prefix, addText: options.addText, childTypes: options.childTypes, formCssClass: "dynamic-" + options.prefix, deleteCssClass: "inline-deletelink", deleteText: options.deleteText, emptyCssClass: "empty-form", removed: alternatingRows, added: function(row) { initPrepopulatedFields(row); reinitDateTimeShortCuts(); updateSelectFilter(); alternatingRows(row); }, addButton: options.addButton }); return $rows; }; // Stacked inlines --------------------------------------------------------- $.fn.stackedPolymorphicFormset = function(options) { var $rows = $(this); var updateInlineLabel = function(row) { $($rows.selector).find(".inline_label").each(function(i) { var count = i + 1; $(this).html($(this).html().replace(/(#\d+)/g, "#" + count)); }); }; var reinitDateTimeShortCuts = function() { // Reinitialize the calendar and clock widgets by force, yuck. if (typeof DateTimeShortcuts !== "undefined") { $(".datetimeshortcuts").remove(); DateTimeShortcuts.init(); } }; var updateSelectFilter = function() { // If any SelectFilter widgets were added, instantiate a new instance. if (typeof SelectFilter !== "undefined") { $(".selectfilter").each(function(index, value) { var namearr = value.name.split('-'); SelectFilter.init(value.id, namearr[namearr.length - 1], false); }); $(".selectfilterstacked").each(function(index, value) { var namearr = value.name.split('-'); SelectFilter.init(value.id, namearr[namearr.length - 1], true); }); } }; var initPrepopulatedFields = function(row) { row.find('.prepopulated_field').each(function() { var field = $(this), input = field.find('input, select, textarea'), dependency_list = input.data('dependency_list') || [], dependencies = []; $.each(dependency_list, function(i, field_name) { dependencies.push('#' + row.find('.form-row .field-' + field_name).find('input, select, textarea').attr('id')); }); if (dependencies.length) { input.prepopulate(dependencies, input.attr('maxlength')); } }); }; $rows.polymorphicFormset({ prefix: options.prefix, addText: options.addText, childTypes: options.childTypes, formCssClass: "dynamic-" + options.prefix, deleteCssClass: "inline-deletelink", deleteText: options.deleteText, emptyCssClass: "empty-form", removed: updateInlineLabel, added: function(row) { initPrepopulatedFields(row); reinitDateTimeShortCuts(); updateSelectFilter(); updateInlineLabel(row); }, addButton: options.addButton }); return $rows; }; $(document).ready(function() { $(".js-inline-polymorphic-admin-formset").each(function() { var data = $(this).data(), inlineOptions = data.inlineFormset; switch(data.inlineType) { case "stacked": $(inlineOptions.name + "-group .inline-related").stackedPolymorphicFormset(inlineOptions.options); break; case "tabular": $(inlineOptions.name + "-group .tabular.inline-related tbody tr").tabularPolymorphicFormset(inlineOptions.options); break; } }); }); })(django.jQuery); django-polymorphic-2.1.2/polymorphic/templates/000077500000000000000000000000001351315731100216645ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/templates/admin/000077500000000000000000000000001351315731100227545ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/templates/admin/polymorphic/000077500000000000000000000000001351315731100253215ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/templates/admin/polymorphic/add_type_form.html000066400000000000000000000004431351315731100310240ustar00rootroot00000000000000{% 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-2.1.2/polymorphic/templates/admin/polymorphic/change_form.html000066400000000000000000000002761351315731100304640ustar00rootroot00000000000000{% extends "admin/change_form.html" %} {% load polymorphic_admin_tags %} {% block breadcrumbs %} {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} django-polymorphic-2.1.2/polymorphic/templates/admin/polymorphic/delete_confirmation.html000066400000000000000000000003061351315731100322200ustar00rootroot00000000000000{% extends "admin/delete_confirmation.html" %} {% load polymorphic_admin_tags %} {% block breadcrumbs %} {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} django-polymorphic-2.1.2/polymorphic/templates/admin/polymorphic/edit_inline/000077500000000000000000000000001351315731100276045ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/templates/admin/polymorphic/edit_inline/stacked.html000066400000000000000000000045131351315731100321130ustar00rootroot00000000000000{% load i18n admin_urls static %}

    {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

    {{ inline_admin_formset.formset.management_form }} {{ inline_admin_formset.formset.non_form_errors }} {% for inline_admin_form in inline_admin_formset %} {% endfor %}
    django-polymorphic-2.1.2/polymorphic/templates/admin/polymorphic/object_history.html000066400000000000000000000003011351315731100312300ustar00rootroot00000000000000{% extends "admin/object_history.html" %} {% load polymorphic_admin_tags %} {% block breadcrumbs %} {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} django-polymorphic-2.1.2/polymorphic/templatetags/000077500000000000000000000000001351315731100223605ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/templatetags/__init__.py000066400000000000000000000055001351315731100244710ustar00rootroot00000000000000""" Template tags for polymorphic The ``polymorphic_formset_tags`` Library ---------------------------------------- .. versionadded:: 1.1 To render formsets in the frontend, the ``polymorphic_tags`` provides extra filters to implement HTML rendering of polymorphic formsets. The following filters are provided; * ``{{ formset|as_script_options }}`` render the ``data-options`` for a JavaScript formset library. * ``{{ formset|include_empty_form }}`` provide the placeholder form for an add button. * ``{{ form|as_form_type }}`` return the model name that the form instance uses. * ``{{ model|as_model_name }}`` performs the same, for a model class or instance. .. code-block:: html+django {% load i18n polymorphic_formset_tags %}
    {% block add_button %} {% if formset.show_add_button|default_if_none:'1' %} {% if formset.empty_forms %} {# django-polymorphic formset (e.g. PolymorphicInlineFormSetView) #}
    {% for model in formset.child_forms %} {% glyphicon 'plus' %} {{ model|as_verbose_name }} {% endfor %}
    {% else %} {% trans "Add" %} {% endif %} {% endif %} {% endblock %} {{ formset.management_form }} {% for form in formset|include_empty_form %} {% block formset_form_wrapper %}
    {{ form.non_field_errors }} {# Add the 'pk' field that is not mentioned in crispy #} {% for field in form.hidden_fields %} {{ field }} {% endfor %} {% block formset_form %} {% crispy form %} {% endblock %}
    {% endblock %} {% endfor %}
    The ``polymorphic_admin_tags`` Library -------------------------------------- The ``{% breadcrumb_scope ... %}`` tag makes sure the ``{{ opts }}`` and ``{{ app_label }}`` values are temporary based on the provided ``{{ base_opts }}``. This allows fixing the breadcrumb in admin templates: .. code-block:: html+django {% extends "admin/change_form.html" %} {% load polymorphic_admin_tags %} {% block breadcrumbs %} {% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %} {% endblock %} """ django-polymorphic-2.1.2/polymorphic/templatetags/polymorphic_admin_tags.py000066400000000000000000000033771351315731100274770ustar00rootroot00000000000000from django.template import Library, Node, TemplateSyntaxError from polymorphic import compat 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, compat.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-2.1.2/polymorphic/templatetags/polymorphic_formset_tags.py000066400000000000000000000041331351315731100300550ustar00rootroot00000000000000import json from django.template import Library from django.utils.encoding import force_text from django.utils.text import capfirst from django.utils.translation import ugettext from polymorphic.formsets import BasePolymorphicModelFormSet register = Library() @register.filter() def include_empty_form(formset): """ Make sure the "empty form" is included when displaying a formset (typically table with input rows) """ for form in formset: yield form if hasattr(formset, "empty_forms"): # BasePolymorphicModelFormSet for form in formset.empty_forms: yield form else: # Standard Django formset yield formset.empty_form @register.filter def as_script_options(formset): """ A JavaScript data structure for the JavaScript code This generates the ``data-options`` attribute for ``jquery.django-inlines.js`` The formset may define the following extra attributes: - ``verbose_name`` - ``add_text`` - ``show_add_button`` """ verbose_name = getattr(formset, "verbose_name", formset.model._meta.verbose_name) options = { "prefix": formset.prefix, "pkFieldName": formset.model._meta.pk.name, "addText": getattr(formset, "add_text", None) or ugettext("Add another %(verbose_name)s") % {"verbose_name": capfirst(verbose_name)}, "showAddButton": getattr(formset, "show_add_button", True), "deleteText": ugettext("Delete"), } if isinstance(formset, BasePolymorphicModelFormSet): # Allow to add different types options["childTypes"] = [ { "name": force_text(model._meta.verbose_name), "type": model._meta.model_name, } for model in formset.child_forms.keys() ] return json.dumps(options) @register.filter def as_form_type(form): """ Usage: ``{{ form|as_form_type }}`` """ return form._meta.model._meta.model_name @register.filter def as_model_name(model): """ Usage: ``{{ model|as_model_name }}`` """ return model._meta.model_name django-polymorphic-2.1.2/polymorphic/tests/000077500000000000000000000000001351315731100210305ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/tests/__init__.py000066400000000000000000000000001351315731100231270ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/tests/admintestcase.py000066400000000000000000000213021351315731100242240ustar00rootroot00000000000000from django.conf import settings from django.conf.urls import include, url from django.contrib.admin import AdminSite from django.contrib.admin.templatetags.admin_urls import admin_urlname from django.contrib.auth.models import User from django.contrib.messages.middleware import MessageMiddleware from django.test import RequestFactory, TestCase from django.urls import clear_url_caches, reverse, set_urlconf class AdminTestCase(TestCase): """ Testing the admin site """ #: The model to test model = None #: The admin class to test admin_class = None @classmethod def setUpClass(cls): super(AdminTestCase, cls).setUpClass() cls.admin_user = User.objects.create_superuser( "admin", "admin@example.org", password="admin" ) def setUp(self): super(AdminTestCase, self).setUp() # Have a separate site, to avoid dependency on polymorphic wrapping or standard admin configuration self.admin_site = AdminSite() if self.model is not None: self.admin_register(self.model, self.admin_class) def tearDown(self): clear_url_caches() set_urlconf(None) def register(self, model): """Decorator, like admin.register()""" def _dec(admin_class): self.admin_register(model, admin_class) return admin_class return _dec def admin_register(self, model, admin_site): """Register an model with admin to the test case, test client and URL reversing code.""" self.admin_site.register(model, admin_site) # Make sure the URLs are reachable by reverse() clear_url_caches() set_urlconf(tuple([url("^tmp-admin/", self.admin_site.urls)])) def get_admin_instance(self, model): try: return self.admin_site._registry[model] except KeyError: raise ValueError("Model not registered with admin: {}".format(model)) @classmethod def tearDownClass(cls): super(AdminTestCase, cls).tearDownClass() clear_url_caches() set_urlconf(None) def get_add_url(self, model): admin_instance = self.get_admin_instance(model) return reverse(admin_urlname(admin_instance.opts, "add")) def get_changelist_url(self, model): admin_instance = self.get_admin_instance(model) return reverse(admin_urlname(admin_instance.opts, "changelist")) def get_change_url(self, model, object_id): admin_instance = self.get_admin_instance(model) return reverse(admin_urlname(admin_instance.opts, "change"), args=(object_id,)) def get_history_url(self, model, object_id): admin_instance = self.get_admin_instance(model) return reverse(admin_urlname(admin_instance.opts, "history"), args=(object_id,)) def get_delete_url(self, model, object_id): admin_instance = self.get_admin_instance(model) return reverse(admin_urlname(admin_instance.opts, "delete"), args=(object_id,)) def admin_get_add(self, model, qs=""): """ Make a direct "add" call to the admin page, circumvening login checks. """ admin_instance = self.get_admin_instance(model) request = self.create_admin_request("get", self.get_add_url(model) + qs) response = admin_instance.add_view(request) self.assertEqual(response.status_code, 200) return response def admin_post_add(self, model, formdata, qs=""): """ Make a direct "add" call to the admin page, circumvening login checks. """ admin_instance = self.get_admin_instance(model) request = self.create_admin_request( "post", self.get_add_url(model) + qs, data=formdata ) response = admin_instance.add_view(request) self.assertFormSuccess(request.path, response) return response def admin_get_changelist(self, model): """ Make a direct "add" call to the admin page, circumvening login checks. """ admin_instance = self.get_admin_instance(model) request = self.create_admin_request("get", self.get_changelist_url(model)) response = admin_instance.changelist_view(request) self.assertEqual(response.status_code, 200) return response def admin_get_change(self, model, object_id, query=None, **extra): """ Perform a GET request on the admin page """ admin_instance = self.get_admin_instance(model) request = self.create_admin_request( "get", self.get_change_url(model, object_id), data=query, **extra ) response = admin_instance.change_view(request, str(object_id)) self.assertEqual(response.status_code, 200) return response def admin_post_change(self, model, object_id, formdata, **extra): """ Make a direct "add" call to the admin page, circumvening login checks. """ admin_instance = self.get_admin_instance(model) request = self.create_admin_request( "post", self.get_change_url(model, object_id), data=formdata, **extra ) response = admin_instance.change_view(request, str(object_id)) self.assertFormSuccess(request.path, response) return response def admin_get_history(self, model, object_id, query=None, **extra): """ Perform a GET request on the admin page """ admin_instance = self.get_admin_instance(model) request = self.create_admin_request( "get", self.get_history_url(model, object_id), data=query, **extra ) response = admin_instance.history_view(request, str(object_id)) self.assertEqual(response.status_code, 200) return response def admin_get_delete(self, model, object_id, query=None, **extra): """ Perform a GET request on the admin delete page """ admin_instance = self.get_admin_instance(model) request = self.create_admin_request( "get", self.get_delete_url(model, object_id), data=query, **extra ) response = admin_instance.delete_view(request, str(object_id)) self.assertEqual(response.status_code, 200) return response def admin_post_delete(self, model, object_id, **extra): """ Make a direct "add" call to the admin page, circumvening login checks. """ if not extra: extra = {"data": {"post": "yes"}} admin_instance = self.get_admin_instance(model) request = self.create_admin_request( "post", self.get_delete_url(model, object_id), **extra ) response = admin_instance.delete_view(request, str(object_id)) self.assertEqual( response.status_code, 302, "Form errors in calling {0}".format(request.path) ) return response def create_admin_request(self, method, url, data=None, **extra): """ Construct an Request instance for the admin view. """ factory_method = getattr(RequestFactory(), method) if data is not None: if method != "get": data["csrfmiddlewaretoken"] = "foo" dummy_request = factory_method(url, data=data) dummy_request.user = self.admin_user # Add the management form fields if needed. # base_data = self._get_management_form_data(dummy_request) # base_data.update(data) # data = base_data request = factory_method(url, data=data, **extra) request.COOKIES[settings.CSRF_COOKIE_NAME] = "foo" request.csrf_processing_done = True # Add properties which middleware would typically do request.session = {} request.user = self.admin_user MessageMiddleware().process_request(request) return request def assertFormSuccess(self, request_url, response): """ Assert that the response was a redirect, not a form error. """ self.assertIn(response.status_code, [200, 302]) if response.status_code != 302: context_data = response.context_data if "errors" in context_data: errors = response.context_data["errors"] elif "form" in context_data: errors = context_data["form"].errors else: raise KeyError("Unknown field for errors in the TemplateResponse!") self.assertEqual( response.status_code, 302, "Form errors in calling {0}:\n{1}".format( request_url, errors.as_text() ), ) self.assertTrue( "/login/?next=" not in response["Location"], "Received login response for {0}".format(request_url), ) django-polymorphic-2.1.2/polymorphic/tests/migrations/000077500000000000000000000000001351315731100232045ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/tests/migrations/0001_initial.py000066400000000000000000002201361351315731100256530ustar00rootroot00000000000000import uuid import django.db.models.deletion import django.db.models.manager from django.db import migrations, models import polymorphic.showfields class Migration(migrations.Migration): initial = True dependencies = [("contenttypes", "0002_remove_content_type_name")] operations = [ migrations.CreateModel( name="SwappableModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.swappablemodel_set+", to="contenttypes.ContentType", ), ), ], options={"swappable": "POLYMORPHIC_TEST_SWAPPABLE"}, ), migrations.CreateModel( name="Base", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field_b", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldType, models.Model), ), migrations.CreateModel( name="BlogBase", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="BlogEntry", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("text", models.CharField(max_length=10)), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.blogentry_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="BlogEntry_limit_choices_to", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("text", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="ChildModelWithManager", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="CustomPkBase", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("b", models.CharField(max_length=1)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="DateModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("date", models.DateTimeField()), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.datemodel_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="Enhance_Base", fields=[ ("base_id", models.AutoField(primary_key=True, serialize=False)), ("field_b", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="Enhance_Plain", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field_p", models.CharField(max_length=10)), ], ), migrations.CreateModel( name="InitTestModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("bar", models.CharField(max_length=100)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldType, models.Model), ), migrations.CreateModel( name="MgrInheritA", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ], managers=[("mgrA", django.db.models.manager.Manager())], ), 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, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldType, models.Model), ), migrations.CreateModel( name="ModelExtraA", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="ModelExtraExternal", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("topic", models.CharField(max_length=10)), ], ), migrations.CreateModel( name="ModelFieldNameTest", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("modelfieldnametest", models.CharField(max_length=10)), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelfieldnametest_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldType, models.Model), ), migrations.CreateModel( name="ModelShow1", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ( "m2m", models.ManyToManyField( related_name="_modelshow1_m2m_+", to="tests.ModelShow1" ), ), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelshow1_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldType, models.Model), ), migrations.CreateModel( name="ModelShow1_plain", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="ModelShow2", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ( "m2m", models.ManyToManyField( related_name="_modelshow2_m2m_+", to="tests.ModelShow2" ), ), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelshow2_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldContent, models.Model), ), migrations.CreateModel( name="ModelShow3", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ( "m2m", models.ManyToManyField( related_name="_modelshow3_m2m_+", to="tests.ModelShow3" ), ), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelshow3_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="ModelUnderRelChild", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("_private2", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="ModelUnderRelParent", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ("_private", models.CharField(max_length=10)), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelunderrelparent_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="MROBase1", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldType, models.Model), ), migrations.CreateModel( name="MROBase3", fields=[("base_3_id", models.AutoField(primary_key=True, serialize=False))], ), migrations.CreateModel( name="One2OneRelatingModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="ParentModelWithManager", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.parentmodelwithmanager_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="PlainA", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ], ), migrations.CreateModel( name="PlainChildModelWithManager", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="PlainParentModelWithManager", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="ProxiedBase", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=10)), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.proxiedbase_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="ProxyBase", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("some_data", models.CharField(max_length=128)), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="RelatedNameClash", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to="contenttypes.ContentType", ), ), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.relatednameclash_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldType, models.Model), ), migrations.CreateModel( name="RelatingModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], ), migrations.CreateModel( name="RelationBase", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field_base", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="SwappedModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.swappedmodel_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="Top", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=50)), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="UUIDPlainA", fields=[ ( "uuid_primary_key", models.UUIDField( default=uuid.uuid1, primary_key=True, serialize=False ), ), ("field1", models.CharField(max_length=10)), ], ), migrations.CreateModel( name="UUIDProject", fields=[ ( "uuid_primary_key", models.UUIDField( default=uuid.uuid1, primary_key=True, serialize=False ), ), ("topic", models.CharField(max_length=30)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, models.Model), ), migrations.CreateModel( name="BlogA", fields=[ ( "blogbase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.BlogBase", ), ), ("info", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.blogbase",), ), migrations.CreateModel( name="BlogB", fields=[ ( "blogbase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.BlogBase", ), ) ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.blogbase",), ), migrations.CreateModel( name="CustomPkInherit", fields=[ ( "custompkbase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to="tests.CustomPkBase", ), ), ("custom_id", models.AutoField(primary_key=True, serialize=False)), ("i", models.CharField(max_length=1)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.custompkbase",), ), migrations.CreateModel( name="Enhance_Inherit", fields=[ ( "enhance_plain_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to="tests.Enhance_Plain", ), ), ( "enhance_base_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Enhance_Base", ), ), ("field_i", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.enhance_base", "tests.enhance_plain"), ), migrations.CreateModel( name="InitTestModelSubclass", fields=[ ( "inittestmodel_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.InitTestModel", ), ) ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.inittestmodel",), ), migrations.CreateModel( name="MgrInheritB", fields=[ ( "mgrinherita_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.MgrInheritA", ), ), ("field2", models.CharField(max_length=10)), ], bases=("tests.mgrinherita",), managers=[("mgrB", django.db.models.manager.Manager())], ), migrations.CreateModel( name="Middle", fields=[ ( "top_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Top", ), ), ("description", models.TextField()), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.top",), ), 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="tests.Model2A", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.model2a",), ), migrations.CreateModel( name="ModelExtraB", fields=[ ( "modelextraa_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.ModelExtraA", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.modelextraa",), ), migrations.CreateModel( name="ModelShow2_plain", fields=[ ( "modelshow1_plain_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.ModelShow1_plain", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.modelshow1_plain",), ), migrations.CreateModel( name="ModelWithMyManager", fields=[ ( "model2a_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Model2A", ), ), ("field4", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, "tests.model2a"), ), migrations.CreateModel( name="ModelWithMyManager2", fields=[ ( "model2a_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Model2A", ), ), ("field4", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, "tests.model2a"), ), migrations.CreateModel( name="ModelWithMyManagerDefault", fields=[ ( "model2a_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Model2A", ), ), ("field4", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, "tests.model2a"), managers=[ ("my_objects", django.db.models.manager.Manager()), ("objects", django.db.models.manager.Manager()), ], ), migrations.CreateModel( name="ModelWithMyManagerNoDefault", fields=[ ( "model2a_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Model2A", ), ), ("field4", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=(polymorphic.showfields.ShowFieldTypeAndContent, "tests.model2a"), ), migrations.CreateModel( name="ModelX", fields=[ ( "base_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Base", ), ), ("field_x", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.base",), ), migrations.CreateModel( name="ModelY", fields=[ ( "base_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Base", ), ), ("field_y", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.base",), ), migrations.CreateModel( name="MROBase2", fields=[ ( "mrobase1_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.MROBase1", ), ) ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.mrobase1",), managers=[ ("objects", django.db.models.manager.Manager()), ("base_objects", django.db.models.manager.Manager()), ], ), migrations.CreateModel( name="NonProxyChild", fields=[ ( "proxybase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.ProxyBase", ), ), ("name", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.proxybase",), ), migrations.CreateModel( name="One2OneRelatingModelDerived", fields=[ ( "one2onerelatingmodel_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.One2OneRelatingModel", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.one2onerelatingmodel",), ), migrations.CreateModel( name="PlainB", fields=[ ( "plaina_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.PlainA", ), ), ("field2", models.CharField(max_length=10)), ], bases=("tests.plaina",), ), migrations.CreateModel( name="RelationA", fields=[ ( "relationbase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.RelationBase", ), ), ("field_a", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.relationbase",), ), migrations.CreateModel( name="RelationB", fields=[ ( "relationbase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.RelationBase", ), ), ("field_b", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.relationbase",), ), migrations.CreateModel( name="TestParentLinkAndRelatedName", fields=[ ( "superclass", models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, related_name="related_name_subclass", serialize=False, to="tests.ModelShow1_plain", ), ) ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.modelshow1_plain",), ), migrations.CreateModel( name="UUIDArtProject", fields=[ ( "uuidproject_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.UUIDProject", ), ), ("artist", models.CharField(max_length=30)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.uuidproject",), ), migrations.CreateModel( name="UUIDPlainB", fields=[ ( "uuidplaina_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.UUIDPlainA", ), ), ("field2", models.CharField(max_length=10)), ], bases=("tests.uuidplaina",), ), migrations.CreateModel( name="UUIDResearchProject", fields=[ ( "uuidproject_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.UUIDProject", ), ), ("supervisor", models.CharField(max_length=30)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.uuidproject",), ), migrations.AddField( model_name="uuidproject", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.uuidproject_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="top", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.top_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="relationbase", name="fk", field=models.ForeignKey( null=True, on_delete=django.db.models.deletion.CASCADE, related_name="relationbase_set", to="tests.RelationBase", ), ), migrations.AddField( model_name="relationbase", name="m2m", field=models.ManyToManyField( related_name="_relationbase_m2m_+", to="tests.RelationBase" ), ), migrations.AddField( model_name="relationbase", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.relationbase_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="relatingmodel", name="many2many", field=models.ManyToManyField(to="tests.Model2A"), ), migrations.AddField( model_name="proxybase", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.proxybase_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="plainchildmodelwithmanager", name="fk", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="childmodel_set", to="tests.PlainParentModelWithManager", ), ), migrations.AddField( model_name="one2onerelatingmodel", name="one2one", field=models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, to="tests.Model2A" ), ), migrations.AddField( model_name="one2onerelatingmodel", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.one2onerelatingmodel_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="mrobase1", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.mrobase1_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="modelunderrelchild", name="parent", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="children", to="tests.ModelUnderRelParent", ), ), migrations.AddField( model_name="modelunderrelchild", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelunderrelchild_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="modelshow1_plain", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelshow1_plain_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="modelextraa", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.modelextraa_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_tests.model2a_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="inittestmodel", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.inittestmodel_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="enhance_base", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.enhance_base_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="custompkbase", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.custompkbase_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="childmodelwithmanager", name="fk", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, related_name="childmodel_set", to="tests.ParentModelWithManager", ), ), migrations.AddField( model_name="childmodelwithmanager", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.childmodelwithmanager_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="blogentry_limit_choices_to", name="blog", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.BlogBase" ), ), migrations.AddField( model_name="blogentry_limit_choices_to", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.blogentry_limit_choices_to_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="blogbase", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.blogbase_set+", to="contenttypes.ContentType", ), ), migrations.AddField( model_name="base", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.base_set+", to="contenttypes.ContentType", ), ), migrations.CreateModel( name="ProxyChild", fields=[], options={"proxy": True}, bases=("tests.proxybase",), ), migrations.CreateModel( name="ProxyModelBase", fields=[], options={"proxy": True}, bases=("tests.proxiedbase",), ), migrations.CreateModel( name="Bottom", fields=[ ( "middle_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Middle", ), ), ("author", models.CharField(max_length=50)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.middle",), ), migrations.CreateModel( name="MgrInheritC", fields=[ ( "mgrinheritb_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.MgrInheritB", ), ) ], bases=(polymorphic.showfields.ShowFieldTypeAndContent, "tests.mgrinheritb"), ), 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="tests.Model2B", ), ), ("field3", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.model2b",), ), migrations.CreateModel( name="ModelExtraC", fields=[ ( "modelextrab_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.ModelExtraB", ), ), ("field3", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.modelextrab",), ), migrations.CreateModel( name="MRODerived", fields=[ ( "mrobase3_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to="tests.MROBase3", ), ), ( "mrobase2_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.MROBase2", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.mrobase2", "tests.mrobase3"), managers=[ ("objects", django.db.models.manager.Manager()), ("base_objects", django.db.models.manager.Manager()), ], ), migrations.CreateModel( name="PlainC", fields=[ ( "plainb_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.PlainB", ), ), ("field3", models.CharField(max_length=10)), ], bases=("tests.plainb",), ), migrations.CreateModel( name="ProxyModelA", fields=[ ( "proxiedbase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.ProxiedBase", ), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.proxymodelbase",), ), migrations.CreateModel( name="ProxyModelB", fields=[ ( "proxiedbase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.ProxiedBase", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.proxymodelbase",), ), migrations.CreateModel( name="RelationBC", fields=[ ( "relationb_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.RelationB", ), ), ("field_c", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.relationb",), ), migrations.CreateModel( name="UUIDPlainC", fields=[ ( "uuidplainb_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.UUIDPlainB", ), ), ("field3", models.CharField(max_length=10)), ], bases=("tests.uuidplainb",), ), migrations.AddField( model_name="blogentry", name="blog", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.BlogA" ), ), migrations.CreateModel( name="Model2D", fields=[ ( "model2c_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.Model2C", ), ), ("field4", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.model2c",), ), migrations.CreateModel( name="InlineModelBase", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ) ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="InlineParent", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("title", models.CharField(max_length=10)), ], ), migrations.CreateModel( name="InlineModelA", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("field1", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="InlineModelB", fields=[ ( "inlinemodela_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.InlineModelA", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.inlinemodela",), ), migrations.AddField( model_name="inlinemodela", name="parent", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="tests.InlineParent", related_name="inline_children", ), ), migrations.AddField( model_name="inlinemodela", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.inlinemodela_set+", to="contenttypes.ContentType", ), ), migrations.CreateModel( name="ArtProject", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("topic", models.CharField(max_length=30)), ("artist", models.CharField(max_length=30)), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.artproject_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="Duck", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("name", models.CharField(max_length=30)), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.duck_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False}, ), migrations.CreateModel( name="MultiTableBase", 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="MultiTableDerived", fields=[ ( "multitablebase_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.MultiTableBase", ), ), ("field2", models.CharField(max_length=10)), ], options={"abstract": False}, bases=("tests.multitablebase",), ), migrations.AddField( model_name="multitablebase", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.multitablebase_set+", to="contenttypes.ContentType", ), ), migrations.CreateModel( name="RedheadDuck", fields=[], options={"proxy": True}, bases=("tests.duck",), ), migrations.CreateModel( name="RubberDuck", fields=[], options={"proxy": True}, bases=("tests.duck",) ), migrations.CreateModel( name="SubclassSelectorAbstractBaseModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("base_field", models.CharField(default="test_bf", max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="SubclassSelectorProxyBaseModel", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("base_field", models.CharField(default="test_bf", max_length=10)), ( "polymorphic_ctype", models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.subclassselectorproxybasemodel_set+", to="contenttypes.ContentType", ), ), ], options={"abstract": False, "base_manager_name": "objects"}, ), migrations.CreateModel( name="SubclassSelectorAbstractConcreteModel", fields=[ ( "subclassselectorabstractbasemodel_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.SubclassSelectorAbstractBaseModel", ), ), ("abstract_field", models.CharField(default="test_af", max_length=10)), ("concrete_field", models.CharField(default="test_cf", max_length=10)), ], options={"abstract": False}, bases=("tests.subclassselectorabstractbasemodel",), ), migrations.AddField( model_name="subclassselectorabstractbasemodel", name="polymorphic_ctype", field=models.ForeignKey( editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name="polymorphic_tests.subclassselectorabstractbasemodel_set+", to="contenttypes.ContentType", ), ), migrations.CreateModel( name="SubclassSelectorProxyModel", fields=[], options={"proxy": True, "indexes": []}, bases=("tests.subclassselectorproxybasemodel",), ), migrations.CreateModel( name="SubclassSelectorProxyConcreteModel", fields=[ ( "subclassselectorproxybasemodel_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to="tests.SubclassSelectorProxyBaseModel", ), ), ("concrete_field", models.CharField(default="test_cf", max_length=10)), ], options={"abstract": False, "base_manager_name": "objects"}, bases=("tests.subclassselectorproxymodel",), ), ] django-polymorphic-2.1.2/polymorphic/tests/migrations/__init__.py000066400000000000000000000000001351315731100253030ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/tests/models.py000066400000000000000000000277631351315731100227040ustar00rootroot00000000000000# -*- coding: utf-8 -*- import uuid import django from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models.query import QuerySet from polymorphic.managers import PolymorphicManager from polymorphic.models import PolymorphicModel from polymorphic.query import PolymorphicQuerySet from polymorphic.showfields import ( ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent, ) 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) polymorphic_showfield_deferred = True 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): polymorphic_showfield_deferred = True 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", on_delete=models.CASCADE, 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, on_delete=models.CASCADE) 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, on_delete=models.CASCADE, related_name="children" ) _private2 = models.CharField(max_length=10) class MyManagerQuerySet(PolymorphicQuerySet): def my_queryset_foo(self): # Just a method to prove the existence of the custom queryset. return self.all() 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() class ModelWithMyManager(ShowFieldTypeAndContent, Model2A): objects = MyManager() field4 = models.CharField(max_length=10) class ModelWithMyManagerNoDefault(ShowFieldTypeAndContent, Model2A): objects = PolymorphicManager() my_objects = MyManager() field4 = models.CharField(max_length=10) class ModelWithMyManagerDefault(ShowFieldTypeAndContent, Model2A): my_objects = MyManager() objects = PolymorphicManager() field4 = models.CharField(max_length=10) 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 # No manager_inheritance_from_future or Meta set. test that polymorphic restores that. class MROBase3(models.Model): # make sure 'id' field doesn't clash, detected by Django 1.11 base_3_id = models.AutoField(primary_key=True) objects = models.Manager() class MRODerived(MROBase2, MROBase3): if django.VERSION < (3, 0): class Meta: manager_inheritance_from_future = True class ParentModelWithManager(PolymorphicModel): pass class ChildModelWithManager(PolymorphicModel): # Also test whether foreign keys receive the manager: fk = models.ForeignKey( ParentModelWithManager, on_delete=models.CASCADE, related_name="childmodel_set" ) objects = MyManager() class PlainMyManagerQuerySet(QuerySet): def my_queryset_foo(self): # Just a method to prove the existence of the custom queryset. return self.all() 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) class PlainParentModelWithManager(models.Model): pass class PlainChildModelWithManager(models.Model): fk = models.ForeignKey( PlainParentModelWithManager, on_delete=models.CASCADE, related_name="childmodel_set", ) objects = PlainMyManager() 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, on_delete=models.CASCADE) text = models.CharField(max_length=10) class BlogEntry_limit_choices_to(ShowFieldTypeAndContent, PolymorphicModel): blog = models.ForeignKey(BlogBase, on_delete=models.CASCADE) 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 Middle(Top): description = models.TextField() class Bottom(Middle): author = models.CharField(max_length=50) class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel): uuid_primary_key = models.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 = models.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, on_delete=models.CASCADE, 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, on_delete=models.CASCADE, 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 DateModel(PolymorphicModel): date = models.DateTimeField() # Define abstract and swappable (being swapped for SwappedModel) models # To test manager validation (should be skipped for such models) class AbstractModel(PolymorphicModel): class Meta: abstract = True class SwappableModel(AbstractModel): class Meta: swappable = "POLYMORPHIC_TEST_SWAPPABLE" class SwappedModel(AbstractModel): pass class InlineParent(models.Model): title = models.CharField(max_length=10) class InlineModelA(PolymorphicModel): parent = models.ForeignKey( InlineParent, related_name="inline_children", on_delete=models.CASCADE ) field1 = models.CharField(max_length=10) class InlineModelB(InlineModelA): field2 = models.CharField(max_length=10) class AbstractProject(PolymorphicModel): topic = models.CharField(max_length=30) class Meta: abstract = True class ArtProject(AbstractProject): artist = models.CharField(max_length=30) class Duck(PolymorphicModel): name = models.CharField(max_length=30) class RedheadDuck(Duck): class Meta: proxy = True class RubberDuck(Duck): class Meta: proxy = True class MultiTableBase(PolymorphicModel): field1 = models.CharField(max_length=10) class MultiTableDerived(MultiTableBase): field2 = models.CharField(max_length=10) class SubclassSelectorAbstractBaseModel(PolymorphicModel): base_field = models.CharField(max_length=10, default="test_bf") class SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel): abstract_field = models.CharField(max_length=10, default="test_af") class Meta: abstract = True class SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel): concrete_field = models.CharField(max_length=10, default="test_cf") class SubclassSelectorProxyBaseModel(PolymorphicModel): base_field = models.CharField(max_length=10, default="test_bf") class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel): class Meta: proxy = True class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel): concrete_field = models.CharField(max_length=10, default="test_cf") django-polymorphic-2.1.2/polymorphic/tests/test_admin.py000066400000000000000000000112771351315731100235410ustar00rootroot00000000000000from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.utils.html import escape from polymorphic.admin import ( PolymorphicChildModelAdmin, PolymorphicChildModelFilter, PolymorphicInlineSupportMixin, PolymorphicParentModelAdmin, StackedPolymorphicInline, ) from polymorphic.tests.admintestcase import AdminTestCase from polymorphic.tests.models import ( InlineModelA, InlineModelB, InlineParent, Model2A, Model2B, Model2C, Model2D, ) class PolymorphicAdminTests(AdminTestCase): def test_admin_registration(self): """ Test how the registration works """ @self.register(Model2A) class Model2Admin(PolymorphicParentModelAdmin): base_model = Model2A list_filter = (PolymorphicChildModelFilter,) child_models = (Model2B, Model2C, Model2D) @self.register(Model2B) @self.register(Model2C) @self.register(Model2D) class Model2ChildAdmin(PolymorphicChildModelAdmin): base_model = Model2A base_fieldsets = (("Base fields", {"fields": ("field1",)}),) # -- add page ct_id = ContentType.objects.get_for_model(Model2D).pk self.admin_get_add(Model2A) # shows type page self.admin_get_add(Model2A, qs="?ct_id={}".format(ct_id)) # shows type page self.admin_get_add(Model2A) # shows type page self.admin_post_add( Model2A, {"field1": "A", "field2": "B", "field3": "C", "field4": "D"}, qs="?ct_id={}".format(ct_id), ) d_obj = Model2A.objects.all()[0] self.assertEqual(d_obj.__class__, Model2D) self.assertEqual(d_obj.field1, "A") self.assertEqual(d_obj.field2, "B") # -- list page self.admin_get_changelist(Model2A) # asserts 200 # -- edit response = self.admin_get_change(Model2A, d_obj.pk) self.assertContains(response, "field4") self.admin_post_change( Model2A, d_obj.pk, {"field1": "A2", "field2": "B2", "field3": "C2", "field4": "D2"}, ) d_obj.refresh_from_db() self.assertEqual(d_obj.field1, "A2") self.assertEqual(d_obj.field2, "B2") self.assertEqual(d_obj.field3, "C2") self.assertEqual(d_obj.field4, "D2") # -- history self.admin_get_history(Model2A, d_obj.pk) # -- delete self.admin_get_delete(Model2A, d_obj.pk) self.admin_post_delete(Model2A, d_obj.pk) self.assertRaises(Model2A.DoesNotExist, lambda: d_obj.refresh_from_db()) def test_admin_inlines(self): """ Test the registration of inline models. """ class InlineModelAChild(StackedPolymorphicInline.Child): model = InlineModelA class InlineModelBChild(StackedPolymorphicInline.Child): model = InlineModelB class Inline(StackedPolymorphicInline): model = InlineModelA child_inlines = (InlineModelAChild, InlineModelBChild) @self.register(InlineParent) class InlineParentAdmin(PolymorphicInlineSupportMixin, admin.ModelAdmin): inlines = (Inline,) parent = InlineParent.objects.create(title="FOO") self.assertEqual(parent.inline_children.count(), 0) # -- get edit page response = self.admin_get_change(InlineParent, parent.pk) # Make sure the fieldset has the right data exposed in data-inline-formset self.assertContains(response, "childTypes") self.assertContains(response, escape('"type": "inlinemodela"')) self.assertContains(response, escape('"type": "inlinemodelb"')) # -- post edit page self.admin_post_change( InlineParent, parent.pk, { "title": "FOO2", "inline_children-INITIAL_FORMS": 0, "inline_children-TOTAL_FORMS": 1, "inline_children-MIN_NUM_FORMS": 0, "inline_children-MAX_NUM_FORMS": 1000, "inline_children-0-parent": parent.pk, "inline_children-0-polymorphic_ctype": ContentType.objects.get_for_model( InlineModelB ).pk, "inline_children-0-field1": "A2", "inline_children-0-field2": "B2", }, ) parent.refresh_from_db() self.assertEqual(parent.title, "FOO2") self.assertEqual(parent.inline_children.count(), 1) child = parent.inline_children.all()[0] self.assertEqual(child.__class__, InlineModelB) self.assertEqual(child.field1, "A2") self.assertEqual(child.field2, "B2") django-polymorphic-2.1.2/polymorphic/tests/test_contrib.py000066400000000000000000000015751351315731100241110ustar00rootroot00000000000000from unittest import TestCase from polymorphic.contrib.guardian import get_polymorphic_base_content_type from polymorphic.tests.models import Model2D, PlainC class ContribTests(TestCase): """ The test suite """ def test_contrib_guardian(self): # Regular Django inheritance should return the child model content type. obj = PlainC() ctype = get_polymorphic_base_content_type(obj) self.assertEqual(ctype.name, "plain c") ctype = get_polymorphic_base_content_type(PlainC) self.assertEqual(ctype.name, "plain c") # Polymorphic inheritance should return the parent model content type. obj = Model2D() ctype = get_polymorphic_base_content_type(obj) self.assertEqual(ctype.name, "model2a") ctype = get_polymorphic_base_content_type(Model2D) self.assertEqual(ctype.name, "model2a") django-polymorphic-2.1.2/polymorphic/tests/test_multidb.py000066400000000000000000000112151351315731100241010ustar00rootroot00000000000000from __future__ import print_function from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.test import TestCase from polymorphic.tests.models import ( Base, BlogA, BlogEntry, Model2A, Model2B, Model2C, Model2D, ModelX, ModelY, One2OneRelatingModel, RelatingModel, ) class MultipleDatabasesTests(TestCase): multi_db = True def test_save_to_non_default_database(self): Model2A.objects.db_manager("secondary").create(field1="A1") Model2C(field1="C1", field2="C2", field3="C3").save(using="secondary") Model2B.objects.create(field1="B1", field2="B2") Model2D(field1="D1", field2="D2", field3="D3", field4="D4").save() self.assertQuerysetEqual( Model2A.objects.order_by("id"), [Model2B, Model2D], transform=lambda o: o.__class__, ) self.assertQuerysetEqual( Model2A.objects.db_manager("secondary").order_by("id"), [Model2A, Model2C], transform=lambda o: o.__class__, ) def test_instance_of_filter_on_non_default_database(self): Base.objects.db_manager("secondary").create(field_b="B1") ModelX.objects.db_manager("secondary").create(field_b="B", field_x="X") ModelY.objects.db_manager("secondary").create(field_b="Y", field_y="Y") objects = Base.objects.db_manager("secondary").filter(instance_of=Base) self.assertQuerysetEqual( objects, [Base, ModelX, ModelY], transform=lambda o: o.__class__, ordered=False, ) self.assertQuerysetEqual( Base.objects.db_manager("secondary").filter(instance_of=ModelX), [ModelX], transform=lambda o: o.__class__, ) self.assertQuerysetEqual( Base.objects.db_manager("secondary").filter(instance_of=ModelY), [ModelY], transform=lambda o: o.__class__, ) self.assertQuerysetEqual( Base.objects.db_manager("secondary").filter( Q(instance_of=ModelX) | Q(instance_of=ModelY) ), [ModelX, ModelY], transform=lambda o: o.__class__, ordered=False, ) def test_forward_many_to_one_descriptor_on_non_default_database(self): def func(): blog = BlogA.objects.db_manager("secondary").create( name="Blog", info="Info" ) entry = BlogEntry.objects.db_manager("secondary").create( blog=blog, text="Text" ) ContentType.objects.clear_cache() entry = BlogEntry.objects.db_manager("secondary").get(pk=entry.id) self.assertEqual(blog, entry.blog) # Ensure no queries are made using the default database. self.assertNumQueries(0, func) def test_reverse_many_to_one_descriptor_on_non_default_database(self): def func(): blog = BlogA.objects.db_manager("secondary").create( name="Blog", info="Info" ) entry = BlogEntry.objects.db_manager("secondary").create( blog=blog, text="Text" ) ContentType.objects.clear_cache() blog = BlogA.objects.db_manager("secondary").get(pk=blog.id) self.assertEqual(entry, blog.blogentry_set.using("secondary").get()) # Ensure no queries are made using the default database. self.assertNumQueries(0, func) def test_reverse_one_to_one_descriptor_on_non_default_database(self): def func(): m2a = Model2A.objects.db_manager("secondary").create(field1="A1") one2one = One2OneRelatingModel.objects.db_manager("secondary").create( one2one=m2a, field1="121" ) ContentType.objects.clear_cache() m2a = Model2A.objects.db_manager("secondary").get(pk=m2a.id) self.assertEqual(one2one, m2a.one2onerelatingmodel) # Ensure no queries are made using the default database. self.assertNumQueries(0, func) def test_many_to_many_descriptor_on_non_default_database(self): def func(): m2a = Model2A.objects.db_manager("secondary").create(field1="A1") rm = RelatingModel.objects.db_manager("secondary").create() rm.many2many.add(m2a) ContentType.objects.clear_cache() m2a = Model2A.objects.db_manager("secondary").get(pk=m2a.id) self.assertEqual(rm, m2a.relatingmodel_set.using("secondary").get()) # Ensure no queries are made using the default database. self.assertNumQueries(0, func) django-polymorphic-2.1.2/polymorphic/tests/test_orm.py000066400000000000000000001374661351315731100232570ustar00rootroot00000000000000import re import uuid from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Case, Count, Q, When from django.test import TransactionTestCase from polymorphic import compat, query_translate from polymorphic.managers import PolymorphicManager from polymorphic.models import PolymorphicTypeInvalid, PolymorphicTypeUndefined from polymorphic.tests.models import ( ArtProject, Base, BlogA, BlogB, BlogBase, BlogEntry, BlogEntry_limit_choices_to, ChildModelWithManager, CustomPkBase, CustomPkInherit, Duck, Enhance_Base, Enhance_Inherit, InitTestModelSubclass, Model2A, Model2B, Model2C, Model2D, ModelExtraA, ModelExtraB, ModelExtraC, ModelExtraExternal, ModelFieldNameTest, ModelShow1, ModelShow1_plain, ModelShow2, ModelShow2_plain, ModelShow3, ModelUnderRelChild, ModelUnderRelParent, ModelWithMyManager, ModelWithMyManager2, ModelWithMyManagerDefault, ModelWithMyManagerNoDefault, ModelX, ModelY, MRODerived, MultiTableDerived, MyManager, MyManagerQuerySet, NonProxyChild, One2OneRelatingModel, One2OneRelatingModelDerived, ParentModelWithManager, PlainA, PlainB, PlainC, PlainChildModelWithManager, PlainMyManager, PlainMyManagerQuerySet, PlainParentModelWithManager, ProxiedBase, ProxyBase, ProxyChild, ProxyModelA, ProxyModelB, ProxyModelBase, RedheadDuck, RelatingModel, RelationA, RelationB, RelationBase, RelationBC, RubberDuck, SubclassSelectorAbstractBaseModel, SubclassSelectorAbstractConcreteModel, SubclassSelectorProxyBaseModel, SubclassSelectorProxyConcreteModel, TestParentLinkAndRelatedName, UUIDArtProject, UUIDPlainA, UUIDPlainB, UUIDPlainC, UUIDProject, UUIDResearchProject, ) class PolymorphicTests(TransactionTestCase): """ 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. """ a = Model2A.objects.create(field1="A1") b = Model2B.objects.create(field1="B1", field2="B2") c = Model2C.objects.create(field1="C1", field2="C2", field3="C3") d = Model2D.objects.create(field1="D1", field2="D2", field3="D3", field4="D4") return a, b, c, d def test_simple_inheritance(self): self.create_model2abcd() objects = Model2A.objects.all() self.assertQuerysetEqual( objects, [Model2A, Model2B, Model2C, Model2D], transform=lambda o: o.__class__, ordered=False, ) def test_defer_fields(self): self.create_model2abcd() objects_deferred = Model2A.objects.defer("field1").order_by("id") self.assertNotIn( "field1", objects_deferred[0].__dict__, "field1 was not deferred (using defer())", ) self.assertRegex( repr(objects_deferred[0]), r"", ) self.assertRegex( repr(objects_deferred[1]), r"", ) self.assertRegex( repr(objects_deferred[2]), r"", ) self.assertRegex( repr(objects_deferred[3]), r"", ) objects_only = Model2A.objects.only("pk", "polymorphic_ctype", "field1") self.assertIn( "field1", objects_only[0].__dict__, 'qs.only("field1") was used, but field1 was incorrectly deferred', ) self.assertIn( "field1", objects_only[3].__dict__, 'qs.only("field1") was used, but field1 was incorrectly deferred' " on a child model", ) self.assertNotIn( "field4", objects_only[3].__dict__, "field4 was not deferred (using only())" ) self.assertRegex( repr(objects_only[0]), r"" ) self.assertRegex( repr(objects_only[1]), r"", ) self.assertRegex( repr(objects_only[2]), r"", ) self.assertRegex( repr(objects_only[3]), r"", ) ModelX.objects.create(field_b="A1", field_x="A2") ModelY.objects.create(field_b="B1", field_y="B2") objects_deferred = Base.objects.defer("ModelY___field_y") self.assertRegex( repr(objects_deferred[0]), r"", ) self.assertRegex( repr(objects_deferred[1]), r"", ) objects_only = Base.objects.only( "polymorphic_ctype", "ModelY___field_y", "ModelX___field_x" ) self.assertRegex( repr(objects_only[0]), r"", ) self.assertRegex( repr(objects_only[1]), r"", ) def test_defer_related_fields(self): self.create_model2abcd() objects_deferred_field4 = Model2A.objects.defer("Model2D___field4") self.assertNotIn( "field4", objects_deferred_field4[3].__dict__, "field4 was not deferred (using defer(), traversing inheritance)", ) self.assertEqual(objects_deferred_field4[0].__class__, Model2A) self.assertEqual(objects_deferred_field4[1].__class__, Model2B) self.assertEqual(objects_deferred_field4[2].__class__, Model2C) self.assertEqual(objects_deferred_field4[3].__class__, Model2D) objects_only_field4 = Model2A.objects.only( "polymorphic_ctype", "field1", "Model2B___id", "Model2B___field2", "Model2B___model2a_ptr", "Model2C___id", "Model2C___field3", "Model2C___model2b_ptr", "Model2D___id", "Model2D___model2c_ptr", ) self.assertEqual(objects_only_field4[0].__class__, Model2A) self.assertEqual(objects_only_field4[1].__class__, Model2B) self.assertEqual(objects_only_field4[2].__class__, Model2C) self.assertEqual(objects_only_field4[3].__class__, Model2D) def test_manual_get_real_instance(self): self.create_model2abcd() o = Model2A.objects.non_polymorphic().get(field1="C1") self.assertEqual(o.get_real_instance().__class__, Model2C) def test_non_polymorphic(self): self.create_model2abcd() objects = list(Model2A.objects.all().non_polymorphic()) self.assertQuerysetEqual( objects, [Model2A, Model2A, Model2A, Model2A], transform=lambda o: o.__class__, ) def test_get_real_instances(self): self.create_model2abcd() qs = Model2A.objects.all().non_polymorphic() # from queryset objects = qs.get_real_instances() self.assertQuerysetEqual( objects, [Model2A, Model2B, Model2C, Model2D], transform=lambda o: o.__class__, ) # from a manual list objects = Model2A.objects.get_real_instances(list(qs)) self.assertQuerysetEqual( objects, [Model2A, Model2B, Model2C, Model2D], transform=lambda o: o.__class__, ) # from empty list objects = Model2A.objects.get_real_instances([]) self.assertQuerysetEqual(objects, [], transform=lambda o: o.__class__) def test_queryset_missing_derived(self): a = Model2A.objects.create(field1="A1") b = Model2B.objects.create(field1="B1", field2="B2") c = Model2C.objects.create(field1="C1", field2="C2", field3="C3") b_base = Model2A.objects.non_polymorphic().get(pk=b.pk) c_base = Model2A.objects.non_polymorphic().get(pk=c.pk) b.delete(keep_parents=True) # e.g. table was truncated qs_base = Model2A.objects.order_by("field1").non_polymorphic() qs_polymorphic = Model2A.objects.order_by("field1").all() self.assertEqual(list(qs_base), [a, b_base, c_base]) self.assertEqual(list(qs_polymorphic), [a, c]) def test_queryset_missing_contenttype(self): stale_ct = ContentType.objects.create(app_label="tests", model="nonexisting") a1 = Model2A.objects.create(field1="A1") a2 = Model2A.objects.create(field1="A2") c = Model2C.objects.create(field1="C1", field2="C2", field3="C3") c_base = Model2A.objects.non_polymorphic().get(pk=c.pk) Model2B.objects.filter(pk=a2.pk).update(polymorphic_ctype=stale_ct) qs_base = Model2A.objects.order_by("field1").non_polymorphic() qs_polymorphic = Model2A.objects.order_by("field1").all() self.assertEqual(list(qs_base), [a1, a2, c_base]) self.assertEqual(list(qs_polymorphic), [a1, a2, c]) 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.assertQuerysetEqual( objects, [Model2C, Model2D], transform=lambda o: o.__class__, ordered=False ) def test_create_instanceof_q(self): q = query_translate.create_instanceof_q([Model2B]) expected = sorted( [ ContentType.objects.get_for_model(m).pk for m in [Model2B, Model2C, Model2D] ] ) self.assertEqual(dict(q.children), dict(polymorphic_ctype__in=expected)) def test_base_manager(self): def base_manager(model): return (type(model._base_manager), model._base_manager.model) self.assertEqual(base_manager(PlainA), (models.Manager, PlainA)) self.assertEqual(base_manager(PlainB), (models.Manager, PlainB)) self.assertEqual(base_manager(PlainC), (models.Manager, PlainC)) self.assertEqual(base_manager(Model2A), (PolymorphicManager, Model2A)) self.assertEqual(base_manager(Model2B), (PolymorphicManager, Model2B)) self.assertEqual(base_manager(Model2C), (PolymorphicManager, Model2C)) self.assertEqual( base_manager(One2OneRelatingModel), (PolymorphicManager, One2OneRelatingModel), ) self.assertEqual( base_manager(One2OneRelatingModelDerived), (PolymorphicManager, One2OneRelatingModelDerived), ) def test_instance_default_manager(self): def default_manager(instance): return ( type(instance.__class__._default_manager), instance.__class__._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(default_manager(plain_a), (models.Manager, PlainA)) self.assertEqual(default_manager(plain_b), (models.Manager, PlainB)) self.assertEqual(default_manager(plain_c), (models.Manager, PlainC)) self.assertEqual(default_manager(model_2a), (PolymorphicManager, Model2A)) self.assertEqual(default_manager(model_2b), (PolymorphicManager, Model2B)) self.assertEqual(default_manager(model_2c), (PolymorphicManager, Model2C)) def test_foreignkey_field(self): self.create_model2abcd() object2a = Model2A.base_objects.get(field1="C1") self.assertEqual(object2a.model2b.__class__, Model2B) object2b = Model2B.base_objects.get(field1="C1") self.assertEqual(object2b.model2c.__class__, 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(b.one2one.__class__, Model2A) self.assertEqual(b.one2one_id, b.one2one.id) c = One2OneRelatingModelDerived.objects.get(field1="f1") self.assertEqual(c.one2one.__class__, Model2C) self.assertEqual(a.one2onerelatingmodel.__class__, One2OneRelatingModelDerived) 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.assertQuerysetEqual( ModelShow1_plain.objects.all(), [ModelShow1_plain, ModelShow2_plain], transform=lambda o: o.__class__, ordered=False, ) def test_extra_method(self): a, b, c, d = self.create_model2abcd() objects = Model2A.objects.extra(where=["id IN ({}, {})".format(b.id, c.id)]) self.assertQuerysetEqual( objects, [Model2B, Model2C], transform=lambda o: o.__class__, ordered=False ) objects = Model2A.objects.extra( select={"select_test": "field1 = 'A1'"}, where=["field1 = 'A1' OR field1 = 'B1'"], order_by=["-id"], ) self.assertQuerysetEqual( objects, [Model2B, Model2A], transform=lambda o: o.__class__ ) 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=["tests_modelextraexternal"], select={"topic": "tests_modelextraexternal.topic"}, where=["tests_modelextraa.id = tests_modelextraexternal.id"], ) if compat.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.assertQuerysetEqual( objects, [Model2B, Model2C, Model2D], transform=lambda o: o.__class__, ordered=False, ) objects = Model2A.objects.filter(instance_of=Model2B) self.assertQuerysetEqual( objects, [Model2B, Model2C, Model2D], transform=lambda o: o.__class__, ordered=False, ) objects = Model2A.objects.filter(Q(instance_of=Model2B)) self.assertQuerysetEqual( objects, [Model2B, Model2C, Model2D], transform=lambda o: o.__class__, ordered=False, ) objects = Model2A.objects.not_instance_of(Model2B) self.assertQuerysetEqual( objects, [Model2A], transform=lambda o: o.__class__, ordered=False ) def test_polymorphic___filter(self): self.create_model2abcd() objects = Model2A.objects.filter( Q(Model2B___field2="B2") | Q(Model2C___field3="C3") ) self.assertQuerysetEqual( objects, [Model2B, Model2C], transform=lambda o: o.__class__, ordered=False ) def test_polymorphic_applabel___filter(self): self.create_model2abcd() assert Model2B._meta.app_label == "tests" objects = Model2A.objects.filter( Q(tests__Model2B___field2="B2") | Q(tests__Model2C___field3="C3") ) self.assertQuerysetEqual( objects, [Model2B, Model2C], transform=lambda o: o.__class__, ordered=False ) def test_query_filter_exclude_is_immutable(self): # given q_to_reuse = Q(Model2B___field2="something") untouched_q_object = Q(Model2B___field2="something") # when Model2A.objects.filter(q_to_reuse).all() # then self.assertEqual(q_to_reuse.children, untouched_q_object.children) # given q_to_reuse = Q(Model2B___field2="something") untouched_q_object = Q(Model2B___field2="something") # when Model2B.objects.filter(q_to_reuse).all() # then self.assertEqual(q_to_reuse.children, untouched_q_object.children) 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): a, b, c, d = self.create_model2abcd() oa = Model2A.objects.get(id=b.id) self.assertEqual(oa.__class__, Model2B) self.assertEqual(Model2A.objects.count(), 4) oa.delete() objects = Model2A.objects.all() self.assertQuerysetEqual( objects, [Model2A, Model2C, Model2D], transform=lambda o: o.__class__, ordered=False, ) def test_combine_querysets(self): ModelX.objects.create(field_x="x", field_b="1") ModelY.objects.create(field_y="y", field_b="2") qs = Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY) qs = qs.order_by("field_b") 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") # MyManager should reverse the sorting of field1 objects = ModelWithMyManager.objects.all() self.assertQuerysetEqual( objects, [(ModelWithMyManager, "D1b", "D4b"), (ModelWithMyManager, "D1a", "D4a")], transform=lambda o: (o.__class__, o.field1, o.field4), ) self.assertIs(type(ModelWithMyManager.objects), MyManager) self.assertIs(type(ModelWithMyManager._default_manager), MyManager) self.assertIs(type(ModelWithMyManager.base_objects), models.Manager) def test_user_defined_manager_as_secondary(self): self.create_model2abcd() ModelWithMyManagerNoDefault.objects.create(field1="D1a", field4="D4a") ModelWithMyManagerNoDefault.objects.create(field1="D1b", field4="D4b") # MyManager should reverse the sorting of field1 objects = ModelWithMyManagerNoDefault.my_objects.all() self.assertQuerysetEqual( objects, [ (ModelWithMyManagerNoDefault, "D1b", "D4b"), (ModelWithMyManagerNoDefault, "D1a", "D4a"), ], transform=lambda o: (o.__class__, o.field1, o.field4), ) self.assertIs(type(ModelWithMyManagerNoDefault.my_objects), MyManager) self.assertIs(type(ModelWithMyManagerNoDefault.objects), PolymorphicManager) self.assertIs( type(ModelWithMyManagerNoDefault._default_manager), PolymorphicManager ) self.assertIs(type(ModelWithMyManagerNoDefault.base_objects), models.Manager) def test_user_objects_manager_as_secondary(self): self.create_model2abcd() ModelWithMyManagerDefault.objects.create(field1="D1a", field4="D4a") ModelWithMyManagerDefault.objects.create(field1="D1b", field4="D4b") self.assertIs(type(ModelWithMyManagerDefault.my_objects), MyManager) self.assertIs(type(ModelWithMyManagerDefault.objects), PolymorphicManager) self.assertIs(type(ModelWithMyManagerDefault._default_manager), MyManager) self.assertIs(type(ModelWithMyManagerDefault.base_objects), models.Manager) 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.assertQuerysetEqual( objects, [(ModelWithMyManager2, "D1a", "D4a"), (ModelWithMyManager2, "D1b", "D4b")], transform=lambda o: (o.__class__, o.field1, o.field4), ordered=False, ) 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) 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_queryset_on_proxy_model_does_not_return_superclasses(self): ProxyBase.objects.create(some_data="Base1") ProxyBase.objects.create(some_data="Base2") ProxyChild.objects.create(some_data="Child1") ProxyChild.objects.create(some_data="Child2") ProxyChild.objects.create(some_data="Child3") self.assertEqual(5, ProxyBase.objects.count()) self.assertEqual(3, ProxyChild.objects.count()) 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 = NonProxyChild.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.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 object1_pk = ProxyModelA.objects.create(name="object1").pk object2_pk = ProxyModelB.objects.create(name="object2", field2="bb").pk # Getting single objects object1 = ProxyModelBase.objects.get(name="object1") object2 = ProxyModelBase.objects.get(name="object2") self.assertEqual( repr(object1), '' % object1_pk, ) self.assertEqual( repr(object2), '' % object2_pk, ) self.assertIsInstance(object1, ProxyModelA) self.assertIsInstance(object2, ProxyModelB) # Same for lists objects = list(ProxyModelBase.objects.all().order_by("name")) self.assertEqual( repr(objects[0]), '' % object1_pk, ) self.assertEqual( repr(objects[1]), '' % object2_pk, ) 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" ) # p should be Plain1 and t TestParentLinkAndRelatedName, so not equal self.assertNotEqual(p, t) self.assertEqual(p, t.superclass) self.assertEqual(p.related_name_subclass, t) # test that we can delete the object t.delete() def test_polymorphic__aggregate(self): """ test ModelX___field syntax on aggregate (should work for annotate either) """ Model2A.objects.create(field1="A1") Model2B.objects.create(field1="A1", field2="B2") Model2B.objects.create(field1="A1", field2="B2") # aggregate using **kwargs result = Model2A.objects.aggregate(cnt=Count("Model2B___field2")) self.assertEqual(result, {"cnt": 2}) # aggregate using **args self.assertRaisesMessage( AssertionError, "PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only", lambda: Model2A.objects.aggregate(Count("Model2B___field2")), ) def test_polymorphic__complex_aggregate(self): """ test (complex expression on) aggregate (should work for annotate either) """ Model2A.objects.create(field1="A1") Model2B.objects.create(field1="A1", field2="B2") Model2B.objects.create(field1="A1", field2="B2") # aggregate using **kwargs result = Model2A.objects.aggregate( cnt_a1=Count(Case(When(field1="A1", then=1))), cnt_b2=Count(Case(When(Model2B___field2="B2", then=1))), ) self.assertEqual(result, {"cnt_b2": 2, "cnt_a1": 3}) # aggregate using **args # we have to set the defaul alias or django won't except a complex expression # on aggregate/annotate def ComplexAgg(expression): complexagg = Count(expression) * 10 complexagg.default_alias = "complexagg" return complexagg with self.assertRaisesMessage( AssertionError, "PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only", ): Model2A.objects.aggregate(ComplexAgg("Model2B___field2")) def test_polymorphic__expressions(self): from django.db.models.functions import Concat # no exception raised result = Model2B.objects.annotate(val=Concat("field1", "field2")) self.assertEqual(list(result), []) def test_null_polymorphic_id(self): """Test that a proper error message is displayed when the database lacks the ``polymorphic_ctype_id``""" Model2A.objects.create(field1="A1") Model2B.objects.create(field1="A1", field2="B2") Model2B.objects.create(field1="A1", field2="B2") Model2A.objects.all().update(polymorphic_ctype_id=None) with self.assertRaises(PolymorphicTypeUndefined): list(Model2A.objects.all()) def test_invalid_polymorphic_id(self): """Test that a proper error message is displayed when the database ``polymorphic_ctype_id`` is invalid""" Model2A.objects.create(field1="A1") Model2B.objects.create(field1="A1", field2="B2") Model2B.objects.create(field1="A1", field2="B2") invalid = ContentType.objects.get_for_model(PlainA).pk Model2A.objects.all().update(polymorphic_ctype_id=invalid) with self.assertRaises(PolymorphicTypeInvalid): list(Model2A.objects.all()) def test_bulk_create_abstract_inheritance(self): ArtProject.objects.bulk_create( [ ArtProject(topic="Painting with Tim", artist="T. Turner"), ArtProject(topic="Sculpture with Tim", artist="T. Turner"), ] ) self.assertEqual( sorted(ArtProject.objects.values_list("topic", "artist")), [("Painting with Tim", "T. Turner"), ("Sculpture with Tim", "T. Turner")], ) def test_bulk_create_proxy_inheritance(self): RedheadDuck.objects.bulk_create( [ RedheadDuck(name="redheadduck1"), Duck(name="duck1"), RubberDuck(name="rubberduck1"), ] ) RubberDuck.objects.bulk_create( [ RedheadDuck(name="redheadduck2"), RubberDuck(name="rubberduck2"), Duck(name="duck2"), ] ) self.assertEqual( sorted(RedheadDuck.objects.values_list("name", flat=True)), ["redheadduck1", "redheadduck2"], ) self.assertEqual( sorted(RubberDuck.objects.values_list("name", flat=True)), ["rubberduck1", "rubberduck2"], ) self.assertEqual( sorted(Duck.objects.values_list("name", flat=True)), [ "duck1", "duck2", "redheadduck1", "redheadduck2", "rubberduck1", "rubberduck2", ], ) def test_bulk_create_unsupported_multi_table_inheritance(self): with self.assertRaises(ValueError): MultiTableDerived.objects.bulk_create( [MultiTableDerived(field1="field1", field2="field2")] ) def test_can_query_using_subclass_selector_on_abstract_model(self): obj = SubclassSelectorAbstractConcreteModel.objects.create(concrete_field="abc") queried_obj = SubclassSelectorAbstractBaseModel.objects.filter( SubclassSelectorAbstractConcreteModel___concrete_field="abc" ).get() self.assertEqual(obj.pk, queried_obj.pk) def test_can_query_using_subclass_selector_on_proxy_model(self): obj = SubclassSelectorProxyConcreteModel.objects.create(concrete_field="abc") queried_obj = SubclassSelectorProxyBaseModel.objects.filter( SubclassSelectorProxyConcreteModel___concrete_field="abc" ).get() self.assertEqual(obj.pk, queried_obj.pk) def test_prefetch_related_behaves_normally_with_polymorphic_model(self): b1 = RelatingModel.objects.create() b2 = RelatingModel.objects.create() a = b1.many2many.create() # create Model2A b2.many2many.add(a) # add same to second relating model qs = RelatingModel.objects.prefetch_related("many2many") for obj in qs: self.assertEqual(len(obj.many2many.all()), 1) def test_prefetch_related_with_missing(self): b1 = RelatingModel.objects.create() b2 = RelatingModel.objects.create() rel1 = Model2A.objects.create(field1="A1") rel2 = Model2B.objects.create(field1="A2", field2="B2") b1.many2many.add(rel1) b2.many2many.add(rel2) rel2.delete(keep_parents=True) qs = RelatingModel.objects.order_by("pk").prefetch_related("many2many") objects = list(qs) self.assertEqual(len(objects[0].many2many.all()), 1) # derived object was not fetched self.assertEqual(len(objects[1].many2many.all()), 0) # base object does exist self.assertEqual(len(objects[1].many2many.non_polymorphic()), 1) def test_refresh_from_db_fields(self): """Test whether refresh_from_db(fields=..) works as it performs .only() queries""" obj = Model2B.objects.create(field1="aa", field2="bb") Model2B.objects.filter(pk=obj.pk).update(field1="aa1", field2="bb2") obj.refresh_from_db(fields=["field2"]) assert obj.field1 == "aa" assert obj.field2 == "bb2" obj.refresh_from_db(fields=["field1"]) assert obj.field1 == "aa1" django-polymorphic-2.1.2/polymorphic/tests/test_query_translate.py000066400000000000000000000000001351315731100256510ustar00rootroot00000000000000django-polymorphic-2.1.2/polymorphic/tests/test_regression.py000066400000000000000000000016131351315731100246220ustar00rootroot00000000000000from django.test import TestCase from polymorphic.tests.models import Bottom, Middle, Top 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.order_by("pk"), [repr(r) for r in expected_queryset] ) expected_queryset = [middle, bottom] self.assertQuerysetEqual( Middle.objects.order_by("pk"), [repr(r) for r in expected_queryset] ) expected_queryset = [bottom] self.assertQuerysetEqual( Bottom.objects.order_by("pk"), [repr(r) for r in expected_queryset] ) django-polymorphic-2.1.2/polymorphic/tests/test_utils.py000066400000000000000000000052051351315731100236030ustar00rootroot00000000000000from django.test import TransactionTestCase from polymorphic.models import PolymorphicModel, PolymorphicTypeUndefined from polymorphic.tests.models import ( Enhance_Base, Enhance_Inherit, Model2A, Model2B, Model2C, Model2D, ) from polymorphic.utils import ( get_base_polymorphic_model, reset_polymorphic_ctype, sort_by_subclass, ) class UtilsTests(TransactionTestCase): def test_sort_by_subclass(self): self.assertEqual( sort_by_subclass(Model2D, Model2B, Model2D, Model2A, Model2C), [Model2A, Model2B, Model2C, Model2D, Model2D], ) def test_reset_polymorphic_ctype(self): """ Test the the polymorphic_ctype_id can be restored. """ Model2A.objects.create(field1="A1") Model2D.objects.create(field1="A1", field2="B2", field3="C3", field4="D4") Model2B.objects.create(field1="A1", field2="B2") Model2B.objects.create(field1="A1", field2="B2") Model2A.objects.all().update(polymorphic_ctype_id=None) with self.assertRaises(PolymorphicTypeUndefined): list(Model2A.objects.all()) reset_polymorphic_ctype(Model2D, Model2B, Model2D, Model2A, Model2C) self.assertQuerysetEqual( Model2A.objects.order_by("pk"), [Model2A, Model2D, Model2B, Model2B], transform=lambda o: o.__class__, ) def test_get_base_polymorphic_model(self): """ Test that finding the base polymorphic model works. """ # Finds the base from every level (including lowest) self.assertIs(get_base_polymorphic_model(Model2D), Model2A) self.assertIs(get_base_polymorphic_model(Model2C), Model2A) self.assertIs(get_base_polymorphic_model(Model2B), Model2A) self.assertIs(get_base_polymorphic_model(Model2A), Model2A) # Properly handles multiple inheritance self.assertIs(get_base_polymorphic_model(Enhance_Inherit), Enhance_Base) # Ignores PolymorphicModel itself. self.assertIs(get_base_polymorphic_model(PolymorphicModel), None) def test_get_base_polymorphic_model_skip_abstract(self): """ Skipping abstract models that can't be used for querying. """ class A(PolymorphicModel): class Meta: abstract = True class B(A): pass class C(B): pass self.assertIs(get_base_polymorphic_model(A), None) self.assertIs(get_base_polymorphic_model(B), B) self.assertIs(get_base_polymorphic_model(C), B) self.assertIs(get_base_polymorphic_model(C, allow_abstract=True), A) django-polymorphic-2.1.2/polymorphic/utils.py000066400000000000000000000046351351315731100214100ustar00rootroot00000000000000import sys from django.contrib.contenttypes.models import ContentType from django.db import DEFAULT_DB_ALIAS from polymorphic.base import PolymorphicModelBase from polymorphic.models import PolymorphicModel def reset_polymorphic_ctype(*models, **filters): """ Set the polymorphic content-type ID field to the proper model Sort the ``*models`` from base class to descending class, to make sure the content types are properly assigned. Add ``preserve_existing=True`` to skip models which already have a polymorphic content type. """ using = filters.pop("using", DEFAULT_DB_ALIAS) ignore_existing = filters.pop("ignore_existing", False) models = sort_by_subclass(*models) if ignore_existing: # When excluding models, make sure we don't ignore the models we # just assigned the an content type to. hence, start with child first. models = reversed(models) for new_model in models: new_ct = ContentType.objects.db_manager(using).get_for_model( new_model, for_concrete_model=False ) qs = new_model.objects.db_manager(using) if ignore_existing: qs = qs.filter(polymorphic_ctype__isnull=True) if filters: qs = qs.filter(**filters) qs.update(polymorphic_ctype=new_ct) def _compare_mro(cls1, cls2): if cls1 is cls2: return 0 try: index1 = cls1.mro().index(cls2) except ValueError: return -1 # cls2 not inherited by 1 try: index2 = cls2.mro().index(cls1) except ValueError: return 1 # cls1 not inherited by 2 return (index1 > index2) - (index1 < index2) # python 3 compatible cmp. def sort_by_subclass(*classes): """ Sort a series of models by their inheritance order. """ if sys.version_info[0] == 2: return sorted(classes, cmp=_compare_mro) else: from functools import cmp_to_key return sorted(classes, key=cmp_to_key(_compare_mro)) def get_base_polymorphic_model(ChildModel, allow_abstract=False): """ First the first concrete model in the inheritance chain that inherited from the PolymorphicModel. """ for Model in reversed(ChildModel.mro()): if ( isinstance(Model, PolymorphicModelBase) and Model is not PolymorphicModel and (allow_abstract or not Model._meta.abstract) ): return Model return None django-polymorphic-2.1.2/runtests.py000077500000000000000000000061641351315731100175740ustar00rootroot00000000000000#!/usr/bin/env python -Wd import sys import warnings from os.path import abspath, dirname import dj_database_url import django from django.conf import settings from django.core.management import execute_from_command_line # python -Wd, or run via coverage: warnings.simplefilter("always", DeprecationWarning) # 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(), dirname(abspath(django.__file__)) ) ) if not settings.configured: settings.configure( DEBUG=False, DATABASES={ "default": dj_database_url.config( env="PRIMARY_DATABASE", default="sqlite://:memory:" ), "secondary": dj_database_url.config( env="SECONDARY_DATABASE", default="sqlite://:memory:" ), }, TEST_RUNNER="django.test.runner.DiscoverRunner", INSTALLED_APPS=( "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.messages", "django.contrib.sessions", "django.contrib.sites", "django.contrib.admin", "polymorphic", "polymorphic.tests", ), MIDDLEWARE=( "django.middleware.common.CommonMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", ), SITE_ID=3, TEMPLATES=[ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": (), "OPTIONS": { "loaders": ( "django.template.loaders.filesystem.Loader", "django.template.loaders.app_directories.Loader", ), "context_processors": ( "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.request", "django.template.context_processors.static", "django.contrib.messages.context_processors.messages", "django.contrib.auth.context_processors.auth", ), }, } ], POLYMORPHIC_TEST_SWAPPABLE="polymorphic.swappedmodel", ROOT_URLCONF=None, ) 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-2.1.2/setup.cfg000066400000000000000000000022411351315731100171410ustar00rootroot00000000000000[metadata] name = django-polymorphic version = 2.1.2 description = Seamless polymorphic inheritance for Django models long_description = file:README.rst author = Bert Constantin author_email = bert.constantin@gmx.de maintainer = Christopher Glass maintainer_email = tribaal@gmail.com url = https://github.com/django-polymorphic/django-polymorphic download_url = https://github.com/django-polymorphic/django-polymorphic/tarball/master keywords = django, polymorphic classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django Framework :: Django :: 1.11 Framework :: Django :: 2.0 Framework :: Django :: 2.1 Framework :: Django :: 2.2 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Topic :: Database [options] packages = find: include_package_data = True install_requires = Django >= 1.11 [options.packages.find] exclude = polymorphic.tests [bdist_wheel] universal = 1 django-polymorphic-2.1.2/setup.py000077500000000000000000000000751351315731100170400ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup setup() django-polymorphic-2.1.2/tox.ini000066400000000000000000000014501351315731100166340ustar00rootroot00000000000000[tox] envlist = py27-django{111} py35-django{111,20} py36-django{111,20,21,22,master} py37-django{21,22,master} docs [testenv] setenv = PYTHONWARNINGS = all postgres: DEFAULT_DATABASE = postgres:///default postgres: SECONDARY_DATABASE = postgres:///secondary deps = coverage dj-database-url django111: Django >= 1.11, < 2.0 django20: Django ~= 2.0 django21: Django ~= 2.1 django22: Django ~= 2.2 djangomaster: https://github.com/django/django/archive/master.tar.gz postgres: psycopg2 commands = coverage run --source polymorphic runtests.py [testenv:docs] deps = Sphinx sphinx_rtd_theme -r{toxinidir}/docs/_ext/djangodummy/requirements.txt changedir = docs commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html [flake8] # ignore line size max-line-length = 300