ModelAdmin线程安全/缓存问题 [英] ModelAdmin thread-safety/caching issues

查看:105
本文介绍了ModelAdmin线程安全/缓存问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最终,我的目标是扩展Django的ModelAdmin以提供字段级权限,也就是给定对象的属性和被编辑对象的字段的值,我想控制字段/线条对用户可见。我最终通过在ModelAdmin中添加一个 can_view_field()方法来完成此任务,并修改了内置的 get_form() get_fieldset()删除/排除字段的方法+用户没有权限的内联(由 can_view_field()确定))看。如果您希望看到代码,那么我将其放在在一个pastebin 中,因为它很长,只是有点相关。



几乎可以工作。我似乎遇到某种线程安全或缓存问题,其中ModelAdmin对象的状态以可重复的方式从一个请求泄露给另一个请求。



我将用一个简单的例子说明问题。假设我有一个ModelAdmin我已经通过字段级权限代码扩展的模型。该模型有两个字段:
- public_field ,可由任何员工查看/编辑
- secret_field ,只能由超级用户查看/编辑



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

  def can_view_field(self,request,obj,field_name):

返回布尔值,表示用户是否有必要的权限
查看传递的字段

如果obj为None:
return request.user.has_perm '%s。%s_%s'%(
self.opts.app_label,
action,
obj .__ class __.__ name __。lower()
))
else:
如果field_name ==public_field:
返回True
如果field_name ==secret_field和request.is_superuser:
return True
return False

测试用例1:使用新的服务器休息如果您首先以超级用户身份查看更改列表,则会看到表单应该发生,同时 public_field secret_field 可见。如果您注销并将其视为工作人员(但不是超级用户),则只会看到 public_field



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



我已经完成了一些基本的线程安全诊断:


  1. 在$ code> get_form()的末尾,我打印出了ModelForm对象的内存地址。应该是这样,它是每个请求唯一的。因此,ModelForm对象不是问题。

  2. 在管理员注册之前,我尝试打印ModelAdmin对象的内存地址。在测试用例1中,两个请求都是唯一的。但是,在测试用例2中,根据第二个请求,它根本不打印。

在这一点上,我无能为力。我的下一个研究点将是管理注册系统(我绝对不知道什么)。状态会重新启动服务器,因此似乎必须缓存ModelAdmin?还是线程安全问题?如果我把它变成一个工厂,并返回一个 deepcopy()的ModelAdmin,它会提供一个新的ModelAdmin与每个请求?我无能为力,不胜感激。谢谢!

解决方案

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



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


Ultimately, my goal is to extend Django's ModelAdmin to provide field-level permissions—that is, given properties of the request object and values of the fields of the object being edited, I would like to control whether or not the fields/inlines are visible to the user. I ultimately accomplished this by adding a can_view_field() method to the ModelAdmin and modifying the built-in get_form() and get_fieldset() methods to remove/exclude fields+inlines that the user does not have permissions (as determined by can_view_field()) to see. If you'd like to see the code, I placed it in a pastebin, since it's long and only somewhat relevant.

It works great...almost. I appear to have run into some sort of thread-safety or caching issue, where the state of the ModelAdmin object is being leaked from one request to another in a reproducible manner.

I'll illustrate the problem with a simple example. Suppose that I have a model whose ModelAdmin I have extended with the field-level permissions code. This model has two fields: - public_field, which can be seen/edited by any staff member - secret_field, which can only be seen/edited by superusers

In this case, the can_view_field() method would look like this:

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

Test case 1: with a fresh server restart, if you first view the changelist form as a superuser, you see the form as should happen, with both public_field and secret_field visible. If you log out and view it as a staff member (but not superuser), you only see public_field.

Test case 2: with a fresh server restart, if you log in as a staff member first, you still only see public_field. However, if you then log out and view as a superuser, you do not see secret_field. This is 100% reproducible.

I've done some basic thread-safety diagnostics:

  1. At the end of get_form(), I've printed out the memory address of the ModelForm object. As it should be, it is unique with each request. Therefore, the ModelForm object is not the problem.
  2. Immediately before the admin registration, I tried printing the memory address of the ModelAdmin object. In test case 1, it is unique with both requests. However with test case 2, it does not print at all on the second request.

At this point, I'm clueless. My next point of research will be the admin registration system (which I admittedly know nothing about). The state resets with a server restart, so it seems that the ModelAdmin must be cached? Or is it a thread-safety issue? If I turn it into a factory and return a deepcopy() of the ModelAdmin, would it serve a fresh ModelAdmin with each request? I'm clueless and would appreciate any thoughts. Thanks!

解决方案

I'm confused about why you think ModelAdmin should be a new instance on each request. The admin objects are instantiated by the admin.site.register(Model) calls in each admin.py, which in turn is called from admin.autodiscover() in urls.py. In other words, this happens on process startup. Given the dynamic multi-process nature of most web serving environments, you may or may not get a new process with any particular request - certainly you won't get one every single time.

Because of this, it's not wise to store or alter state on a global object like ModelAdmin. I haven't looked through your linked code properly, but there was at least one case where you were altering an attribute on self as a result of a method call. Don't do that - you'll need to find some other way of passing dynamic values between methods.

这篇关于ModelAdmin线程安全/缓存问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆