Integrations¶
When integrating polymorphic models into third party apps you have three primary options:
Hope it just works (it might!).
Ensure the querysets the third party apps see are
not polymorphic.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:
Inherit our admin classes from both
PolymorphicParentModelAdmin/PolymorphicChildModelAdminand VersionAdmin.Override the
admin/polymorphic/object_history.htmltemplate.
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.