**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.
* needs_tests: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:1>
* owner: nobody => Paolo Melchiorre
* status: new => assigned
* stage: Unreviewed => Accepted
Comment:
Thanks for the report. What do you think about raising `AttributeError`
instead of `FieldError`?
{{{#!diff
diff --git a/django/db/models/query_utils.py
b/django/db/models/query_utils.py
index 9754864eef..4f3358eb8d 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -199,7 +199,7 @@ class DeferredAttribute:
val = self._check_parent_chain(instance)
if val is None:
if instance.pk is None and self.field.generated:
- raise FieldError(
+ raise AttributeError(
"Cannot read a generated field from an unsaved
model."
)
instance.refresh_from_db(fields=[field_name])
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:2>
* component: contrib.admin => Database layer (models, ORM)
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:3>
Comment (by Paolo Melchiorre):
Replying to [comment:2 Mariusz Felisiak]:
> Thanks for the report. What do you think about raising `AttributeError`
instead of `FieldError`?
Mariusz I think it's a good idea.
I'm going to update the proposed patch accordingly
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:4>
* needs_tests: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:5>
* needs_tests: 0 => 1
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:6>
* needs_tests: 1 => 0
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:7>
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:8>
* status: assigned => closed
* resolution: => fixed
Comment:
In [changeset:"2f1ab16be54255213d5c3e4d925a3a24997dc917" 2f1ab16]:
{{{
#!CommitTicketReference repository=""
revision="2f1ab16be54255213d5c3e4d925a3a24997dc917"
Fixed #34842 -- Fixed ModelAdmin.readonly_fields crash with
GeneratedFields.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/34842#comment:9>