Integrations

When integrating polymorphic models into third party apps you have three primary options:

  1. Hope it just works (it might!).

  2. Ensure the querysets the third party apps see are not polymorphic.

  3. Override or extend relevant third party app code to work with polymorphic querysets.

If it does not just work, option 1 is usually the easiest. We provide some integrations in polymorphic.contrib for popular third party apps and provide guidance for others below.

This page does not exhaustively cover all integrations. If you feel your integration need is very common you may consider opening a PR to either provide support in code or documentation here.

This page covers supported and tested integration advice. For all other integration advice please refer to our integrations discussion page.

For the integration examples on this page, we use the following polymorphic model hierarchy:

 1from django.db import models
 2from polymorphic.models import PolymorphicModel
 3
 4
 5class Article(PolymorphicModel):
 6    title = models.CharField(max_length=100)
 7    content = models.TextField()
 8    created = models.DateTimeField(auto_now_add=True)
 9
10    def __str__(self):
11        return self.title
12
13
14class BlogPost(Article):
15    author = models.CharField(max_length=100)
16
17
18class NewsArticle(Article):
19    source = models.CharField(max_length=100)

django-guardian

Added in version 1.0.2.

No special modifications are required to integrate with django-guardian. However, if you would like all object level permissions to be managed at the base model level, rather than have unique permissions for each polymorphic subclass, then you can use the helper function polymorphic.contrib.guardian.get_polymorphic_base_content_type() to unify the permissions for your entire polymorphic model tree into a single namespace a the base level:

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

Added in version 1.1.

The polymorphic.contrib.extra_views package provides classes to display polymorphic formsets using the classes from django-extra-views. See the documentation of:

Tip

The complete working code for this example can be found in the extra_views integration test.

Example View

Here’s how to create a view using PolymorphicFormSetView to handle polymorphic formsets:

 1from polymorphic.contrib.extra_views import PolymorphicFormSetView
 2from polymorphic.formsets import PolymorphicFormSetChild
 3from django.urls import reverse_lazy
 4from ..models import Article, BlogPost, NewsArticle
 5
 6
 7class ArticleFormSetView(PolymorphicFormSetView):
 8    model = Article
 9    template_name = "extra_views/article_formset.html"
10    success_url = reverse_lazy("extra_views:articles")
11    fields = "__all__"
12
13    # extra will add two empty forms for models in the order of their appearance
14    # in formset_children
15    factory_kwargs = {"extra": 2, "can_delete": True}
16
17    formset_children = [
18        PolymorphicFormSetChild(BlogPost, fields="__all__"),
19        PolymorphicFormSetChild(NewsArticle, fields="__all__"),
20    ]

URL Configuration

Configure the URL patterns to route to your formset view:

1from django.urls import path
2from .views import ArticleFormSetView
3
4app_name = "extra_views"
5
6urlpatterns = [
7    path("articles/", ArticleFormSetView.as_view(), name="articles"),
8]

Template

The template for rendering the formset:

{% load extra_views_tags %}
<!DOCTYPE html>
<html>
<head>
    <title>Article Formset</title>
</head>
<body>
    <h1>Article Formset</h1>
    <form method="post">
        {% csrf_token %}
        {{ formset.management_form }}
        {% for form in formset %}
            <div class="formset-form">
                <h3>{{ form.instance|model_name }}</h3>
                {{ form.as_p }}
            </div>
        {% endfor %}
        <button type="submit">Save</button>
    </form>
</body>
</html>

model_name is a template tag implemented like so:

@register.filter
def model_name(instance):
    """Get the model class name of an instance."""
    return instance._meta.verbose_name.title()

django-reversion

Support for django-reversion works as expected with polymorphic models. We just need to do two things:

  1. Inherit our admin classes from both PolymorphicParentModelAdmin / PolymorphicChildModelAdmin and VersionAdmin.

  2. Override the admin/polymorphic/object_history.html template.

Tip

The complete working code for this example can be found in the reversion integration test.

Admin Configuration

The admin configuration combines PolymorphicParentModelAdmin and PolymorphicChildModelAdmin with VersionAdmin:

 1from django.contrib import admin
 2from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin
 3from reversion.admin import VersionAdmin
 4from ..models import Article, BlogPost, NewsArticle
 5
 6
 7class ArticleChildAdmin(PolymorphicChildModelAdmin, VersionAdmin):
 8    base_model = Article
 9
10
11@admin.register(BlogPost)
12class BlogPostAdmin(ArticleChildAdmin):
13    pass
14
15
16@admin.register(NewsArticle)
17class NewsArticleAdmin(ArticleChildAdmin):
18    pass
19
20
21class ArticleParentAdmin(VersionAdmin, PolymorphicParentModelAdmin):
22    """
23    Parent admin for Article model with reversion support.
24
25    Note: VersionAdmin must come before PolymorphicParentModelAdmin
26    in the inheritance order.
27    """
28
29    base_model = Article
30    child_models = (BlogPost, NewsArticle)
31    list_display = ("title", "created")
32
33
34admin.site.register(Article, ArticleParentAdmin)

Custom Template

Since both PolymorphicParentModelAdmin and VersionAdmin. define object_history.html template, you need to create a custom template that combines both:

{% 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 using the breadcrumb_scope tag.