Return rule denial reasons (and improved integration with rest framework)
See original GitHub issueThanks for the great library! I love the philosophy to use rules written in simple python functions over database tables.
I ran into a few troubles trying to integrate it with a django rest framework project - we don’t use django permissions, which means we can’t use DjangoObjectPermissions easily.
I thought to connect django-rules directly to drf permissions - ends up similar-ish to dry-rest-permissions but using django-rules.
I also wanted to be able to return custom error messages explaining why a permission was denied.
I managed to implement this by subclassing stuff in django-rules so that you can use it like this:
# in rules.py
@predicate(messages=('Team is archived', 'Team is not archived'))
def is_team_archived(_, team):
return team.status = 'archived'
can_archive_team = ~is_team_archived
# standalone use
result, message = can_archive_team.test_with_message(user, team)
if result:
print('yes')
else:
print('no:', message) # prints "no: Team is archived"
# use in a @detail_route
# creates a drf compatible permission class
CanArchiveTeam = create_permission_class('CanArchiveTeam', can_archive_team)
@detail_route(
methods=['POST'],
permission_classes=(IsAuthenticated, CanArchiveTeam)
)
def archive(self, request, pk=None):
# do archiving
If the CanArchiveTeam
does not pass, then it returns a permission denied error with the message Team is archived
.
There is a bit more information in our project discussion.
I wanted to avoid magic naming conventions (the approach taken by dry-rest-permissions) or referring to permissions using string names - explicit imports are much clearer to me.
Actually, the only API change for rules is changing/adding a method that returns (result, message)
instead of result
, and accepting messages as predicate args.
My questions are:
- would you be interested in having something this inside django-rules?
- if not, would you be interested in making django-rules officially extendable so I don’t need to use private methods/variables which might break in future versions?
The alternative is a fork, but I don’t want to contribute to the ever fragmenting django permissions ecosystem. Thanks!
Issue Analytics
- State:
- Created 6 years ago
- Reactions:4
- Comments:5 (2 by maintainers)
I think a good approach would be to allow a predicate to fail by raising a specific type of exception instead of returning
false
–rules
could catch that, turn the result intofalse
, stash the exception as the denial reason and offer a way for the caller to retrieve it.Perhaps a nicer way to avoid breaking backwards compatibility for predicates would be to return either True/False as now or True/
Failed
whereFailed
is a new object that is false-y but also contains a message string that is processed byrules
?