I'm a django novice. It's my first Django project. As you can guess it from the title of the question, my problem is a bit long to explain.
In short: I want to know how to make a basic Admin Inline display for a ManyToMany relationship with intermediate polymorphic model. I'm having an TemplateDoesNotExist at /admin/blu_forms/contactentry/e6529155-82a3-43b6-9e14-34951c88471a/change/
error.
The initial idea is simple: I have two classes ContactEntry and ContactField. A contact entry can have many fields and vice-versa, so we have a ManyToMany relationship between the two. Since I want to store typed data in this relationship (because a given field is linked to typed data for a given entry - field relation) so I use an intermediate model (through model) to hold that data. The correct typing is handled with Polymorphism (django-polymorphic).
This works well. I made a custom script to populate the database (PostgreSQL) and it works nicely.
My issue is with the admin website. When I register ContactEntry and ContactField, I see the list of data, but when I click to see the details of a specific instance, I get an error.
The correct lists of instances:
And the error I get when I click for instance on a ContactEntry instance:
Below is my code in my project/app/admin.py
:
from django.contrib import admin
from polymorphic.admin import PolymorphicParentModelAdmin
from polymorphic.admin import PolymorphicChildModelAdmin
from polymorphic.admin import PolymorphicChildModelFilter
from polymorphic.admin import StackedPolymorphicInline
from polymorphic.admin import PolymorphicInlineSupportMixin
from .models import *
# TODO: complete admin with ManyToMany relationship
class ContactFieldDataInline(StackedPolymorphicInline):
model = ContactFieldData
class BooleanContactFieldDataInline(StackedPolymorphicInline.Child):
model = BooleanContactFieldData
class CharContactFieldDataInline(StackedPolymorphicInline.Child):
model = CharContactFieldData
class TextContactFieldDataInline(StackedPolymorphicInline.Child):
model = TextContactFieldData
class IntegerContactFieldDataInline(StackedPolymorphicInline.Child):
model = IntegerContactFieldData
class FloatContactFieldDataInline(StackedPolymorphicInline.Child):
model = FloatContactFieldData
class DateContactFieldDataInline(StackedPolymorphicInline.Child):
model = DateContactFieldData
class DateTimeContactFieldDataInline(StackedPolymorphicInline.Child):
model = DateTimeContactFieldData
class EmailContactFieldDataInline(StackedPolymorphicInline.Child):
model = EmailContactFieldData
class URLContactFieldDataInline(StackedPolymorphicInline.Child):
model = URLContactFieldData
child_inlines = [
BooleanContactFieldDataInline,
CharContactFieldDataInline,
TextContactFieldDataInline,
IntegerContactFieldDataInline,
FloatContactFieldDataInline,
DateContactFieldDataInline,
DateTimeContactFieldDataInline,
EmailContactFieldDataInline,
URLContactFieldDataInline
]
class ContactFieldAdmin(
PolymorphicInlineSupportMixin,
admin.ModelAdmin
):
inlines = [ContactFieldDataInline]
class ContactEntryAdmin(
PolymorphicInlineSupportMixin,
admin.ModelAdmin
):
inlines = [ContactFieldDataInline]
admin.site.register(ContactEntry, ContactEntryAdmin)
admin.site.register(ContactField, ContactFieldAdmin)
And my project/app/models.py
:
import datetime
import uuid
from django.db import models
from django.utils import timezone
from django.contrib import admin
from polymorphic.models import PolymorphicModel
class ContactField(models.Model):
"""
A Blumorpho custom field.
"""
uuid = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False
)
# mandatory fields
name = models.CharField(
unique=True, max_length=100
)
#other_names = models.JSONField(null=True, blank=True)
# generated
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class ContactEntry(models.Model):
"""
Blumorpho contact entry containing
fields with information about companies
and startups.
"""
uuid = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False
)
# mandatory fields (cannot be missing)
# NOTE: defaults: null=False, blank=False
entry_id = models.IntegerField(unique=True)
entry_title = models.CharField(max_length=200)
entry_date = models.DateTimeField()
# generated fields
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# relations
contact_field_datas = models.ManyToManyField(
'ContactField',
related_name='contact_entries',
through='ContactFieldData'
)
# TODO: abstract causes error
def __str__(self):
return f"ContactEntry[id: {self.entry_id}] " + \
f"title: {self.entry_title}"
class ContactFieldData(PolymorphicModel):
"""
Intermediate model for storing value for a
ManyToMany ContactEntry to ContactField relationship.
NOTE: Does not allow duplicates.
NOTE: Cannot be abstract. Using polymorphic models.
"""
# mandatory fields
contact_field = models.ForeignKey(
ContactField, on_delete=models.CASCADE
)
contact_entry = models.ForeignKey(
ContactEntry, on_delete=models.CASCADE
)
field_number = models.IntegerField()
def __str__(self):
return f"FieldData[{self.contact_field.name}, " + \
f"nb: {self.field_number}]: " + \
f"{self.value}"
class Meta:
#abstract = True # cannot be abstract for polymorphic models
unique_together = ('contact_field', 'contact_entry')
class BooleanContactFieldData(ContactFieldData):
value = models.BooleanField(default=False)
class CharContactFieldData(ContactFieldData):
value = models.CharField(max_length=200)
class TextContactFieldData(ContactFieldData):
value = models.TextField()
class IntegerContactFieldData(ContactFieldData):
value = models.IntegerField()
class FloatContactFieldData(ContactFieldData):
value = models.FloatField()
class DateContactFieldData(ContactFieldData):
value = models.DateField()
class DateTimeContactFieldData(ContactFieldData):
value = models.DateTimeField()
class EmailContactFieldData(ContactFieldData):
value = models.EmailField(max_length=200)
class URLContactFieldData(ContactFieldData):
value = models.URLField(max_length=300)
And the full error message:
Environment:
Request Method: GET
Request URL: http://127.0.0.1:8000/admin/blu_forms/contactentry/e6529155-82a3-43b6-9e14-34951c88471a/change/
Django Version: 4.1
Python Version: 3.10.4
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blu_forms.apps.BluFormsConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware']
Template loader postmortem
Django tried loading these templates, in this order:
Using engine Django:
This engine did not provide a list of tried templates.
Template error:
In template /home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/contrib/admin/templates/admin/change_form.html, error at line 58
admin/polymorphic/edit_inline/stacked.html
48 : {% block field_sets %}
49 : {% for fieldset in adminform %}
50 : {% include "admin/includes/fieldset.html" %}
51 : {% endfor %}
52 : {% endblock %}
53 :
54 : {% block after_field_sets %}{% endblock %}
55 :
56 : {% block inline_field_sets %}
57 : {% for inline_admin_formset in inline_admin_formsets %}
58 : {% include inline_admin_formset.opts.template %}
59 : {% endfor %}
60 : {% endblock %}
61 :
62 : {% block after_related_objects %}{% endblock %}
63 :
64 : {% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
65 :
66 : {% block admin_change_form_document_ready %}
67 : <script id="django-admin-form-add-constants"
68 : src="{% static 'admin/js/change_form.js' %}"
Traceback (most recent call last):
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/backends/django.py", line 62, in render
return self.template.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 175, in render
return self._render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 167, in _render
return self.nodelist.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in render
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
return self.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/loader_tags.py", line 157, in render
return compiled_parent._render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 167, in _render
return self.nodelist.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in render
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
return self.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/loader_tags.py", line 157, in render
return compiled_parent._render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 167, in _render
return self.nodelist.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in render
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
return self.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/loader_tags.py", line 63, in render
result = block.nodelist.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in render
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
return self.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/loader_tags.py", line 63, in render
result = block.nodelist.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in render
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 1005, in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in self]))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
return self.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/defaulttags.py", line 238, in render
nodelist.append(node.render_annotated(context))
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
return self.render(context)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/loader_tags.py", line 197, in render
template = context.template.engine.select_template(template_name)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/engine.py", line 212, in select_template
raise TemplateDoesNotExist(", ".join(not_found))
The above exception (admin/polymorphic/edit_inline/stacked.html) was the direct cause of the following exception:
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/core/handlers/base.py", line 220, in _get_response
response = response.render()
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/response.py", line 114, in render
self.content = self.rendered_content
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/response.py", line 92, in rendered_content
return template.render(context, self._request)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/backends/django.py", line 64, in render
reraise(exc, self.backend)
File "/home/onyr/anaconda3/envs/django/lib/python3.10/site-packages/django/template/backends/django.py", line 85, in reraise
raise new from exc
Exception Type: TemplateDoesNotExist at /admin/blu_forms/contactentry/e6529155-82a3-43b6-9e14-34951c88471a/change/
Exception Value: admin/polymorphic/edit_inline/stacked.html
I have read ManyToMany relationship on Django Model doc, django-polymorphic admin integration and especially Working with many-to-many intermediary models or searched StackOverflow for an answer but I still don't know how to fix the error. I also stumbled upon this unfixed issue but I'm not sure what to conclude from it...
I have the feeling I should try creating my own template but have no idea how to do it. Thanks for any help.
Note: I know I could remove the use of django-polymorphic
and just make a ManyToMany relation to every derived intermediate object... but it's not my goal. I really do wish to be using "real" polymorphism because it will save me a lot of work in the future, especially while dealing with templating.
Ok... the answer is really short...
Just add polymorphic
to INSTALLED_APPS in settings.py
.
Just like in this settings.py
example:
INSTALLED_APPS = [
'django.contrib.admin',
...
"polymorphic",
]