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:
@@ -0,0 +1,23 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN mkdir -p /app/media /app/staticfiles
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "--threads", "2", "--timeout", "60", "wrestleDesk.wsgi:application"]
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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):
|
||||
|
||||
@@ -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'
|
||||
@@ -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)
|
||||
|
||||
@@ -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)),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -13,7 +13,7 @@ from exercises.views import ExerciseViewSet
|
||||
from templates.views import TrainingTemplateViewSet, TemplateExerciseViewSet
|
||||
from trainings.views import TrainingViewSet, AttendanceViewSet, TrainingExerciseViewSet
|
||||
from homework.views import HomeworkViewSet, HomeworkExerciseItemViewSet, HomeworkAssignmentViewSet, HomeworkStatusViewSet, TrainingHomeworkAssignmentViewSet
|
||||
from auth_app.views import login, register, refresh_token, me, user_preferences
|
||||
from auth_app.views import UserManagementViewSet, login, register, refresh_token, me, user_preferences
|
||||
from stats.views import dashboard_stats
|
||||
from leistungstest.views import LeistungstestStatsViewSet
|
||||
|
||||
@@ -34,6 +34,7 @@ router.register(r'homework-assignments', HomeworkAssignmentViewSet, basename='ho
|
||||
router.register(r'homework-status', HomeworkStatusViewSet, basename='homework-status')
|
||||
router.register(r'training-assignments', TrainingHomeworkAssignmentViewSet, basename='training-assignment')
|
||||
router.register(r'leistungstest-stats', LeistungstestStatsViewSet, basename='leistungstest-stats')
|
||||
router.register(r'auth/users', UserManagementViewSet, basename='usermanagement')
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
|
||||
Reference in New Issue
Block a user