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:
pacnpal
2025-12-21 17:33:24 -05:00
parent b9063ff4f8
commit 7ba0004c93
74 changed files with 11134 additions and 198 deletions

View File

@@ -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):