#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.

Reply via email to