简体   繁体   中英

How to add multiple fields to Django Model Admin readonly_fields dynamically

I have a use case where I need to retrieve status information for each row in the Django model admin list view.

I can retrieve data using code like:

def blah(admin.ModelAdmin):
    @staticmethod
    def status(instance):
      return Blah(instance).get_info()['status']
       
    readonly_fields = ('id', 'status')

However, this 'Blah' class returns both the status and progress. Is there an easy way to call this 'Blah' class with the instance, return the status field and also a progress field and add both to the readonly_fields tuple without duplication like:

def blah(admin.ModelAdmin):
    @staticmethod
    def status(instance):
      return Blah(instance).get_info()['status']

    @staticmethod
    def progress(instance):
      return Blah(instance).get_info()['progress']
       
    readonly_fields = ('id', 'status', 'progress')

I think you may use a class decorator.

def get_blah_info(field):
    return staticmethod(lambda x: Blah(x).get_info()[field])

def blah_decorator(*fields):
    def wrapper(cls):
        for field in fields:
            setattr(cls, field, get_blah_info(field))
            cls.readonly_fields.append(field)
        return cls
    return wrapper

@blah_decorator('status', 'progress')
class BlahAdmin(admin.ModelAdmin):
    readonly_fields = ['id']

But I don't catch why you are using a static method.

A more advanced example:

from django.utils.translation import ugettext_lazy as _

def get_blah_info(blah_class, field):
    def get_info(self, instance):
        return blah_class(instance).get_info()[field]
    return get_info

def blah_decorator(blah_class, **fields):
    def wrapper(cls):
        # Make sure readonly_fields is a list so that we can append elements
        readonly_fields = getattr(cls, 'readonly_fields', [])
        if not hasattr(readonly_fields, 'append'):
            readonly_fields = list(readonly_fields)

        for field, short_description in fields.items():
            # Define the method for each field and append it to readonly_fields
            get_info = get_blah_info(blah_class, field)
            get_info.__name__ = field
            get_info.short_description = short_description
            setattr(cls, field, get_info)
            readonly_fields.append(field)
        cls.readonly_fields = readonly_fields
        return cls
    return wrapper

@blah_decorator(Blah, status=_("Status"), progress=_("Progress"))
class BlahAdmin(admin.ModelAdmin):
    readonly_fields = ['id']

Of course, the above example can be adapted to use static methods if you prefer.


Another solution would be to use a metaclass .

class BlahMetaclass(type):

    @staticmethod
    def get_blah_info(blah_class, field):
        def get_info(self, instance):
            return blah_class(instance).get_info()[field]
        return get_info

    def __new__(cls, cls_name, bases, attrs):
        blah_class = attrs['blah_class']
        blah_fields = attrs['blah_fields']
        readonly_fields = attrs.get('readonly_fields', [])
        if not hasattr(readonly_fields, 'append'):
            readonly_fields = list(readonly_fields)

        for field, short_description in blah_fields:
            if field in attrs:
                continue  # Let the class have the precedence
            get_info = cls.get_blah_info(blah_class, field)
            get_info.__name__ = field
            get_info.short_description = short_description
            attrs[field] = get_info
            if field not in readonly_fields:
                # Do not add `field` to `readonly_fields` if it is already present.
                # This enables to redefine the fields order rather than
                # appending `blah_fields`.
                readonly_fields.append(readonly_fields)

        attrs['readonly_fields'] = readonly_fields

        # Optionally remove `blah_class` and `blah_fields` if
        # not useful any further.
        del attrs['blah_class']
        del attrs['blah_fields']

        return super().__new__(cls, clsname, bases, attrs)


class BlahModelAdmin(admin.ModelAdmin, metaclass=BlahMetaclass):
    """Optionally, create a new base ModelAdmin."""


class BlahAdmin(BlahModelAdmin):

    blah_class = Blah
    blah_fields = [
        ('status' _("Status")),
        ('progress', _("Progress")),
    ]

    readonly_fields = ['id']
    # Or, for instance: readonly_fields = ['status', 'id', 'progress']
    # If you want to change the order

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM