Skip to main content
 首页 » 编程设计

python之如何动态地向 Django Model Admin readonly_fields 添加多个字段

2025年04月02日40telwanggs

我有一个用例,我需要检索 Django 模型管理 ListView 中每一行的状态信息。

我可以使用如下代码检索数据:

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

但是,这个“Blah”类会同时返回状态和进度。有没有一种简单的方法可以用实例调用这个“Blah”类,返回状态字段和进度字段,并将两者添加到 readonly_fields 元组而不重复,如:

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') 

请您参考如下方法:

我想你可以使用类装饰器。

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'] 

但我不明白你为什么要使用静态方法。

一个更高级的例子:

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'] 

当然,如果您愿意,可以将上面的示例修改为使用静态方法。


另一种解决方案是使用 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