#34842: Unmanaged read-only generated fields in admin
-------------------------------------+-------------------------------------
Reporter: Paolo | Owner: nobody
Melchiorre |
Type: Bug | Status: new
Component: | Version: dev
contrib.admin |
Severity: Release | Keywords: field, database,
blocker | generated, admin
Triage Stage: | Has patch: 1
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
Using read-only generated fields in the admin breaks the add template
instance page.
**Model**
{{{
from from django.db import models
class Square(models.Model):
side = models.IntegerField()
area = models.GeneratedField(expression=F("side") * F("side"),
db_persist=True)
}}}
**Admin**
{{{
from django.contrib import admin
from .models import Square
@admin.register(Square)
class SquareAdmin(admin.ModelAdmin):
readonly_fields = ("area",)
}}}
**Steps**
1) Open the creation page (es:
http://localhost:8000/admin/geometricfigures/square/add/)
**Traceback**
{{{
Environment:
Request Method: GET
Request URL: http://localhost:8000/admin/geometricfigures/square/add/
Django Version: 5.0.dev20230915033643
Python Version: 3.11.4
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.postgres',
'django.contrib.gis',
'geometricfigures']
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 error:
In template
/home/paulox/Projects/django/django/contrib/admin/templates/admin/includes/fieldset.html,
error at line 18
Cannot read a generated field from an unsaved model.
8 : {% if line.fields|length == 1 %}{{ line.errors }}{%
else %}<div class="flex-container form-multiline">{% endif %}
9 : {% for field in line %}
10 : <div>
11 : {% if not line.fields|length == 1 and not
field.is_readonly %}{{ field.errors }}{% endif %}
12 : <div class="flex-container{% if not
line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{
field.field.name }}{% endif %}{% if not field.is_readonly and field.errors
%} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{%
elif field.is_checkbox %} checkbox-row{% endif %}">
13 : {% if field.is_checkbox %}
14 : {{ field.field }}{{
field.label_tag }}
15 : {% else %}
16 : {{ field.label_tag }}
17 : {% if field.is_readonly %}
18 : <div class="readonly"> {{
field.contents }} </div>
19 : {% else %}
20 : {{ field.field }}
21 : {% endif %}
22 : {% endif %}
23 : </div>
24 : {% if field.field.help_text %}
25 : <div class="help"{% if
field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{%
endif %}>
26 : <div>{{ field.field.help_text|safe
}}</div>
27 : </div>
28 : {% endif %}
Traceback (most recent call last):
File "/home/paulox/Projects/django/django/core/handlers/exception.py",
line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/core/handlers/base.py", line
220, in _get_response
response = response.render()
^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/response.py", line
114, in render
self.content = self.rendered_content
^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/response.py", line
92, in rendered_content
return template.render(context, self._request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/backends/django.py",
line 61, in render
return self.template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 171,
in render
return self._render(context)
^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 163,
in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/loader_tags.py", line
159, in render
return compiled_parent._render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 163,
in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/loader_tags.py", line
159, in render
return compiled_parent._render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 163,
in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/loader_tags.py", line
65, in render
result = block.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/loader_tags.py", line
65, in render
result = block.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/defaulttags.py", line
241, in render
nodelist.append(node.render_annotated(context))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/loader_tags.py", line
210, in render
return template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 173,
in render
return self._render(context)
^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 163,
in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/defaulttags.py", line
241, in render
nodelist.append(node.render_annotated(context))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/defaulttags.py", line
241, in render
nodelist.append(node.render_annotated(context))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/defaulttags.py", line
325, in render
return nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/defaulttags.py", line
325, in render
return nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in render
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1000,
in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in
self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 961,
in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 1059,
in render
output = self.filter_expression.resolve(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 710,
in resolve
obj = self.var.resolve(context)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 842,
in resolve
value = self._resolve_lookup(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/template/base.py", line 909,
in _resolve_lookup
current = current()
^^^^^^^^^
File "/home/paulox/Projects/django/django/contrib/admin/helpers.py",
line 271, in contents
f, attr, value = lookup_field(field, obj, model_admin)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/contrib/admin/utils.py", line
308, in lookup_field
value = getattr(obj, name)
^^^^^^^^^^^^^^^^^^
File "/home/paulox/Projects/django/django/db/models/query_utils.py",
line 202, in __get__
raise FieldError(
^
Exception Type: FieldError at /admin/geometricfigures/square/add/
Exception Value: Cannot read a generated field from an unsaved model.
}}}
**Patch**
{{{
diff --git a/django/contrib/admin/helpers.py
b/django/contrib/admin/helpers.py
index 90ca7affc8..f7e45b408c 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -9,7 +9,7 @@ from django.contrib.admin.utils import (
lookup_field,
quote,
)
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import FieldError, ObjectDoesNotExist
from django.db.models.fields.related import (
ForeignObjectRel,
ManyToManyRel,
@@ -268,7 +268,7 @@ class AdminReadonlyField:
)
try:
f, attr, value = lookup_field(field, obj, model_admin)
- except (AttributeError, ValueError, ObjectDoesNotExist):
+ except (AttributeError, ValueError, ObjectDoesNotExist,
FieldError):
result_repr = self.empty_value_display
else:
if field in self.form.fields:
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/34842>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/0107018a9877cd9c-a9229e90-1c6c-45f8-aabb-7bba58f6b11f-000000%40eu-central-1.amazonses.com.