diff --git a/docs/_ext/djangodummy/__init__.py b/docs/_ext/djangodummy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/_ext/djangodummy/requirements.txt b/docs/_ext/djangodummy/requirements.txt new file mode 100644 index 0000000..b5041fb --- /dev/null +++ b/docs/_ext/djangodummy/requirements.txt @@ -0,0 +1,3 @@ +# for readthedocs +# Remaining requirements are picked up from setup.py +Django==1.5.10 diff --git a/docs/_ext/djangodummy/settings.py b/docs/_ext/djangodummy/settings.py new file mode 100644 index 0000000..414e9fa --- /dev/null +++ b/docs/_ext/djangodummy/settings.py @@ -0,0 +1,11 @@ +# 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' diff --git a/docs/_ext/djangoext/__init__.py b/docs/_ext/djangoext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/_ext/djangoext/docstrings.py b/docs/_ext/djangoext/docstrings.py new file mode 100644 index 0000000..5ae5bf1 --- /dev/null +++ b/docs/_ext/djangoext/docstrings.py @@ -0,0 +1,47 @@ +""" +Automatically mention all model fields as parameters in the model construction. +Based on http://djangosnippets.org/snippets/2533/ +""" +import django +from django.utils.html import strip_tags +from django.utils.encoding import force_unicode +import inspect + + +def improve_model_docstring(app, what, name, obj, options, lines): + from django.db import models # must be inside the function, to allow settings initialization first. + + if inspect.isclass(obj) and issubclass(obj, models.Model): + if django.VERSION >= (1,8): + model_fields = obj._meta.get_fields() + elif django.VERSION >= (1,6): + model_fields = obj._meta.fields + else: + model_fields = obj._meta._fields() + + for field in model_fields: + help_text = strip_tags(force_unicode(field.help_text)) + verbose_name = force_unicode(field.verbose_name).capitalize() + + # Add parameter + if help_text: + lines.append(u':param %s: %s' % (field.attname, help_text)) + else: + lines.append(u':param %s: %s' % (field.attname, verbose_name)) + + # Add type + if isinstance(field, models.ForeignKey): + to = field.rel.to + lines.append(u':type %s: %s to :class:`~%s.%s`' % (field.attname, type(field).__name__, to.__module__, to.__name__)) + else: + lines.append(u':type %s: %s' % (field.attname, type(field).__name__)) + + # Return the extended docstring + return lines + +# Allow this module to be used as sphinx extension: +def setup(app): + # Generate docstrings for Django model fields + # Register the docstring processor with sphinx + app.connect('autodoc-process-docstring', improve_model_docstring) + diff --git a/docs/_ext/djangoext/roles.py b/docs/_ext/djangoext/roles.py new file mode 100644 index 0000000..b5ed3e1 --- /dev/null +++ b/docs/_ext/djangoext/roles.py @@ -0,0 +1,8 @@ +# Allow :django:setting:`SITE_ID` to work. + +def setup(app): + app.add_crossref_type( + directivename = "setting", + rolename = "setting", + indextemplate = "pair: %s; setting", + ) diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..935762d --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,13 @@ +API Documentation +================= + +.. toctree:: + + polymorphic.admin + polymorphic.contrib.extra_views + polymorphic.contrib.guardian + polymorphic.formsets + polymorphic.managers + polymorphic.models + polymorphic.templatetags + polymorphic.utils diff --git a/docs/api/polymorphic.admin.rst b/docs/api/polymorphic.admin.rst new file mode 100644 index 0000000..aadabee --- /dev/null +++ b/docs/api/polymorphic.admin.rst @@ -0,0 +1,93 @@ +polymorphic.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: + diff --git a/docs/api/polymorphic.contrib.extra_views.rst b/docs/api/polymorphic.contrib.extra_views.rst new file mode 100644 index 0000000..b30133f --- /dev/null +++ b/docs/api/polymorphic.contrib.extra_views.rst @@ -0,0 +1,7 @@ +polymorphic.contrib.extra_views +=============================== + +.. automodule:: polymorphic.contrib.extra_views + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/polymorphic.contrib.guardian.rst b/docs/api/polymorphic.contrib.guardian.rst new file mode 100644 index 0000000..87a171b --- /dev/null +++ b/docs/api/polymorphic.contrib.guardian.rst @@ -0,0 +1,7 @@ +polymorphic.contrib.guardian +============================ + +.. automodule:: polymorphic.contrib.guardian + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/polymorphic.formsets.rst b/docs/api/polymorphic.formsets.rst new file mode 100644 index 0000000..abc48d2 --- /dev/null +++ b/docs/api/polymorphic.formsets.rst @@ -0,0 +1,41 @@ +polymorphic.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: diff --git a/docs/api/polymorphic.managers.rst b/docs/api/polymorphic.managers.rst new file mode 100644 index 0000000..d7cc448 --- /dev/null +++ b/docs/api/polymorphic.managers.rst @@ -0,0 +1,21 @@ +polymorphic.managers +==================== + +.. automodule:: polymorphic.managers + + +The ``PolymorphicManager`` class +-------------------------------- + + +.. autoclass:: polymorphic.managers.PolymorphicManager + :members: + :show-inheritance: + + +The ``PolymorphicQuerySet`` class +--------------------------------- + +.. autoclass:: polymorphic.managers.PolymorphicQuerySet + :members: + :show-inheritance: diff --git a/docs/api/polymorphic.models.rst b/docs/api/polymorphic.models.rst new file mode 100644 index 0000000..8bf3949 --- /dev/null +++ b/docs/api/polymorphic.models.rst @@ -0,0 +1,8 @@ +polymorphic.models +================== + +.. automodule:: polymorphic.models + +.. autoclass:: polymorphic.models.PolymorphicModel + :members: + :show-inheritance: diff --git a/docs/api/polymorphic.templatetags.rst b/docs/api/polymorphic.templatetags.rst new file mode 100644 index 0000000..dde9cc7 --- /dev/null +++ b/docs/api/polymorphic.templatetags.rst @@ -0,0 +1,4 @@ +polymorphic.templatetags.polymorphic_admin_tags +=============================================== + +.. automodule:: polymorphic.templatetags diff --git a/docs/api/polymorphic.utils.rst b/docs/api/polymorphic.utils.rst new file mode 100644 index 0000000..97fcfc6 --- /dev/null +++ b/docs/api/polymorphic.utils.rst @@ -0,0 +1,5 @@ +polymorphic.utils +================= + +.. automodule:: polymorphic.utils + :members: diff --git a/docs/conf.py b/docs/conf.py index f14fa5f..155cccc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,6 +13,8 @@ import sys import os +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 @@ -21,6 +23,9 @@ sys.path.insert(0, os.path.abspath('_ext')) sys.path.insert(0, os.path.abspath('..')) os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings' +if django.VERSION >= (1, 8): + django.setup() + # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -31,7 +36,9 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'djangodummy.settings' extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.graphviz', - 'sphinx.ext.intersphinx' + 'sphinx.ext.intersphinx', + 'djangoext.docstrings', + 'djangoext.roles', ] # Add any paths that contain templates here, relative to this directory. @@ -98,7 +105,8 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -#html_theme = 'alabaster' +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 @@ -252,5 +260,8 @@ texinfo_documents = [ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { #'http://docs.python.org/': None, - 'https://docs.djangoproject.com/en/dev': 'https://docs.djangoproject.com/en/dev/_objects', + 'https://docs.djangoproject.com/en/stable': 'https://docs.djangoproject.com/en/stable/_objects', } + +# autodoc settings +autodoc_member_order = 'groupwise' diff --git a/docs/index.rst b/docs/index.rst index f648091..e2c1769 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,8 +1,9 @@ Welcome to django-polymorphic's documentation! ============================================== -Django-polymorphic simplifies using inherited models in Django projects. -When a query is made at the base model, the inherited model classes are returned. +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... @@ -43,6 +44,7 @@ Features * 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. @@ -56,6 +58,7 @@ Getting started quickstart admin performance + third-party Advanced topics --------------- @@ -67,9 +70,10 @@ Advanced topics migrating managers advanced - third-party changelog contributing + api/index + Indices and tables ================== diff --git a/docs/third-party.rst b/docs/third-party.rst index 837f21f..f1d412a 100644 --- a/docs/third-party.rst +++ b/docs/third-party.rst @@ -2,6 +2,35 @@ Third-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-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 ------------------- @@ -97,24 +126,9 @@ This doesn't work, since it needs to look for revisions of the child model. Usin the view of the actual child model is used, similar to the way the regular change and delete views are redirected. -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-reversion: https://github.com/etianen/django-reversion -.. _django-reversion-compare: https://github.com/jedie/django-reversion-compare +.. _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-guardian: https://github.com/django-guardian/django-guardian +.. _django-reversion-compare: https://github.com/jedie/django-reversion-compare +.. _django-reversion: https://github.com/etianen/django-reversion diff --git a/polymorphic/admin/generic.py b/polymorphic/admin/generic.py index 747be67..bd3453f 100644 --- a/polymorphic/admin/generic.py +++ b/polymorphic/admin/generic.py @@ -10,6 +10,7 @@ class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInl """ Base class for variation of inlines based on generic foreign keys. """ + #: The formset class formset = BaseGenericPolymorphicInlineFormSet def get_formset(self, request, obj=None, **kwargs): @@ -58,4 +59,5 @@ class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin): """ The stacked layout for generic inlines. """ + #: The default template to use. template = 'admin/polymorphic/edit_inline/stacked.html' diff --git a/polymorphic/admin/inlines.py b/polymorphic/admin/inlines.py index 51e8821..7f64d5c 100644 --- a/polymorphic/admin/inlines.py +++ b/polymorphic/admin/inlines.py @@ -245,4 +245,5 @@ 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' diff --git a/polymorphic/formsets/__init__.py b/polymorphic/formsets/__init__.py index 5605d75..02c47ae 100644 --- a/polymorphic/formsets/__init__.py +++ b/polymorphic/formsets/__init__.py @@ -1,6 +1,4 @@ """ -Polymorphic formsets support. - 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. @@ -9,21 +7,6 @@ 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. - -See: -* :func:`polymorphic_inlineformset_factory` -* :class:`PolymorphicFormSetChild` - -The internal machinery can be used to extend the formset classes. This includes: -* :class:`BasePolymorphicModelFormSet` -* :class:`BasePolymorphicInlineFormSet` -* :func:`polymorphic_child_forms_factory` - -For generic relations, a similar set is available: -* :class:`BasePolymorphicGenericInlineFormSet` -* :class:`PolymorphicGenericFormSetChild` -* :func:`polymorphic_generic_inlineformset_factory` - """ from .models import ( BasePolymorphicModelFormSet, diff --git a/polymorphic/managers.py b/polymorphic/managers.py index 875d40b..d2e677e 100644 --- a/polymorphic/managers.py +++ b/polymorphic/managers.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -""" PolymorphicManager - Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/ +""" +The manager class for use in the models. """ from __future__ import unicode_literals import warnings @@ -14,6 +14,12 @@ except ImportError: from django.utils.encoding import python_2_unicode_compatible # Django 1.5 +__all__ = ( + 'PolymorphicManager', + 'PolymorphicQuerySet', +) + + @python_2_unicode_compatible class PolymorphicManager(models.Manager): """ diff --git a/polymorphic/models.py b/polymorphic/models.py index f99541f..03cca0d 100644 --- a/polymorphic/models.py +++ b/polymorphic/models.py @@ -1,17 +1,6 @@ # -*- coding: utf-8 -*- """ Seamless Polymorphic Inheritance for Django Models -================================================== - -Please see README.rst and DOCS.rst for further information. - -Or on the Web: -http://chrisglass.github.com/django_polymorphic/ -http://github.com/chrisglass/django_polymorphic - -Copyright: -This code and affiliated files are (C) by Bert Constantin and individual contributors. -Please see LICENSE and AUTHORS for more information. """ from __future__ import absolute_import @@ -33,18 +22,8 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): Abstract base class that provides polymorphic behaviour for any model directly or indirectly derived from it. - For usage instructions & examples please see documentation. - - PolymorphicModel declares one field for internal use (polymorphic_ctype) - and provides a polymorphic manager as the default manager - (and as 'objects'). - - PolymorphicModel overrides the save() and __init__ methods. - - If your derived class overrides any of these methods as well, then you need - to take care that you correctly call the method of the superclass, like: - - super(YourClass,self).save(*args,**kwargs) + 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) @@ -57,6 +36,7 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): abstract = True # 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, related_name='polymorphic_%(app_label)s.%(class)s_set+') @@ -69,24 +49,24 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): base_objects = models.Manager() @classmethod - def translate_polymorphic_Q_object(self_class, q): - return translate_polymorphic_Q_object(self_class, q) + def translate_polymorphic_Q_object(cls, q): + return translate_polymorphic_Q_object(cls, q) def pre_save_polymorphic(self, using=DEFAULT_DB_ALIAS): - """Normally not needed. - This function may be called manually in special use-cases. When the object - is saved for the first time, we store its real class in polymorphic_ctype. - When the object later is retrieved by PolymorphicQuerySet, it uses this - field to figure out the real class of this object - (used by PolymorphicQuerySet._get_real_instances) """ + 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): - """Overridden model save function which supports the polymorphism - functionality (through pre_save_polymorphic).""" + """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) @@ -94,7 +74,8 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): def get_real_instance_class(self): """ - Normally not needed. + 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. @@ -121,10 +102,10 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): return model def get_real_concrete_instance_class_id(self): - model_class = self.get_real_instance_class() - if model_class is None: + ct = self.get_real_concrete_instance_class() + if ct is None: return None - return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).pk + return ct.pk def get_real_concrete_instance_class(self): model_class = self.get_real_instance_class() @@ -133,11 +114,18 @@ class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)): return ContentType.objects.db_manager(self._state.db).get_for_model(model_class, for_concrete_model=True).model_class() def get_real_instance(self): - """Normally not needed. + """ + 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. - Each method call executes one db query (if necessary).""" + + .. 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 diff --git a/polymorphic/query.py b/polymorphic/query.py index 443f24f..c842414 100644 --- a/polymorphic/query.py +++ b/polymorphic/query.py @@ -63,7 +63,7 @@ class PolymorphicQuerySet(QuerySet): """ def __init__(self, *args, **kwargs): - "init our queryset object member variables" + # init our queryset object member variables 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 @@ -74,7 +74,7 @@ class PolymorphicQuerySet(QuerySet): super(PolymorphicQuerySet, self).__init__(*args, **kwargs) def _clone(self, *args, **kwargs): - "Django's _clone only copies its own variables, so we need to copy ours here" + # 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 = ( @@ -101,17 +101,17 @@ class PolymorphicQuerySet(QuerySet): 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.""" + """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.""" + """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." + # 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) # the Q objects additional_args = translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs, using=self.db) # filter_field='data' return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(q_objects) + additional_args), **kwargs) @@ -413,10 +413,10 @@ class PolymorphicQuerySet(QuerySet): By overriding it, we modify the objects that this queryset returns when it is evaluated (or its get method or other object-returning methods are called). - Here we do the same as: + Here we do the same as:: - base_result_objects=list(super(PolymorphicQuerySet, self).iterator()) - real_results=self._get_real_instances(base_result_objects) + base_result_objects = list(super(PolymorphicQuerySet, self).iterator()) + real_results = self._get_real_instances(base_result_objects) for o in real_results: yield o but it requests the objects in chunks from the database, @@ -464,6 +464,17 @@ class PolymorphicQuerySet(QuerySet): 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 not base_result_objects: base_result_objects = self diff --git a/polymorphic/templatetags/__init__.py b/polymorphic/templatetags/__init__.py index e69de29..f08e9fb 100644 --- a/polymorphic/templatetags/__init__.py +++ b/polymorphic/templatetags/__init__.py @@ -0,0 +1,16 @@ +""" +Template tags to use in the admin. + +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 %} +"""