mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 09:31:08 -05:00
chore: fix pghistory migration deps and improve htmx utilities
- Update pghistory dependency from 0007 to 0006 in account migrations - Add docstrings and remove unused imports in htmx_forms.py - Add DJANGO_SETTINGS_MODULE bash commands to Claude settings - Add state transition definitions for ride statuses
This commit is contained in:
@@ -3,17 +3,147 @@ Moderation Permissions
|
||||
|
||||
This module contains custom permission classes for the moderation system,
|
||||
providing role-based access control for moderation operations.
|
||||
|
||||
Each permission class includes an `as_guard()` class method that converts
|
||||
the permission to an FSM guard function, enabling alignment between API
|
||||
permissions and FSM transition checks.
|
||||
"""
|
||||
|
||||
from typing import Callable, Any, Optional
|
||||
from rest_framework import permissions
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class IsModerator(permissions.BasePermission):
|
||||
class PermissionGuardAdapter:
|
||||
"""
|
||||
Adapter that wraps a DRF permission class as an FSM guard.
|
||||
|
||||
This allows DRF permission classes to be used as conditions
|
||||
for FSM transitions, ensuring consistent authorization between
|
||||
API endpoints and state transitions.
|
||||
|
||||
Example:
|
||||
guard = IsModeratorOrAdmin.as_guard()
|
||||
# Use in FSM transition conditions
|
||||
@transition(conditions=[guard])
|
||||
def approve(self, user=None):
|
||||
pass
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
permission_class: type,
|
||||
error_message: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Initialize the guard adapter.
|
||||
|
||||
Args:
|
||||
permission_class: The DRF permission class to adapt
|
||||
error_message: Custom error message on failure
|
||||
"""
|
||||
self.permission_class = permission_class
|
||||
self._custom_error_message = error_message
|
||||
self._last_error_code: Optional[str] = None
|
||||
|
||||
@property
|
||||
def error_code(self) -> Optional[str]:
|
||||
"""Return the error code from the last failed check."""
|
||||
return self._last_error_code
|
||||
|
||||
def __call__(self, instance: Any, user: Any = None) -> bool:
|
||||
"""
|
||||
Check if the permission passes for the given user.
|
||||
|
||||
Args:
|
||||
instance: Model instance being transitioned
|
||||
user: User attempting the transition
|
||||
|
||||
Returns:
|
||||
True if the permission check passes
|
||||
"""
|
||||
self._last_error_code = None
|
||||
|
||||
if user is None:
|
||||
self._last_error_code = "NO_USER"
|
||||
return False
|
||||
|
||||
# Create a mock request object for DRF permission check
|
||||
class MockRequest:
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
self.data = {}
|
||||
self.method = "POST"
|
||||
|
||||
mock_request = MockRequest(user)
|
||||
permission = self.permission_class()
|
||||
|
||||
# Check permission
|
||||
if not permission.has_permission(mock_request, None):
|
||||
self._last_error_code = "PERMISSION_DENIED"
|
||||
return False
|
||||
|
||||
# Check object permission if available
|
||||
if hasattr(permission, "has_object_permission"):
|
||||
if not permission.has_object_permission(mock_request, None, instance):
|
||||
self._last_error_code = "OBJECT_PERMISSION_DENIED"
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_error_message(self) -> str:
|
||||
"""Return user-friendly error message."""
|
||||
if self._custom_error_message:
|
||||
return self._custom_error_message
|
||||
return f"Permission denied by {self.permission_class.__name__}"
|
||||
|
||||
def get_required_roles(self) -> list:
|
||||
"""Return list of roles that would satisfy this permission."""
|
||||
# Try to infer from permission class name
|
||||
name = self.permission_class.__name__
|
||||
if "Superuser" in name:
|
||||
return ["SUPERUSER"]
|
||||
elif "Admin" in name:
|
||||
return ["ADMIN", "SUPERUSER"]
|
||||
elif "Moderator" in name:
|
||||
return ["MODERATOR", "ADMIN", "SUPERUSER"]
|
||||
return ["USER", "MODERATOR", "ADMIN", "SUPERUSER"]
|
||||
|
||||
|
||||
class GuardMixin:
|
||||
"""
|
||||
Mixin that adds guard adapter functionality to DRF permission classes.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def as_guard(cls, error_message: Optional[str] = None) -> Callable:
|
||||
"""
|
||||
Convert this permission class to an FSM guard function.
|
||||
|
||||
Args:
|
||||
error_message: Optional custom error message
|
||||
|
||||
Returns:
|
||||
Guard function compatible with FSM transition conditions
|
||||
|
||||
Example:
|
||||
guard = IsModeratorOrAdmin.as_guard()
|
||||
|
||||
# In transition definition
|
||||
@transition(conditions=[guard])
|
||||
def approve(self, user=None):
|
||||
pass
|
||||
"""
|
||||
return PermissionGuardAdapter(cls, error_message=error_message)
|
||||
|
||||
|
||||
class IsModerator(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that only allows moderators to access the view.
|
||||
|
||||
Use `IsModerator.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -29,9 +159,11 @@ class IsModerator(permissions.BasePermission):
|
||||
return self.has_permission(request, view)
|
||||
|
||||
|
||||
class IsModeratorOrAdmin(permissions.BasePermission):
|
||||
class IsModeratorOrAdmin(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that allows moderators, admins, and superusers to access the view.
|
||||
|
||||
Use `IsModeratorOrAdmin.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -47,9 +179,11 @@ class IsModeratorOrAdmin(permissions.BasePermission):
|
||||
return self.has_permission(request, view)
|
||||
|
||||
|
||||
class IsAdminOrSuperuser(permissions.BasePermission):
|
||||
class IsAdminOrSuperuser(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that only allows admins and superusers to access the view.
|
||||
|
||||
Use `IsAdminOrSuperuser.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -65,12 +199,14 @@ class IsAdminOrSuperuser(permissions.BasePermission):
|
||||
return self.has_permission(request, view)
|
||||
|
||||
|
||||
class CanViewModerationData(permissions.BasePermission):
|
||||
class CanViewModerationData(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that allows users to view moderation data based on their role.
|
||||
|
||||
- Regular users can only view their own reports
|
||||
- Moderators and above can view all moderation data
|
||||
|
||||
Use `CanViewModerationData.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -96,12 +232,14 @@ class CanViewModerationData(permissions.BasePermission):
|
||||
return False
|
||||
|
||||
|
||||
class CanModerateContent(permissions.BasePermission):
|
||||
class CanModerateContent(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that allows users to moderate content based on their role.
|
||||
|
||||
- Only moderators and above can moderate content
|
||||
- Includes additional checks for specific moderation actions
|
||||
|
||||
Use `CanModerateContent.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -141,13 +279,15 @@ class CanModerateContent(permissions.BasePermission):
|
||||
return False
|
||||
|
||||
|
||||
class CanAssignModerationTasks(permissions.BasePermission):
|
||||
class CanAssignModerationTasks(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that allows users to assign moderation tasks to others.
|
||||
|
||||
- Moderators can assign tasks to themselves
|
||||
- Admins can assign tasks to moderators and themselves
|
||||
- Superusers can assign tasks to anyone
|
||||
|
||||
Use `CanAssignModerationTasks.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -186,12 +326,14 @@ class CanAssignModerationTasks(permissions.BasePermission):
|
||||
return False
|
||||
|
||||
|
||||
class CanPerformBulkOperations(permissions.BasePermission):
|
||||
class CanPerformBulkOperations(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that allows users to perform bulk operations.
|
||||
|
||||
- Only admins and superusers can perform bulk operations
|
||||
- Includes additional safety checks for destructive operations
|
||||
|
||||
Use `CanPerformBulkOperations.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -225,12 +367,14 @@ class CanPerformBulkOperations(permissions.BasePermission):
|
||||
return False
|
||||
|
||||
|
||||
class IsOwnerOrModerator(permissions.BasePermission):
|
||||
class IsOwnerOrModerator(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that allows object owners or moderators to access the view.
|
||||
|
||||
- Users can access their own objects
|
||||
- Moderators and above can access any object
|
||||
|
||||
Use `IsOwnerOrModerator.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
@@ -259,13 +403,15 @@ class IsOwnerOrModerator(permissions.BasePermission):
|
||||
return False
|
||||
|
||||
|
||||
class CanManageUserRestrictions(permissions.BasePermission):
|
||||
class CanManageUserRestrictions(GuardMixin, permissions.BasePermission):
|
||||
"""
|
||||
Permission that allows users to manage user restrictions and moderation actions.
|
||||
|
||||
- Moderators can create basic restrictions (warnings, temporary suspensions)
|
||||
- Admins can create more severe restrictions (longer suspensions, content removal)
|
||||
- Superusers can create any restriction including permanent bans
|
||||
|
||||
Use `CanManageUserRestrictions.as_guard()` to get an FSM-compatible guard.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
|
||||
Reference in New Issue
Block a user