models.py 5.45 KB
Newer Older
1
from typing import List
2
from django.utils import timezone
3

4
from django.contrib.auth.models import AbstractUser
5
6
from django.core.exceptions import ValidationError
from django.db import models
7
from django.db.models import Q
8
from django.utils.functional import cached_property
9
10
from rest_framework.response import Response

11
from backend_app.models.abstract.base import BaseModelSerializer, BaseModel
12
13
14
15
16
17
18
19
20
21
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 (
22
23
        get_user_level(user) >= OBJ_MODERATION_PERMISSIONS["moderator"]
        and not allow_sharing_personal_info
24
25
26
27
28
29
    ):
        raise ValidationError(
            {
                "allow_sharing_personal_info": "Users that are moderators, members of DRI or staff, must allow sharing of their 'identity', sorry."
            }
        )
30
31
32


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

37
38
39
40
41
42
    @cached_property
    def cached_groups(self) -> List[str]:
        out = ["authenticated_user"]
        for group in self.groups.all():
            out.append(group.name)
        return out
43
44
45
46
47
48

    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"
    )
49
    has_validated_cgu_rgpd = models.BooleanField(default=False, null=False)
50
    is_banned = models.BooleanField(default=False, null=False)
51

52
53
54
55
56
57
    # 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)

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
87
88
    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",
89
            "delete_next_time",
90
91
            "is_staff",
        )
92
        read_only_fields = ("username", "first_name", "last_name", "email")
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
125
126


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"
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174


SITE_INFORMATION_VARIANTS = (
    ("info", "info"),
    ("success", "success"),
    ("warning", "warning"),
    ("error", "error"),
)


class SiteInformation(BaseModel):
    start = models.DateTimeField(null=False)
    end = models.DateTimeField(null=True)
    message = models.TextField(max_length=500)
    variant = models.CharField(
        max_length=10,
        choices=SITE_INFORMATION_VARIANTS,
        default="info",
        null=False,
        blank=False,
    )


class SiteInformationSerializer(BaseModelSerializer):
    class Meta:
        model = SiteInformation
        fields = BaseModelSerializer.Meta.fields + (
            "start",
            "end",
            "message",
            "variant",
        )


class SiteInformationViewSet(BaseModelViewSet):
    permission_classes = (ReadOnly,)
    serializer_class = SiteInformationSerializer
    end_point_route = "information"

    def get_queryset(self):
        get = self.request.GET
        if "now" in get.keys():
            now = timezone.now()
            return SiteInformation.objects.filter(
                Q(start__lte=now) & (Q(end__isnull=True) | Q(end__gte=now))
            )
        else:
            return SiteInformation.objects.all()