feat: implement user management system

- Add role field to UserProfile (superadmin/admin/trainer)
- Add role-based permission classes
- Create UserManagementViewSet with CRUD and password change
- Add API types and components for user management
- Create users management page in settings
- Only superadmins can manage users
This commit is contained in:
Andrej Spielmann
2026-03-26 16:42:08 +01:00
parent 7611533718
commit 28222d634d
19 changed files with 1960 additions and 7 deletions
@@ -0,0 +1,26 @@
# Generated by Django 4.2.29 on 2026-03-26 15:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("auth_app", "0004_userprofile"),
]
operations = [
migrations.AddField(
model_name="userprofile",
name="role",
field=models.CharField(
choices=[
("superadmin", "Super Admin"),
("admin", "Admin"),
("trainer", "Trainer"),
],
default="trainer",
max_length=20,
),
),
]
+8 -1
View File
@@ -3,11 +3,18 @@ from django.contrib.auth.models import User
class UserProfile(models.Model):
ROLE_CHOICES = [
('superadmin', 'Super Admin'),
('admin', 'Admin'),
('trainer', 'Trainer'),
]
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
club = models.ForeignKey('clubs.Club', on_delete=models.SET_NULL, null=True, blank=True, related_name='user_profiles')
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='trainer')
def __str__(self):
return f"{self.user.username} Profile"
return f"{self.user.username} Profile ({self.get_role_display()})"
class UserPreferences(models.Model):
+25
View File
@@ -0,0 +1,25 @@
from rest_framework import permissions
class IsSuperAdmin(permissions.BasePermission):
def has_permission(self, request, view):
return (
request.user.is_authenticated and
hasattr(request.user, 'profile') and
request.user.profile.role == 'superadmin'
)
class IsAdminOrSuperAdmin(permissions.BasePermission):
def has_permission(self, request, view):
if not request.user.is_authenticated:
return False
if not hasattr(request.user, 'profile'):
return False
return request.user.profile.role in ['admin', 'superadmin']
class HasUserManagementAccess(permissions.BasePermission):
def has_permission(self, request, view):
if not request.user.is_authenticated:
return False
if not hasattr(request.user, 'profile'):
return False
return request.user.profile.role == 'superadmin'
+49 -1
View File
@@ -1,6 +1,6 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import UserPreferences
from .models import UserPreferences, UserProfile
class UserSerializer(serializers.ModelSerializer):
@@ -62,3 +62,51 @@ class UserPreferencesSerializer(serializers.ModelSerializer):
class Meta:
model = UserPreferences
fields = '__all__'
class UserListSerializer(serializers.ModelSerializer):
role = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'role', 'date_joined']
def get_role(self, obj):
if hasattr(obj, 'profile'):
return obj.profile.role
return 'trainer'
class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
role = serializers.ChoiceField(choices=UserProfile.ROLE_CHOICES, default='trainer')
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'password', 'role']
def create(self, validated_data):
role = validated_data.pop('role', 'trainer')
user = User.objects.create_user(**validated_data)
UserProfile.objects.create(user=user, role=role)
return user
class UserUpdateSerializer(serializers.ModelSerializer):
role = serializers.ChoiceField(choices=UserProfile.ROLE_CHOICES, required=False)
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'role']
def update(self, instance, validated_data):
role = validated_data.pop('role', None)
user = super().update(instance, validated_data)
if role and hasattr(user, 'profile'):
user.profile.role = role
user.profile.save()
return user
class PasswordChangeSerializer(serializers.Serializer):
password = serializers.CharField(write_only=True, required=True)
+15
View File
@@ -0,0 +1,15 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'users', views.UserManagementViewSet, basename='usermanagement')
urlpatterns = [
path('login/', views.login, name='login'),
path('register/', views.register, name='register'),
path('refresh/', views.refresh_token, name='refresh'),
path('me/', views.me, name='me'),
path('preferences/', views.user_preferences, name='preferences'),
path('', include(router.urls)),
]
+33 -3
View File
@@ -1,12 +1,17 @@
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes, throttle_classes
from rest_framework import status, viewsets
from rest_framework.decorators import api_view, permission_classes, throttle_classes, action
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from .models import UserPreferences
from .serializers import LoginSerializer, RegisterSerializer, UserSerializer, UserPreferencesSerializer
from .serializers import (
LoginSerializer, RegisterSerializer, UserSerializer, UserPreferencesSerializer,
UserListSerializer, UserCreateSerializer, UserUpdateSerializer, PasswordChangeSerializer
)
from .permissions import HasUserManagementAccess
class AuthRateThrottle(AnonRateThrottle):
@@ -96,3 +101,28 @@ def user_preferences(request):
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=400)
class UserManagementViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().select_related('profile')
permission_classes = [HasUserManagementAccess]
def get_serializer_class(self):
if self.action == 'create':
return UserCreateSerializer
elif self.action in ['update', 'partial_update']:
return UserUpdateSerializer
return UserListSerializer
def get_queryset(self):
return User.objects.all().select_related('profile').order_by('-date_joined')
@action(detail=True, methods=['post'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordChangeSerializer(data=request.data)
if serializer.is_valid():
user.set_password(serializer.validated_data['password'])
user.save()
return Response({'status': 'password set'})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)