Role Class¶
Role classes must be implemented by inheriting the Role
class present in improved_permissions.roles
.
Whenever you start your project, they are automatically validated and can already be used. If something is wrong, RoleManager
will raise an exception with a message explaining the cause of the error.
Note
Only Role classes inside modules called roles.py
are automatically validated. See here to change this behavior.
Required attributes¶
The Role
class has some attributes that are required to be properly registered by our RoleManager
. The description of these attributes is in the following table:
Attribute | Type | Description |
---|---|---|
verbose_name |
str |
Used to print some information about the role. |
models |
list or ALL_MODELS |
Defines which models this role can be attached. |
allow or deny |
list |
Defines which permissions should be allowed or denied. You must define only one of them. |
Optional Attributes¶
The Role
class also has other attributes, which are considered as optional. When they are not declared, we assign default values for these arguments.
Attribute | Type | Default | Description |
---|---|---|---|
unique |
bool |
False |
Only one User instance is allowed to be attached to a given object using this role. |
ranking |
int |
0 |
Used in order to solve permissions conflit. More about this in the examples. |
inherit |
bool |
False |
Allows this role to inherit permissions from its child models. Read about this feature here. |
inherit_allow or inherit_deny |
list |
[] |
Specifies which inherit permissions should be allowed or denied. You must define only one of them. |
Unique Roles¶
You will probably arrive in a case where an object can only have one User instance assigned to a particular role. But, it is as easy as learning python to do this using DIP. For example:
class CarOwner(Role):
verbose_name = 'Owner of a Car'
models = [Car]
deny = []
Now, let’s test this on the terminal:
my_car = Car.objects.create(name='My New Car')
# Giving a new car to john!
john = User.objects.get(pk=1)
assign_role(john, CarOwner, my_car)
# That's right.
has_role(john, CarOwner, my_car)
>>> True
# Oh, no...
bob = User.objects.get(pk=2)
assign_role(bob, CarOwner, my_car)
# And now?
has_role(bob, CarOwner, my_car)
>>> True # Noooooo :(
To fix this, let’s change the implementation of the role class and add the unique
attribute.
class CarOwner(Role):
verbose_name = 'Owner of a Car'
models = [Car]
deny = []
unique = True
Let’s test this again:
my_car = Car.objects.create(name='My New Car')
# Giving a new car to john!
john = User.objects.get(pk=1)
assign_role(john, CarOwner, my_car)
# That's right.
has_role(john, CarOwner, my_car)
>>> True
# And now we are protected :D
bob = User.objects.get(pk=2)
assign_role(bob, CarOwner, my_car)
>>> InvalidRoleAssignment: 'The object "Car" already has a "CarOwner" attached and it is marked as unique.'
Role Ranking¶
There are several cases that can lead your project to have permissions conflicts. We have a basic scenario to show you how this happens and how you can use role ranking to solve it. For example:
class Teacher(Role):
verbose_name = 'Teacher'
models = [User]
deny = ['user.update_user']
class Advisor(Role):
verbose_name = 'Advisor'
models = [User]
deny = []
Note that these roles have conflicting permissions if both are assigned to the same User instance. To solve this conflict problem, you can assign an integer value to ranking
, present in the Role class. This value will be used to sort the permissions to be used by the DIP.
In other words, the lower the ranking
value, more important this role is. So, let’s work using ranking now:
class Teacher(Role):
verbose_name = 'Teacher'
models = [User]
deny = ['user.update_user']
ranking = 1
class Advisor(Role):
verbose_name = 'Teacher'
models = [User]
deny = []
ranking = 0
Now let’s test this on the terminal:
john = User.objects.get(pk=1)
bob = User.objects.get(pk=2)
assign_role(john, Advisor, bob)
assign_role(john, Teacher, bob)
# Now has_permission returns True using
# the role "Advisor" by Role Ranking.
has_permission(john, 'user.update_user', bob)
>>> True
Role Classes using ALL_MODELS¶
If you need a role that manages any model of your project, you can define the models
attribute using ALL_MODELS
. These classes are inherit=True
by default because they don’t have their own permissions, only inherited permissions. For example:
# myapp/roles.py
from improved_permissions.roles import ALL_MODELS, Role
class SuperUser(Role):
verbose_name = 'Super Man Role'
models = ALL_MODELS
deny = []
inherit_deny = []
Because this class is not attached to a specific model, you can use the shortcuts without defining objects. For example:
from myapp.models import Book
from myapp.roles import SuperUser
john = User.objects.get(pk=1)
book = Book.objects.create(title='Nice Book', content='Such content.')
# You shouldn't pass an object during assignment.
assign_role(john, SuperUser)
# This line will raise an InvalidRoleAssignment exception
assign_role(john, SuperUser, book)
# You can check with and without an object.
has_permission(john, 'myapp.read_book')
>>> True
has_permission(john, 'myapp.read_book', book)
>>> True
Public Methods¶
The role classes have some class methods that you can call if you need them.
-
get_verbose_name(): str
Returns the verbose_name
attribute. Example:
from myapp.roles import Author, Reviewer
Author.get_verbose_name()
>>> 'Author'
Reviewer.get_verbose_name()
>>> 'Reviewer'
-
is_my_model(model): bool
Checks if the role can be attached to the argument model
. The argument can be either the model class or an instance. Example:
from myapp.models import Book
from myapp.roles import Author
Author.is_my_model('some data')
>>> False
Author.is_my_model(Book)
>>> True
my_book = Book.objects.create(title='Nice Book', content='Nice content.')
Author.is_my_model(my_book)
>>> True
-
get_models(): list
Returns a list of all model classes which this role can be attached. If the models
attribute was defined using ALL_MODELS
, this method will return a list of all valid models of the project. For example:
from myapp.models import Book
from myapp.roles import Author, SuperUser
Author.get_models()
>>> [Book]
SuperUser.get_models()
>>> [Book, User, Permission, ContentType, ...] # all models known by Django
In the next section, we describe all existing shortcuts in this app.