理解Django admin的readonly_fields

7 投票
2 回答
14410 浏览
提问于 2025-04-17 10:54

我写了一些代码,用来区分Django后台的两个用户组,这样可以让所有字段变成只读,或者只让其中一些字段变成只读,这些设置都是直接在ModelAdmin类里完成的。

首先,这是我的代码:

class PersonAdmin(admin.ModelAdmin):
    readonly_fields = ('created_at','created_by',)

def get_form(self, request, obj=None, **kwargs):
    if obj:     # we are in edit mode
        if request.user.is_superuser:
            self.readonly_fields = ()
        else:
            for group in request.user.groups.all():
                if str(group) == 'readonlyuser':
                    allfields = tuple(obj._meta.get_all_field_names())
                    self.readonly_fields = allfields

    return super(PersonAdmin, self).get_form(request, obj, **kwargs)

我根据用户组来设置字段。只要这两个组的用户没有同时登录,一切都运行得很好! 但是,一旦一个“只读”用户登录后,管理员用户也会发现所有字段都变成只读了。

经过我的检查,我找到了一个解决办法: 如果我在for循环里为管理员用户加一个额外的if语句,事情就会按预期工作。

if str(group) == 'adminuser':
    self.readonly_fields = PersonAdmin.readonly_fields

这是为什么呢?发生了什么事?

我没有特别的缓存设置,这个问题在开发服务器和使用Apache与WSGI的服务器上都出现。

根据我的理解,request.user.groups.all()应该返回当前登录用户所属的所有组。如果另一个用户在不同的IP和会话下匹配了这个if语句,Django是从哪里获取所有字段(只读)的呢?

2 个回答

1

因为在Django管理后台中,字段是在第一次运行时(也就是当网页服务器重启后)被设置好的(也就是被缓存起来了)。你可以通过重新声明readonly_fields这个元组来解决这个问题。可以试试下面这样的写法(这个没有测试过):

def get_form(self, request, obj=None, **kwargs):  
    self.readonly_fields = ('created_at','created_by',)
    # ...
16

ModelAdmin 这个东西在接收到请求时只会被创建一次。所以当你这样定义只读字段的时候,其实是把这个设置永久性地应用到了所有请求上。

只要你使用的是 Django 1.2 以上的版本,就可以用一个叫 get_readonly_fields 的方法来实现这个功能:

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            return super(MyModelAdmin, self).get_readonly_fields(request, obj)
        else:
            return ('created_at', 'created_by')

readonly_fields 属性从你的 ModelAdmin 中去掉,或者设置成对所有人都应该是只读的字段。然后,在 else 这个部分里,指定那些只对非超级用户是只读的字段。

撰写回答