开发者

Python - make class decorator work on derived classes

开发者 https://www.devze.com 2023-03-14 01:40 出处:网络
In the app we\'re developing using Django, in some cases we need to automatically assign permissions to users for some models, that has owners (there is no rule for field\'s name, it can be \"user\",

In the app we're developing using Django, in some cases we need to automatically assign permissions to users for some models, that has owners (there is no rule for field's name, it can be "user", "owner", "coach" etc., also there can by more than one field.) My solution is to create a decorator containing those fields names, that will be put before model definition, like this (not using django-specific code in samples):

@auto_assign_perms('owner', 'user')
class Test(Base):
    pass

Let's assume that Base is an abstract class deriving after Django's Model class, where I add functionality to assign permissions after object is saved. For now I only print a list of users assigned to the class. Below you can find code for the decorator and Base class:

class auto_assign_perms(object):
    def __init__(self, *users):
        self.users = users

    def __call__(self, cls):
        cls.owners.update(self.users)
        return cls


class Base(object):
    owners = set()

    def save(self, *args, **kwargs):
        for owner in self.owners:
            print owner,
        print

And my models could look like this:

@auto_assign_perms('owner', 'user')
class Test(Base):
    pass

@auto_assign_perms('coach')
class Test2(Base):
    pass

The problem is that both child classes contains all three fields ('owner', 'user', 'coach'), altough print self.__class__.__name__ in Base.save() method properly shows "Test" or "Test2". I tried to add classmethod get_owners() in Base class and then itera开发者_如何学编程ting over its results, but it doesn't helps. How can I solve this? Maybe I should use metaclasses (I don't get them yet)? Thanks in advance.


You need to set the list of owners, not update:

class auto_assign_perms(object):
    def __init__(self, *users):
        self.users = users

    def __call__(self, cls):
        cls.owners = set(self.users) # <- here
        return cls

#some tests
@auto_assign_perms('owner', 'user')
class Test(Base):
    pass

@auto_assign_perms('coach')
class Test2(Base):
    pass


t = Test()
t.save()
t = Test2()
t.save()

>>> 
owner user
coach


You are using owners as a class variable of Base so whenever you change owners the change will be seen in all the derived classes.

To fix that you should define the owners variable as class variable of the derived classes:

class Base(object):

    def save(self, *args, **kwargs):
        for owner in self.owners:
            print owner,
        print

@auto_assign_perms('owner', 'user')
class Test(Base):
     owners = set()

@auto_assign_perms('coach')
class Test2(Base):
     owners = set()


Call me paranoia but i find this solution more elegant and that because i don't think you need owners to be a class variable at all:

def auto_assign_perms(*users):

    def class_wrapper(cls):
        class ClassWrapper(cls):
            def __init__(self, owners=users):
                super(cls, self).__init__(owners=owners)

        ClassWrapper.__name__ = cls.__name__
        ClassWrapper.__module__ = cls.__module__

        return ClassWrapper

    return class_wrapper


class Base(object):
    def __init__(self, owners=None):
        if owners is None:
            owners = set()
        self.owners = owners

    def save(self, *args, **kwargs):
        for owner in self.owners:
            print owner,
        print


@auto_assign_perms('owner', 'user')
class Test1(Base):
    pass


@auto_assign_perms('coach')
class Test2(Base):
    pass


class Test3(Base):
    pass


t = Test1(); t.save() # owner user
t = Test2(); t.save() # coach
t = Test3(); t.save() # 
0

精彩评论

暂无评论...
验证码 换一张
取 消