-
-
Notifications
You must be signed in to change notification settings - Fork 217
Description
Add an access-control mixin that evaluates boolean expressions over a user's groups and permissions, so you don't have to stack multiple mixins. Example: group("editors") & (perm("blog.change_post") | perm("blog.add_post")). It must support object-level permissions and preserve AccessMixin behaviors.
Implement [AccessExpressionMixin] as a new class that integrates with the existing AccessMixin and a small expression evaluator. (Do not modify existing mixins)
Expression language requirements->
Token functions: group("name"), perm("app.codename"), any_group("g1", ...), all_groups("g1", ...)
Operators: ~ (NOT, highest precedence), & (AND), | (OR, lowest), and parentheses
Errors: raise ValueError for malformed input or unknown functions
Literals: String literals use double quotes (for example group("editors")). Accepting single quotes is optional and not required by tests.
Semantics->
group(name): true if and only if user.is_authenticated and the user is in the given group
perm(app.codename): true if and only if user.is_authenticated and user.has_perm("app.codename", obj)
any_group(...): true if user is in at least one listed group
all_groups(...): true if user is in every listed group
~expr, expr1 & expr2 (short-circuit on false), expr1 | expr2 (short-circuit on true)
Mixin behavior (public interface)
Class: AccessExpressionMixin(AccessMixin)
Required attribute: access_expression: str
Optional attributes:
allow_superuser: bool = True
allow_staff: bool = False
dispatch(self, request, *args, **kwargs):
1-> If allow_superuser and request.user.is_superuser, allow without evaluating the expression.
2-> If allow_staff and request.user.is_staff, allow without evaluating the expression.
3-> Otherwise, evaluate access_expression using the current request.user and, if defined, get_permission_object() as the permission object (obj) when calling user.has_perm.
4-> On allow: dispatch must return None so the view continues normal processing.
5-> On deny: return handle_no_permission(request), preserving AccessMixin behavior (honor raise_exception, login_url, redirect_field_name).
If the view defines get_permission_object(), pass its return value as the obj argument to user.has_perm(...); otherwise use None.
Test Assumptions:
braces/views/_access.py -> Defines AccessExpressionMixin
braces/views/init.py -> Re-exports AccessExpressionMixin
Public surface expected by tests
Class AccessExpressionMixin(AccessMixin) with attributes:
access_expression: str
allow_superuser: bool
allow_staff: bool
Method dispatch(self, request, *args, **kwargs) with continuation semantics:
Returns None on allow
Returns handle_no_permission(request) on deny
Note - The behavioral semantics (operators, precedence, short-circuits, anonymous behavior, error types, object-level permission usage) are verified only via the public interface above.
The parser and evaluator implementation details and module layout are flexible and not directly referenced by tests.