From c99d7224138bc6d21f73103b937da845f41ef67e Mon Sep 17 00:00:00 2001
From: janmax <j.michal@stud.uni-goettingen.de>
Date: Sat, 17 Feb 2018 22:25:18 +0100
Subject: [PATCH] Added the option delete feedback comments

---
 core/migrations/0007_auto_20180217_2049.py | 24 +++++++
 core/models.py                             | 69 ++++++++++----------
 core/serializers/feedback.py               |  3 +-
 core/tests/test_feedback.py                | 73 ++++++++++++++++++++++
 core/urls.py                               |  4 +-
 core/views/__init__.py                     |  2 +-
 core/views/feedback.py                     | 15 +++++
 docker-compose.yml                         |  2 +
 8 files changed, 156 insertions(+), 36 deletions(-)
 create mode 100644 core/migrations/0007_auto_20180217_2049.py

diff --git a/core/migrations/0007_auto_20180217_2049.py b/core/migrations/0007_auto_20180217_2049.py
new file mode 100644
index 00000000..e1a6b83a
--- /dev/null
+++ b/core/migrations/0007_auto_20180217_2049.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.0.2 on 2018-02-17 20:49
+
+import uuid
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0006_auto_20180217_1729'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='feedbackcomment',
+            name='id',
+        ),
+        migrations.AddField(
+            model_name='feedbackcomment',
+            name='comment_id',
+            field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False),
+        ),
+    ]
diff --git a/core/models.py b/core/models.py
index 9bdd0c92..6572cc2b 100644
--- a/core/models.py
+++ b/core/models.py
@@ -425,6 +425,42 @@ class Feedback(models.Model):
         return self.of_submission.type.full_score
 
 
+class FeedbackComment(models.Model):
+    """ This Class contains the Feedback for a specific line of a Submission"""
+    comment_id = models.UUIDField(primary_key=True,
+                                  default=uuid.uuid4,
+                                  editable=False)
+    text = models.TextField()
+    created = models.DateTimeField(auto_now_add=True)
+    modified = models.DateTimeField(auto_now=True)
+
+    visible_to_student = models.BooleanField(default=True)
+
+    of_line = models.PositiveIntegerField(default=0)
+    of_tutor = models.ForeignKey(
+        get_user_model(),
+        related_name="comment_list",
+        on_delete=models.PROTECT
+    )
+    of_feedback = models.ForeignKey(
+        Feedback,
+        related_name="feedback_lines",
+        on_delete=models.CASCADE,
+        null=True
+    )
+
+    class Meta:
+        verbose_name = "Feedback Comment"
+        verbose_name_plural = "Feedback Comments"
+        ordering = ('created',)
+        unique_together = ('of_line', 'of_tutor', 'of_feedback')
+
+    def __str__(self):
+        return 'Comment on line {} of tutor {}: "{}"'.format(self.of_line,
+                                                             self.of_tutor,
+                                                             self.text)
+
+
 class SubscriptionEnded(Exception):
     pass
 
@@ -654,36 +690,3 @@ class TutorSubmissionAssignment(models.Model):
 
     class Meta:
         unique_together = ('submission', 'subscription')
-
-
-class FeedbackComment(models.Model):
-    """ This Class contains the Feedback for a specific line of a Submission"""
-    text = models.TextField()
-    created = models.DateTimeField(auto_now_add=True)
-    modified = models.DateTimeField(auto_now=True)
-
-    visible_to_student = models.BooleanField(default=True)
-
-    of_line = models.PositiveIntegerField(default=0)
-    of_tutor = models.ForeignKey(
-        get_user_model(),
-        related_name="comment_list",
-        on_delete=models.PROTECT
-    )
-    of_feedback = models.ForeignKey(
-        Feedback,
-        related_name="feedback_lines",
-        on_delete=models.CASCADE,
-        null=True
-    )
-
-    class Meta:
-        verbose_name = "Feedback Comment"
-        verbose_name_plural = "Feedback Comments"
-        ordering = ('created',)
-        unique_together = ('of_line', 'of_tutor', 'of_feedback')
-
-    def __str__(self):
-        return 'Comment on line {} of tutor {}: "{}"'.format(self.of_line,
-                                                             self.of_tutor,
-                                                             self.text)
diff --git a/core/serializers/feedback.py b/core/serializers/feedback.py
index a0c9d43e..4fbbab3c 100644
--- a/core/serializers/feedback.py
+++ b/core/serializers/feedback.py
@@ -67,7 +67,8 @@ class FeedbackCommentSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.FeedbackComment
-        fields = ('text',
+        fields = ('pk',
+                  'text',
                   'created',
                   'of_tutor',
                   'of_line',
diff --git a/core/tests/test_feedback.py b/core/tests/test_feedback.py
index 2f14e074..c219388e 100644
--- a/core/tests/test_feedback.py
+++ b/core/tests/test_feedback.py
@@ -387,3 +387,76 @@ class FeedbackPatchTestCase(APITestCase):
 
         response = self.client.patch(self.url, data, format='json')
         self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code)
+
+
+class FeedbackCommentApiEndpointTest(APITestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        cls.burl = '/api/feedback/'
+        cls.data = make_test_data({
+            'submission_types': [
+                {
+                    'name': '01. Sort this or that',
+                    'full_score': 35,
+                    'description': 'Very complicated',
+                    'solution': 'Trivial!'
+                }],
+            'students': [
+                {'username': 'student01'}
+            ],
+            'tutors': [
+                {'username': 'tutor01'},
+                {'username': 'tutor02'},
+            ],
+            'reviewers': [
+                {'username': 'reviewer01'},
+            ],
+            'submissions': [{
+                'text': 'function blabl\n'
+                        '   on multi lines\n'
+                        '       for blabla in bla:\n',
+                'type': '01. Sort this or that',
+                'user': 'student01',
+                'feedback': {
+                    'score': 5,
+                    'is_final': True,
+                    'feedback_lines': {
+                        '1': [{'text': 'This is very bad!',
+                               'of_tutor': 'tutor01'}],
+                        '2': [{'text': 'And this is even worse!',
+                               'of_tutor': 'tutor02'}],
+                    }
+                }
+            }]
+        })
+
+    def setUp(self):
+        self.url = '/api/feedback-comment/%s/'
+        self.tutor01 = self.data['tutors'][0]
+        self.tutor02 = self.data['tutors'][1]
+
+    def test_tutor_can_delete_own_comment(self):
+        self.client.force_authenticate(user=self.tutor01)
+        comment = FeedbackComment.objects.get(of_tutor=self.tutor01)
+        response = self.client.delete(self.url % comment.pk)
+        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
+
+    def test_tutor_cannot_delete_foreign_comment(self):
+        self.client.force_authenticate(user=self.tutor02)
+        comment = FeedbackComment.objects.get(of_tutor=self.tutor02)
+        self.client.force_authenticate(self.tutor01)
+        response = self.client.delete(self.url % comment.pk)
+        self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code)
+
+    def test_reviewer_can_delete_everything_they_want(self):
+        reviewer = self.data['reviewers'][0]
+        self.client.force_authenticate(user=reviewer)
+        comment01 = FeedbackComment.objects.get(of_tutor=self.tutor02)
+        comment02 = FeedbackComment.objects.get(of_tutor=self.tutor02)
+
+        response = self.client.delete(self.url % comment01.pk)
+        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
+
+        response = self.client.delete(self.url % comment02.pk)
+        self.assertEqual(status.HTTP_204_NO_CONTENT, response.status_code)
diff --git a/core/urls.py b/core/urls.py
index a9950095..61a0cfc5 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -9,12 +9,14 @@ router.register('student', views.StudentReviewerApiViewSet,
                 base_name='student')
 router.register('examtype', views.ExamApiViewSet)
 router.register('feedback', views.FeedbackApiView)
+router.register('feedback-comment', views.FeedbackCommentApiView)
+router.register('submission', views.SubmissionViewSet,
+                base_name='submission')
 router.register('submissiontype', views.SubmissionTypeApiView)
 router.register('tutor', views.TutorApiViewSet, base_name='tutor')
 router.register('subscription', views.SubscriptionApiViewSet,
                 base_name='subscription')
 router.register('assignment', views.AssignmentApiViewSet)
-router.register('submission', views.SubmissionViewSet, base_name='submission')
 
 # regular views that are not viewsets
 regular_views_urlpatterns = [
diff --git a/core/views/__init__.py b/core/views/__init__.py
index beac6e7a..e653604f 100644
--- a/core/views/__init__.py
+++ b/core/views/__init__.py
@@ -1,4 +1,4 @@
-from .feedback import FeedbackApiView  # noqa
+from .feedback import FeedbackApiView, FeedbackCommentApiView  # noqa
 from .subscription import SubscriptionApiViewSet, AssignmentApiViewSet  # noqa
 from .common_views import *  # noqa
 from .export import StudentCSVExport  # noqa
diff --git a/core/views/feedback.py b/core/views/feedback.py
index 3e9dc9fd..2a9700a4 100644
--- a/core/views/feedback.py
+++ b/core/views/feedback.py
@@ -108,3 +108,18 @@ class FeedbackApiView(
 
         serializer.save()
         return Response(serializer.data)
+
+
+class FeedbackCommentApiView(viewsets.GenericViewSet):
+    """ Gets a list of an individual exam by Id if provided """
+    permission_classes = (permissions.IsTutorOrReviewer,)
+    queryset = models.FeedbackComment.objects.all()
+
+    def destroy(self, request, *args, **kwargs):
+        comment = self.get_object()
+
+        user = request.user
+        if user.role == models.UserAccount.TUTOR and user != comment.of_tutor:
+            raise PermissionDenied(detail='Can only delete your own commits.')
+
+        return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/docker-compose.yml b/docker-compose.yml
index c48787ce..baf820d4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,6 +7,8 @@ services:
     restart: always
     networks:
       - default
+    ports:
+      6543:5432
 
   grady:
     image: docker.gitlab.gwdg.de/j.michal/grady:master
-- 
GitLab