from django.contrib.auth import get_user_model from django.contrib.auth.password_validation import validate_password from django.core.exceptions import ValidationError as DjangoValidationError from django.db import transaction from django.utils import timezone from rest_framework import serializers from .models import Invitation from .permissions import default_permissions, effective_permissions, management_permissions User = get_user_model() class UserSerializer(serializers.ModelSerializer): is_admin = serializers.SerializerMethodField() permissions = serializers.SerializerMethodField() management_permissions = serializers.SerializerMethodField() color = serializers.RegexField(r'^#[0-9a-fA-F]{6}$', required=False) class Meta: model = User fields = [ 'id', 'username', 'first_name', 'last_name', 'email', 'is_active', 'is_staff', 'is_superuser', 'is_admin', 'permissions', 'management_permissions', 'date_joined', 'color', ] read_only_fields = ['id', 'date_joined', 'is_admin'] def get_is_admin(self, obj): return obj.is_superuser or obj.is_staff def get_permissions(self, obj): if not hasattr(self, '_permission_defaults'): self._permission_defaults = default_permissions() return effective_permissions(obj, self._permission_defaults) def get_management_permissions(self, obj): return management_permissions(obj) def validate(self, attrs): actor = self.context['request'].user if not actor.is_superuser: if self.instance.is_superuser: raise serializers.ValidationError('Seul un superutilisateur peut modifier ce compte.') if attrs.get('is_superuser', False): raise serializers.ValidationError({'is_superuser': 'Seul un superutilisateur peut accorder ce rôle.'}) if self.instance.pk == actor.pk: if not attrs.get('is_active', self.instance.is_active): raise serializers.ValidationError({'is_active': 'Vous ne pouvez pas désactiver votre propre compte.'}) if not (attrs.get('is_staff', self.instance.is_staff) or attrs.get('is_superuser', self.instance.is_superuser)): raise serializers.ValidationError('Vous ne pouvez pas retirer votre propre accès administrateur.') return attrs class RegisterSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True) password_confirm = serializers.CharField(write_only=True) invite = serializers.UUIDField(write_only=True) class Meta: model = User fields = [ 'username', 'password', 'password_confirm', 'first_name', 'last_name', 'invite', ] def validate(self, attrs): if attrs['password'] != attrs['password_confirm']: raise serializers.ValidationError({ 'password_confirm': ( 'Les mots de passe ne correspondent pas.' ) }) candidate = User(**{field: attrs.get(field, '') for field in ('username', 'first_name', 'last_name')}) try: validate_password(attrs['password'], candidate) except DjangoValidationError as error: raise serializers.ValidationError({'password': error.messages}) from error return attrs def validate_invite(self, value): try: invitation = Invitation.objects.get(token=value) except Invitation.DoesNotExist as error: raise serializers.ValidationError( 'Ce lien d’invitation est invalide.' ) from error if not invitation.is_valid: raise serializers.ValidationError( 'Ce lien d’invitation est expiré ou déjà utilisé.' ) return value def create(self, validated_data): invitation_token = validated_data.pop('invite') validated_data.pop('password_confirm') with transaction.atomic(): invitation = ( Invitation.objects .select_for_update() .get(token=invitation_token) ) if not invitation.is_valid: raise serializers.ValidationError({ 'invite': ( 'Ce lien d’invitation est expiré ou déjà utilisé.' ) }) user = User.objects.create_user(**validated_data) invitation.used_at = timezone.now() invitation.save(update_fields=['used_at']) return user