最终,我的目标是扩展Django的ModelAdmin以提供字段级权限-即,根据请求对象的属性和正在编辑的对象的字段的值,我想控制字段/内联是否可见给用户。我最终通过向ModelAdmin添加can_view_field()方法并修改内置的get_form()get_fieldset()方法以删除/排除用户没有权限(由can_view_field()确定)的字段和内联来实现此目的。如果您想查看代码,我将其放置在in a pastebin上,因为它很长,而且只是有些相关。

效果很好...差不多。我似乎遇到了某种线程安全或缓存问题,其中ModelAdmin对象的状态正以可再现的方式从一个请求泄漏到另一个请求。

我将用一个简单的例子来说明这个问题。假设我有一个模型,该模型已使用字段级权限代码进行了扩展。此模型有两个字段:
-public_field,任何工作人员都可以查看/编辑
-secret_field,只能由 super 用户查看/编辑

在这种情况下,can_view_field()方法将如下所示:

def can_view_field(self, request, obj, field_name):
    """
    Returns boolean indicating whether the user has necessary permissions to
    view the passed field.
    """
    if obj is None:
        return request.user.has_perm('%s.%s_%s' % (
            self.opts.app_label,
            action,
            obj.__class__.__name__.lower()
        ))
    else:
        if field_name == "public_field":
            return True
        if field_name == "secret_field" and request.is_superuser:
            return True
        return False

测试案例1:在重新启动服务器的情况下,如果首先以 super 用户身份查看变更列表表单,则可以看到该表单,public_fieldsecret_field均可见。如果您注销并以工作人员(而不是 super 用户)的身份查看,则只会看到public_field

测试案例2:在重新启动服务器后,如果您首先以工作人员身份登录,您仍然只会看到public_field。但是,如果您随后注销并以 super 用户身份查看,则看不到secret_field。这是100%可重现的。

我已经做了一些基本的线程安全诊断:
  • get_form()的末尾,我已经打印出了ModelForm对象的内存地址。确实应该如此,它对于每个请求都是唯一的。因此,ModelForm对象不是问题。
  • 在管理员注册之前,我尝试打印ModelAdmin对象的内存地址。在测试案例1中,这两个请求都是唯一的。但是,对于测试用例2,它根本不会在第二个请求上打印。

  • 在这一点上,我一无所知。我的下一个研究点将是管理员注册系统(我承认我对此一无所知)。服务器重启后状态会重置,因此看来必须对ModelAdmin进行缓存吗?还是线程安全问题?如果我将其变成工厂并返回ModelAdmin的deepcopy(),它将为每个请求提供新的ModelAdmin吗?我一无所知,不胜感激。谢谢!

    最佳答案

    我对为什么您认为ModelAdmin应该是每个请求的新实例感到困惑。管理对象由每个admin.py中的admin.site.register(Model)调用实例化,而urlt.py中的admin.autodiscover()依次调用该对象。换句话说,这是在进程启动时发生的。考虑到大多数Web服务环境的动态多进程性质,您可能会或可能不会因任何特定请求而获得一个新进程-当然,您不会每次都获得一个新进程。

    因此,在诸如ModelAdmin之类的全局对象上存储或更改状态是不明智的。我没有正确浏览您的链接代码,但是至少有一种情况是您由于方法调用而更改了self上的属性。不要这样做-您需要找到其他在方法之间传递动态值的方法。

    关于python - ModelAdmin线程安全/缓存问题,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/3388111/

    10-09 23:04