Skip to main content
 首页 » 编程设计

python之Django Tastypie 记录级授权

2025年05月04日28yjmyzz

我在使用 tastypie 0.9.12+ 实现记录级授权时遇到问题。

我的设置看起来像这样:

型号

class UserProfile(models.Model): 
    def __unicode__(self): 
        return self.user.get_full_name() 
 
    user = models.OneToOneField(User) 
 
class Sample(models.Model): 
    def __unicode__(self): 
        return '%s' % self.id 
 
    OPEN = 0 
    CLAIMED = 1 
    CLOSED = 2 
    MANUAL = 3 
    MODIFIED = 4 
    DELETED = 5 
    ERROR = 6 
    RESERVED = 7 
    STATUS_CHOICES = ( 
        (OPEN, 'Open'), 
        (CLAIMED, 'Claimed'), 
        (CLOSED, 'Closed'), 
        (MANUAL, 'Manual'), 
        (MODIFIED, 'Modified'), 
        (DELETED, 'Deleted'), 
        (ERROR, 'Error'), 
        (RESERVED, 'Reserved'), 
    ) 
 
    status = models.SmallIntegerField(max_length = 1, default = OPEN, choices = STATUS_CHOICES) 
    user_profile = models.ForeignKey(UserProfile, blank = True, null = True) 

资源

class BaseResource(ModelResource): 
    # Base class with rather strict default settings 
    # All other Resources extend this and override any defaults to higher permissions 
    class Meta: 
        authentication = DjangoAuthentication() 
        authorization = ReadOnlyAuthorization() 
        allowed_methods = [] 
 
class SampleResource(BaseResource): # BaseResource defines a default Meta, setting allowed_methods and authentication for all other resources in the API 
    UserProfile = fields.ForeignKey(UserProfileResource, 'user_profile', null = True, full = True) 
    class Meta(BaseResource.Meta): 
        queryset = Sample.objects.all() 
        resource_name = 'sample' 
        allowed_methods = ['get', 'post', 'put', 'patch'] 
        authorization = SampleAuthorization() 
        always_return_data = True 
 
    def dehydrate_status(self, bundle): 
        return Sample.STATUS_CHOICES[bundle.data['status']][1] 
 
    def hydrate_status(self, bundle): 
        bundle.data['status'] = Sample.__dict__[bundle.data['status'].upper()] 
        return bundle 

授权

class SampleAuthorization(Authorization): 
    # Checks that the records' owner is either None or the logged in user 
    def authorize_user(self, bundle): 
        return bundle.obj.user_profile in (None, self.user_profile(bundle)) 
 
    def user_profile(self, bundle): 
        return user_profile.objects.get(user = bundle.request.user) 
 
 
 
    def read_list(self, object_list, bundle): 
        print 'Read List' 
        return object_list.filter(Q(user_profile = self.user_profile(bundle)) | Q(user_profile = None)) 
 
    def read_detail(self, object_list, bundle): 
        print 'Read Detail' 
        return self.authorize_user(bundle) 
 
    def create_list(self, object_list, bundle): 
        return object_list 
 
    def create_detail(self, object_list, bundle): 
        return self.authorize_user(bundle) 
 
    def update_list(self, object_list, bundle): 
        print 'Update List' 
        allowed = [] 
        for obj in object_list: 
            if obj.user_profile in (None, self.user_profile(bundle)): 
                allowed.append(obj) 
 
        return allowed 
 
    def update_detail(self, object_list, bundle): 
        print 'Update Detail' 
        print bundle.obj.status, bundle.data['status'] 
        # Compare status stored on the server against the user-set status 
        # If server status is >= user status 
        # Raise Unauthorized 
        if bundle.obj.status >= bundle.data['status']: 
                raise Unauthorized('New status must be higher than current status') 
        return self.authorize_user(bundle) 
 
    def delete_list(self, object_list, bundle): 
        raise Unauthorized('Deletion not allowed through API') 
 
    def delete_detail(self, object_list, bundle): 
        raise Unauthorized('Deletion not allowed through API') 

我的问题是 update_detail 似乎被调用了两次,输入不同。请求的更新正试图更改存储在服务器上的记录的状态。新状态必须高于存储状态,否则更改为未授权。

运行上面的代码时,我的输出是这样的:

Read Detail 
Update Detail 
0 Claimed 
Update Detail 
1 1 
[27/Mar/2013 09:35:23] "PATCH /api/1.0/sample/1/ HTTP/1.1" 401 0 

在第一遍中,bundle.obj.status 具有正确的值,但 bundle.data['status'] 尚未被水化。在第二遍中,bundle.obj.status 已更改为新状态,并且新状态已被水化。

因为状态在第一次通过时没有被水化,我无法可靠地比较它们,并且不想手动调用 hydrate_status,因为它会扰乱后台完成的整个水化过程。因为第二次传递的值是相同的,所以无论我将其设置为什么状态,它总是会引发 Unauthorized 异常。

如果该方法被 Tastypie 调用两次,并为存储的状态值和新的状态值输入不同的值,我该如何实现记录级授权?

请您参考如下方法:

事实证明,对 update_detail 的多次调用是 tastypie 框架中的一个错误。

问题已在 github 上提交并在 bug fix 中得到解决.