models.py 4.17 KB
Newer Older
Florent Chehab's avatar
Florent Chehab committed
1 2
from typing import List

3
from django.contrib.auth.models import AbstractUser
Florent Chehab's avatar
Florent Chehab committed
4 5
from django.core.exceptions import ValidationError
from django.db import models
6
from django.utils.functional import cached_property
Florent Chehab's avatar
Florent Chehab committed
7 8 9 10 11 12 13 14 15 16 17 18 19
from rest_framework.response import Response

from backend_app.models.abstract.base import BaseModelSerializer
from backend_app.models.abstract.base import BaseModelViewSet
from backend_app.permissions.app_permissions import IsOwner, ReadOnly
from backend_app.utils import get_user_level, OBJ_MODERATION_PERMISSIONS


def validate(user, allow_sharing_personal_info):
    """
    Custom validation to ensure that moderators, DRI and staff can't be "anonymous" on the plateform
    """
    if (
20 21
        get_user_level(user) >= OBJ_MODERATION_PERMISSIONS["moderator"]
        and not allow_sharing_personal_info
Florent Chehab's avatar
Florent Chehab committed
22 23 24 25 26 27
    ):
        raise ValidationError(
            {
                "allow_sharing_personal_info": "Users that are moderators, members of DRI or staff, must allow sharing of their 'identity', sorry."
            }
        )
28 29 30


class User(AbstractUser):
31 32 33 34
    """
    If you modify this model, don't forget to modify the command that handles its emptying for RGPD
    """

35 36 37 38 39 40
    @cached_property
    def cached_groups(self) -> List[str]:
        out = ["authenticated_user"]
        for group in self.groups.all():
            out.append(group.name)
        return out
Florent Chehab's avatar
Florent Chehab committed
41 42 43 44 45 46

    allow_sharing_personal_info = models.BooleanField(default=True, null=False)
    secondary_email = models.EmailField(null=True, blank=True)
    pseudo = models.CharField(
        blank=False, null=False, max_length=12, default="Anonymous42"
    )
47
    has_validated_cgu_rgpd = models.BooleanField(default=False, null=False)
48
    is_banned = models.BooleanField(default=False, null=False)
Florent Chehab's avatar
Florent Chehab committed
49

50 51 52 53 54 55
    # Handling of account deletion
    delete_next_time = models.BooleanField(
        default=False, null=False
    )  # if true, the account will be deleted at midnight
    is_deleted = models.BooleanField(default=False, null=False)

Florent Chehab's avatar
Florent Chehab committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    def save(self, *args, **kwargs):
        """
        Custom save function to ensure consistency.
        """

        # if the object is not created yet, we can't check to what groups it belongs
        if self.pk is not None:
            validate(self, self.allow_sharing_personal_info)
        return super().save(*args, **kwargs)


class UserSerializer(BaseModelSerializer):
    def validate(self, attrs):
        """
        Also validate at the serializer level to prevent error 500
        """
        data = super().validate(attrs)
        aspi = data["allow_sharing_personal_info"]
        validate(self.get_user_from_request(), aspi)
        return data

    class Meta:
        model = User
        fields = BaseModelSerializer.Meta.fields + (
            "username",
            "first_name",
            "last_name",
            "email",
            "allow_sharing_personal_info",
            "secondary_email",
            "pseudo",
87
            "delete_next_time",
Florent Chehab's avatar
Florent Chehab committed
88 89
            "is_staff",
        )
90
        read_only_fields = ("username", "first_name", "last_name", "email")
Florent Chehab's avatar
Florent Chehab committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124


class UserViewset(BaseModelViewSet):
    def list(self, request, *args, **kwargs):
        # Prevent the querying of all objects.
        return Response(list())

    def retrieve(self, request, *args, **kwargs):
        # Custom behavior to return only the correct set of attributes depending
        # On allow_sharing_personal_info
        instance = self.get_object()
        serializer = self.get_serializer(instance)

        if instance == self.request.user or self.request.user.is_staff:
            out = serializer.data
        else:
            # serializer.data is a property we can't set it again, so we need a little trick for the output
            # First we copy all the values
            out = dict(serializer.data)
            # Then we "correct" them
            for key in [
                "username",
                "first_name",
                "last_name",
                "email",
                "secondary_email",
            ]:
                out[key] = None
        return Response(out)

    queryset = User.objects.all()
    permission_classes = (IsOwner | ReadOnly,)
    serializer_class = UserSerializer
    end_point_route = "users"