diff --git a/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/migrations/0002_historicaluser_disabled_at_user_disabled_at.py b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/migrations/0002_historicaluser_disabled_at_user_disabled_at.py new file mode 100644 index 0000000..eb868e1 --- /dev/null +++ b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/migrations/0002_historicaluser_disabled_at_user_disabled_at.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.2 on 2023-04-18 18:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="historicaluser", + name="disabled_at", + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name="user", + name="disabled_at", + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/models.py b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/models.py index ee845b0..3cae39c 100644 --- a/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/models.py +++ b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/models.py @@ -66,7 +66,7 @@ class User(AbstractUser, TimeStampedModel): "Unselect this instead of deleting accounts." ), ) - + disabled_at = models.DateTimeField(null=True, blank=True) objects = UserManager() history = HistoricalRecords(excluded_fields=["password"]) @@ -89,6 +89,12 @@ class Meta: def activate(self): self.activated_at = timezone.now() self.is_active = True + self.disabled_at = None + self.save() + + def deactivate(self): + self.is_active = False + self.disabled_at = timezone.now() self.save() def can_access_role(self, role): diff --git a/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/test/test_models.py b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/test/test_models.py index 3cb80ad..b9b25e1 100644 --- a/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/test/test_models.py +++ b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/test/test_models.py @@ -28,5 +28,16 @@ def test_user_activate(): user.activate() - assert user.is_active - assert user.activated_at + assert user.is_active == True + assert user.activated_at is not None + assert user.disabled_at is None + +@pytest.mark.django_db +def test_user_deactivate(): + user = UserFactory() + + user.activate() + user.deactivate() + + assert user.is_active is False + assert user.disabled_at is not None diff --git a/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/views.py b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/views.py index 4c7881d..7dea28f 100644 --- a/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/views.py +++ b/{{ cookiecutter.project_slug }}/{{ cookiecutter.project_slug }}/users/views.py @@ -26,6 +26,8 @@ ) from .permissions import IsAdmin, IsUserOrAdmin from .models import HistoricalUser +from .models import User +from django.utils import timezone class UserViewSet(DjoserUserViewSet): @@ -44,17 +46,7 @@ class UserViewSet(DjoserUserViewSet): } search_fields = ["email", "last_name", "first_name"] - def make_role_read_only(self, serializer): - serializer.validated_data.pop('role', None) - def perform_update(self, serializer): - user_being_accessed = self.get_object() - user_making_request = self.request.user - is_same_user = (user_being_accessed == user_making_request) - - if is_same_user or user_being_accessed.role == "USER" or user_being_accessed.role == "EDITOR": - self.make_role_read_only(serializer) - serializer.save() @action( @@ -94,7 +86,6 @@ def cancel_change_email_request(self, request, *args, **kwargs): serialized_user = UserSerializer(user, context={"request": request}) return Response(data=serialized_user.data, status=status.HTTP_200_OK) - @action( detail=False, methods=["post"], @@ -193,7 +184,6 @@ def invitation(self, request): user = serializer.save(is_active=False) - # Send invitation email context = {"user": user} to = [user.email] serialized_email = serialize_email(settings.EMAIL.activation(self.request, context), to) @@ -201,6 +191,38 @@ def invitation(self, request): return Response(status=status.HTTP_204_NO_CONTENT) + @action( + detail=True, + methods=["post"], + serializer_class=NullSerializer, + permission_classes=[IsAdmin], + ) + def disable(self, request, id): + try: + user = User.objects.get(id=id) + except User.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + user.deactivate() + serialized_user = UserSerializer(user, context={"request": request}) + return Response(data=serialized_user.data, status=status.HTTP_200_OK) + + @action( + detail=True, + methods=["post"], + serializer_class=NullSerializer, + permission_classes=[IsAdmin], + ) + def enable(self, request, id): + try: + user = User.objects.get(id=id) + except User.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + + user = User.objects.get(id=id) + user.activate() + serialized_user = UserSerializer(user, context={"request": request}) + return Response(data=serialized_user.data, status=status.HTTP_200_OK) class UserHistoryViewSet( NestedViewSetMixin, viewsets.GenericViewSet, mixins.ListModelMixin