diff --git a/sigma_publications/models.py b/sigma_publications/models.py index c5d10e2..77e35af 100644 --- a/sigma_publications/models.py +++ b/sigma_publications/models.py @@ -22,7 +22,6 @@ class Publication(models.Model): # Related fields # - comments (model PublicationComment) # - posts (model GroupPost) - # - posted_in (model Group.publications through GroupPost) class GroupPost(models.Model): diff --git a/sigma_publications/serializers.py b/sigma_publications/serializers.py index a10d7c2..4fd7f12 100644 --- a/sigma_publications/serializers.py +++ b/sigma_publications/serializers.py @@ -7,13 +7,12 @@ class PublicationSerializer(serializers.ModelSerializer): class Meta: model = Publication - read_only_fields = ('poster_group', 'poster_user') + read_only_fields = ('poster_group', 'poster_user', ) - poster_group = serializers.PrimaryKeyRelatedField(allow_null=True, queryset=Group.objects.all()) + poster_group = serializers.PrimaryKeyRelatedField(read_only=True, allow_null=True) poster_user = serializers.PrimaryKeyRelatedField( read_only=True, - default=serializers.CurrentUserDefault() - ) + default=serializers.CurrentUserDefault()) text = serializers.CharField() diff --git a/sigma_publications/tests.py b/sigma_publications/tests.py index 6cf3351..446ab79 100644 --- a/sigma_publications/tests.py +++ b/sigma_publications/tests.py @@ -19,10 +19,9 @@ faker = FakerFactory.create('fr_FR') class PublicationFactory(factory.django.DjangoModelFactory): class Meta: - model = User + model = Publication - title = factory.LazyAttribute(lambda obj: faker.last_name()) - body = factory.LazyAttribute(lambda obj: faker.text()) + text = factory.LazyAttribute(lambda obj: faker.text()) class GroupPostFactory(factory.django.DjangoModelFactory): @@ -45,6 +44,7 @@ class Meta: from rest_framework.test import APITestCase from sigma_core.tests.factories import UserFactory, GroupFactory, AdminUserFactory, GroupMemberFactory +from sigma_publications.serializers import PublicationSerializer class PublicationTestsSituation(APITestCase): @@ -53,26 +53,35 @@ def setUpTestData(self): super(APITestCase, self).setUpTestData() # Situation # User0 - # -> Group0 + # -> Group0 [Group admin] # -> Group1 # -> Group2 + # -> Publication0 ("draft") # User1 # -> Group1 [Group admin] + # -> GroupPost -> Publication1 + # -> Publication1 # User2 # -> Group2 self.users = UserFactory.create_batch(3) self.groups = GroupFactory.create_batch(3) - GroupMemberFactory(user=self.users[0], group=self.groups[0]) + GroupMemberFactory(user=self.users[0], group=self.groups[0], perm_rank=Group.ADMINISTRATOR_RANK) GroupMemberFactory(user=self.users[0], group=self.groups[1]) GroupMemberFactory(user=self.users[0], group=self.groups[2]) GroupMemberFactory(user=self.users[1], group=self.groups[1], perm_rank=Group.ADMINISTRATOR_RANK) GroupMemberFactory(user=self.users[2], group=self.groups[2]) + # Publications + self.publications = [PublicationFactory(poster_user=self.users[0]), + PublicationFactory(poster_user=self.users[1])] + GroupPostFactory(publication=self.publications[1], group=self.groups[1]) + # Routes self.publications_route = '/publication/' class PublicationTests(PublicationTestsSituation): + # CREATE def gen_new_publication(self, poster_user, poster_group): pub = {'text' : 'Random text', 'poster_user': '', 'poster_group' : ''} if poster_user is not None: @@ -99,8 +108,9 @@ def test_create_publication_as_someone_else(self): self.client.force_authenticate(user=self.users[0]) response = self.client.post(self.publications_route, self.gen_new_publication(self.users[1], None)) self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Publication.objects.filter(poster_user=self.users[0]).count(), 1) - self.assertEqual(Publication.objects.filter(poster_user=self.users[1]).count(), 0) + qs = Publication.objects.filter(pk=response.data.get('id')) + self.assertEqual(qs.filter(poster_user=self.users[0]).count(), 1) + self.assertEqual(qs.filter(poster_user=self.users[1]).count(), 0) def test_create_publication_as_group_ok(self): self.client.force_authenticate(user=self.users[1]) @@ -111,3 +121,49 @@ def test_create_publication_as_user_ok(self): self.client.force_authenticate(user=self.users[0]) response = self.client.post(self.publications_route, self.gen_new_publication(self.users[0], None)) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # UPDATE + def test_update_not_authed(self): + data = PublicationSerializer(self.publications[0]).data + data['text'] = 'Just edit this' + response = self.client.put("%s%d/" % (self.publications_route, self.publications[0].id), data) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_update_not_owner(self): + self.client.force_authenticate(user=self.users[1]) + data = PublicationSerializer(self.publications[0]).data + data['text'] = 'Just edit this' + response = self.client.put("%s%d/" % (self.publications_route, self.publications[0].id), data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_update_poster_group_read_only(self): + self.client.force_authenticate(user=self.users[0]) + data = PublicationSerializer(self.publications[0]).data + data['poster_group'] = self.groups[0].id + response = self.client.put("%s%d/" % (self.publications_route, self.publications[0].id), data) + # Response code is 200 ... + self.publications[0].refresh_from_db() + self.assertEqual(self.publications[0].poster_group, None) + + def test_update_ok(self): + self.client.force_authenticate(user=self.users[0]) + data = PublicationSerializer(self.publications[0]).data + data['text'] = 'Just edit this' + response = self.client.put("%s%d/" % (self.publications_route, self.publications[0].id), data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # DESTROY + def test_delete_not_draft(self): + self.client.force_authenticate(user=self.users[1]) + response = self.client.delete("%s%d/" % (self.publications_route, self.publications[1].id)) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_delete_not_own_publication(self): + self.client.force_authenticate(user=self.users[1]) + response = self.client.delete("%s%d/" % (self.publications_route, self.publications[0].id)) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_delete_own_draft_ok(self): + self.client.force_authenticate(user=self.users[0]) + response = self.client.delete("%s%d/" % (self.publications_route, self.publications[0].id)) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/sigma_publications/views/publication.py b/sigma_publications/views/publication.py index 38d1094..a53c7b2 100644 --- a/sigma_publications/views/publication.py +++ b/sigma_publications/views/publication.py @@ -1,5 +1,5 @@ from django.http import Http404, HttpResponseForbidden -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError, PermissionDenied from rest_framework import viewsets, decorators, status, mixins from rest_framework.response import Response @@ -12,17 +12,21 @@ from django.db.models import F -class PublicationViewSet(mixins.CreateModelMixin, # Everyone can create a publication - #mixins.RetrieveModelMixin, # TODO? - mixins.UpdateModelMixin, # TODO - mixins.DestroyModelMixin, # TODO - #mixins.ListModelMixin, # TODO? +class PublicationViewSet(mixins.CreateModelMixin, # Everyone can create a publication + #mixins.RetrieveModelMixin, # Not needed. Will be retrieved with the GroupPost view + # TODO: Maybe for sigma admins, to see where it is posted ? + mixins.UpdateModelMixin, # Only your own Publication + mixins.DestroyModelMixin, # Only if it is not posted already, or for sigma admins + #mixins.ListModelMixin, # Not needed. Will be retrieved with the GroupPost view viewsets.GenericViewSet): queryset = Publication.objects.all() serializer_class = PublicationSerializer permission_classes = [IsAuthenticated, ] def create(self, request): + """ + Create a new Publication. Only visible by the user at first. + """ serializer = self.get_serializer(data=request.data) allowed_groups = Group.objects.filter(memberships__user=request.user, memberships__perm_rank__gte=F('req_rank_create_group_publication')) @@ -33,3 +37,28 @@ def create(self, request): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) + + def update(self, request, *args, **kwargs): + """ + Update the Publication text. The autor User or autor Group are not modifiable. + """ + if not request.user.is_sigma_admin(): + self.queryset = self.queryset.filter(poster_user=request.user) + return super().update(request, *args, **kwargs) + + def check_object_permissions(self, request, publication): + if self.action == "destroy": + if publication.comments.all(): + raise PermissionDenied() + if publication.posts.all(): + raise PermissionDenied() + return super().check_object_permissions(request, publication) + + def destroy(self, request, *args, **kwargs): + """ + Destroy the Publication. + Only possible if there is no comment and if the publication is not already posted. + """ + if not request.user.is_sigma_admin(): + self.queryset = self.queryset.filter(poster_user=request.user) + return super().destroy(request, *args, **kwargs)