Opened 3 months ago

Closed 3 months ago

#33703 closed Uncategorized (invalid)

ModelAdmin.get_fields wrong work with ModelAdmin.fieldsets statement

Reported by: Maxim Danilov Owned by: nobody
Component: contrib.admin Version: 4.0
Severity: Normal Keywords: admin, modeladmin
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Maxim Danilov)

almost all my issue is "not fixed", but i try

I found a bug: ModelAdmin.get_fields works wrong with fieldset statement.

How to reproduce it:

from django.db import models
from django.contrib.admin import ModelAdmin

class MyModel(models.Model):
    title = models.CharField('Meta Title of object', max_length=80, blank=True)
    slug = models.CharField('Meta Slug of object', max_length=80, blank=True)

class MyModelAdmin1(ModelAdmin):

    fields = ('title', )


class MyModelAdmin2(ModelAdmin):
    fieldset = ('title', )


print(MyModelAdmin1(MyModel, None).get_fields(None))
print(MyModelAdmin2(MyModel, None).get_fields(None))

save as test.py, in new or old project.
python manage.py test output:

('title',)                                                                                                                                                                                        
['title', 'slug']                                                                                                                                                                                 
Found xxx test(s).
...

Problem is nor directly in def get_fields.

    def get_fields(self, request, obj=None):
        """
        Hook for specifying fields.
        """
        if self.fields:
            return self.fields
        # _get_form_for_get_fields() is implemented in subclasses.
        form = self._get_form_for_get_fields(request, obj)
        return [*form.base_fields, *self.get_readonly_fields(request, obj)]

But in call of self._get_form_for_get_fields

    def _get_form_for_get_fields(self, request, obj):
        return self.get_form(request, obj, fields=None)

The author of _get_form_for_get_fields has forgotten how self.get_form handles "fields":

    def get_form(self, request, obj=None, change=False, **kwargs):
        """
        Return a Form class for use in the admin add view. This is used by
        add_view and change_view.
        """
        if 'fields' in kwargs:
            fields = kwargs.pop('fields')
        else:
            fields = flatten_fieldsets(self.get_fieldsets(request, obj))
        ...

I thought we had two options to fix this:

    def _get_form_for_get_fields(self, request, obj):
        return self.get_form(request, obj)

or

    def get_form(self, request, obj=None, change=False, **kwargs):
        """
        Return a Form class for use in the admin add view. This is used by
        add_view and change_view.
        """
        fields = kwargs.pop('fields', None) or []    
        if not fields:
            fields = flatten_fieldsets(self.get_fieldsets(request, obj))
        ...

BUT! My solution not works, we receive a recursion:

in get_fields
  call self._get_form_for_get_fields

in _get_form_for_get_fields
  call self.get_form

in get_form
  call self.get_fieldsets

in get_fieldsets
  call self.get_fields (go to 1. step)

p.s.
this issue is easy to create, we already have 'contrib.admin' in "component select" in Issue-Creation-Form, unlike, for example, 'contrib.gis'.
For 'contrib.gis' we suddenly use GIS in uppercase.

Attachments (1)

test.py (1.4 KB) - added by Maxim Danilov 3 months ago.
example which works for me on django 4.01

Download all attachments as: .zip

Change History (5)

Changed 3 months ago by Maxim Danilov

Attachment: test.py added

example which works for me on django 4.01

comment:1 Changed 3 months ago by Maxim Danilov

Description: modified (diff)
Easy pickings: unset
Has patch: unset

comment:2 Changed 3 months ago by Mariusz Felisiak

Resolution: invalid
Status: newclosed

Thanks for the report, however as far as I'm aware get_fields() works as intended and documented, it's a hook which allows specifying fields dynamically. It returns ModelAdmin.fields or base fields and readonly-fields, if not specified.

It shouldn't check fieldsets definition, but the other way around, fieldsets should check specified fields. That's why you're getting an infinite recursion. Moreover fields=None was added intentionally in 23e1b59cf2ad6a75637dd0273973e657e48e317e.

comment:3 Changed 3 months ago by Maxim Danilov

Resolution: invalid
Status: closednew

documentation:


ModelAdmin.get_fields
The get_fields method is given the HttpRequest and the obj being edited (or None on an add form) and is expected to return a list of fields, as described above in the ModelAdmin.fields section.

ModelAdmin.fields

Use the fields option to make simple layout changes in the forms on the “add” and “change” pages such as showing only a subset of available fields
For more complex layout needs, see the fieldsets option.


I use only .fieldsets, without .fields definition. ModelAdmin.get_fields give me a wrong list.
Why you set my ticket as invalid?

comment:4 Changed 3 months ago by Mariusz Felisiak

Resolution: invalid
Status: newclosed

Exactly as you quoted the docs:

The get_fields method is given the HttpRequest and the obj being edited (or None on an add form) and is expected to return a list of fields, as described above in the ModelAdmin.fields section.

and ModelAdmin.fields:

Use the fields option to make simple layout changes in the forms on the “add” and “change” pages such as showing only a subset of available fields For more complex layout needs, see the fieldsets option.

You didn't use ModelAdmin.fields or overwrite ModelAdmin.get_fields(), so ModelAdmin.get_fields() returned base fields and readonly-fields. ModelAdmin.get_fields() is not described as a tool for getting displayed fields, it's a hook to overwrite displayed fields when you need a conditional logic based on a request and you don't use fieldsets for a complex layout, not the other way around.

You can raise your doubts on the DevelopersMailingList to reach a wider audience and see what other think, if you don't agree. Thanks.

Note: See TracTickets for help on using tickets.
Back to Top