Python - creating a class decorator on derived classes

In the application that we are developing using Django, in some cases we need to automatically assign permissions for users for some models that have owners (there is no rule for the field name, it can be "user", "owner", "trainer" and etc., there can also be more than one field.) My solution is to create a decorator containing these field names that will be placed before the model definition like this (without using the django code in the samples):

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

Suppose Base is an abstract class that comes after the Django Model class, where I add functionality to assign permissions after saving the object. At the moment, I am only printing a list of users assigned to the class. Below you can find the code for the decorator and the 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 may 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 contain all three fields ('owner', 'user', 'coach') , altough print self.__class__.__name__ in the Base.save() method correctly shows "Test" or "Test2" . I tried adding classmethod get_owners() to the Base class and then repeating its results, but that doesn't help. How can i solve this? Maybe I should use metaclasses (I don't get them yet)? Thanks in advance.

+4
source share
3 answers

You need to establish a list of owners, and 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 
+4
source

You use owners as a variable of the Base class, so whenever you change owners , the change will be visible in all derived classes.

To fix this, you must define the owners variable as a class variable of 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() 
+1
source

Call me paranoia, but I find this solution more elegant, because I don’t think that owners need 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
source

All Articles