diff --git a/backend/Pipfile b/backend/Pipfile index 791d4045..301191b5 100644 --- a/backend/Pipfile +++ b/backend/Pipfile @@ -43,9 +43,6 @@ gunicorn = "*" django-scheduler = "*" typing-extensions = "*" drf-excel = "*" -django-extensions = "*" -django-debug-toolbar = "*" -parameterized = "*" [requires] -python_version = "3.10.8" +python_version = "3" diff --git a/backend/ohq/migrations/0020_auto_20221106_1919.py b/backend/ohq/migrations/0020_auto_20221106_1919.py deleted file mode 100644 index 9ee22740..00000000 --- a/backend/ohq/migrations/0020_auto_20221106_1919.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 3.1.7 on 2022-11-06 19:19 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('ohq', '0019_auto_20211114_1800'), - ] - - operations = [ - migrations.CreateModel( - name='Review', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField()), - ('rating', models.IntegerField(choices=[(1, 'One star'), (2, 'Two stars'), (3, 'Three stars'), (4, 'Four stars'), (5, 'Five stars')])), - ], - ), - migrations.AddField( - model_name='question', - name='review', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='ohq.review'), - ), - migrations.AddConstraint( - model_name='question', - constraint=models.UniqueConstraint(fields=('review',), name='unique_question'), - ), - ] diff --git a/backend/ohq/migrations/0021_auto_20221110_1803.py b/backend/ohq/migrations/0021_auto_20221110_1803.py deleted file mode 100644 index a9cc2fca..00000000 --- a/backend/ohq/migrations/0021_auto_20221110_1803.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.7 on 2022-11-10 18:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('ohq', '0020_auto_20221106_1919'), - ] - - operations = [ - migrations.AlterField( - model_name='review', - name='content', - field=models.TextField(blank=True), - ), - ] diff --git a/backend/ohq/migrations/0022_auto_20221111_0135.py b/backend/ohq/migrations/0022_auto_20221111_0135.py deleted file mode 100644 index ac872992..00000000 --- a/backend/ohq/migrations/0022_auto_20221111_0135.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.1.7 on 2022-11-11 01:35 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('ohq', '0021_auto_20221110_1803'), - ] - - operations = [ - migrations.AlterField( - model_name='question', - name='review', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='ohq.review'), - ), - ] diff --git a/backend/ohq/models.py b/backend/ohq/models.py index f35594d6..bbadb7ad 100644 --- a/backend/ohq/models.py +++ b/backend/ohq/models.py @@ -240,26 +240,6 @@ class Meta: def __str__(self): return f"{self.course}: {self.name}" -class Review(models.Model): - """ - TA reviews within a question - """ - RATING_ONE = 1 - RATING_TWO = 2 - RATING_THREE = 3 - RATING_FOUR = 4 - RATING_FIVE = 5 - RATING_CHOICES = [ - (RATING_ONE, "One star"), - (RATING_TWO, "Two stars"), - (RATING_THREE, "Three stars"), - (RATING_FOUR, "Four stars"), - (RATING_FIVE, "Five stars") - ] - - content = models.TextField(blank=True) - rating = models.IntegerField(choices=RATING_CHOICES) - class Question(models.Model): """ @@ -302,14 +282,6 @@ class Question(models.Model): should_send_up_soon_notification = models.BooleanField(default=False) tags = models.ManyToManyField(Tag, blank=True) student_descriptor = models.CharField(max_length=255, blank=True, null=True) - review = models.OneToOneField(Review, on_delete=models.CASCADE, blank=True, null=True) - - class Meta: - constraints = [ - models.UniqueConstraint( - fields=["review"], name="unique_question" - ) - ] class CourseStatistic(models.Model): @@ -453,4 +425,3 @@ class Review(models.Model): question = models.OneToOneField(Question, related_name="reviews", on_delete=models.CASCADE, primary_key=True, blank=True, null=False) time_updated = models.DateTimeField(auto_now=True) - diff --git a/backend/ohq/permissions.py b/backend/ohq/permissions.py index 7cff3993..28b9ca66 100644 --- a/backend/ohq/permissions.py +++ b/backend/ohq/permissions.py @@ -504,22 +504,31 @@ def has_permission(self, request, view): return True + class ReviewPermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + membership = Membership.objects.get(course=view.kwargs["course_pk"], user=request.user) + question = Question.objects.get(pk=view.kwargs["question_pk"]) + + # Students can get or modify their own review + # Only Head TAs and Professors can get or modify any questions + if view.action in ["retrieve"]: + return question.asked_by == request.user or membership.is_leadership + + if view.action in ["update", "partial_update"]: + return question.asked_by == request.user + def has_permission(self, request, view): - # Anonymous users can't do anything if not request.user.is_authenticated: return False - + membership = Membership.objects.filter( course=view.kwargs["course_pk"], user=request.user ).first() - + # Non-Students can't do anything if membership is None: return False - - # Only students can create, modify and delete reviews - if view.action in ["create", "update", "partial_update", "destroy"]: + + if view.action == "create": return (not membership.is_ta) and (not membership.is_leadership) - - return True diff --git a/backend/ohq/serializers.py b/backend/ohq/serializers.py index c35fda43..c09fc3fb 100644 --- a/backend/ohq/serializers.py +++ b/backend/ohq/serializers.py @@ -4,7 +4,6 @@ from django.core.exceptions import ObjectDoesNotExist from django.utils import timezone from django.utils.crypto import get_random_string -from django.http import JsonResponse from phonenumber_field.serializerfields import PhoneNumberField from rest_framework import serializers from rest_live.signals import save_handler @@ -52,41 +51,6 @@ def save(self): self.validated_data["queue"] = Queue.objects.get(pk=self.context["view"].kwargs["queue_pk"]) return super().save() -class QuestionReviewRouteMixin(serializers.ModelSerializer): - """ - Mixin for serializers that overrides the save method to - properly handle the URL parameter for questions. - """ - - def save(self): - self.validated_data["question"] = Question.objects.get(pk=self.context["view"].kwargs["question_pk"]) - - if self.validated_data["question"].status != "ANSWERED": - return JsonResponse({"detail": "This question has not been answered by a TA yet"}) - - if self.context["request"].method == "POST" and "rating" in self.validated_data: - if "rating" not in self.validated_data: - return JsonResponse({"detail": "A rating must be provided"}) - if self.validated_data["question"].review != None: - return JsonResponse({"detail": "This question is already reviewed."}) - review = Review(content="", rating=self.validated_data["rating"]) - if "content" in self.validated_data: - review.content = self.validated_data["content"] - review.save() - self.validated_data["question"].review = review - self.validated_data["question"].save() - return JsonResponse({"detail": "Your review has been posted."}) - - if self.context["request"].method == "PATCH": - self.validated_data["review"] = Review.objects.get(pk=self.context["view"].kwargs["pk"]) - if "rating" not in self.validated_data and "content" not in self.validated_data: - return JsonResponse({"detail": "Your review does not contain any content or rating."}) - if "rating" in self.validated_data: - self.validated_data["review"].rating = self.validated_data["rating"] - if "content" in self.validated_data: - self.validated_data["review"].content = self.validated_data["content"] - self.validated_data["review"].save() - return JsonResponse({"detail": "Your review is updated"}) class SemesterSerializer(serializers.ModelSerializer): pretty = serializers.SerializerMethodField() @@ -246,26 +210,11 @@ class Meta: fields = ("id", "name") -class ReviewSerializer(QuestionReviewRouteMixin): - """ - Serializer for review - """ - question = serializers.ReadOnlyField(source="question.text") - ta_first_name = serializers.ReadOnlyField(source="question.responded_to_by.first_name") - ta_last_name = serializers.ReadOnlyField(source="question.responded_to_by.last_name") - - class Meta: - model = Review - fields = ("id", "content", "rating", "question", "ta_first_name", "ta_last_name") - read_only_fields = ("question", "ta_first_name", "ta_last_name") - - class QuestionSerializer(QueueRouteMixin): asked_by = UserSerializer(read_only=True) responded_to_by = UserSerializer(read_only=True) tags = TagSerializer(many=True) position = serializers.IntegerField(default=-1, read_only=True) - review = ReviewSerializer(read_only=True) class Meta: model = Question @@ -286,7 +235,6 @@ class Meta: "resolved_note", "position", "student_descriptor", - "review", ) read_only_fields = ( "time_asked", @@ -297,7 +245,6 @@ class Meta: "should_send_up_soon_notification", "resolved_note", "position", - "review", ) def update(self, instance, validated_data): @@ -396,11 +343,6 @@ def create(self, validated_data): except ObjectDoesNotExist: continue return question - - def get_review(self, obj): - review = Review.objects.get(question=obj) - serializer = ReviewSerializer(review) - return serializer.data class MembershipPrivateSerializer(CourseRouteMixin): diff --git a/backend/ohq/views.py b/backend/ohq/views.py index e23ffe0a..77fdfb5d 100644 --- a/backend/ohq/views.py +++ b/backend/ohq/views.py @@ -180,27 +180,6 @@ def get_queryset(self): ) return prefetch(qs, self.get_serializer_class()) - @action(detail=True) - def reviews(self, request, pk): - membership = Membership.objects.get(course=pk, user=self.request.user) - - if membership.kind == "PROFESSOR" or membership.kind_to_pretty == "HEAD_TA" or self.request.user.is_superuser: - questions = Question.objects.select_related("review").filter(queue__in=Queue.objects.filter(course=pk)) - - elif membership.kind == "TA": - questions = Question.objects.select_related("review").filter(queue__in=Queue.objects.filter(course=pk), responded_to_by=self.request.user) - - elif membership.kind == "STUDENT": - questions = Question.objects.select_related("review").filter(queue__in=Queue.objects.filter(course=pk), asked_by=self.request.user) - - reviews = [] - serializer = ReviewSerializer(many=True) - for question in questions: - reviews.append(question.review) - serializer = ReviewSerializer(reviews, many=True) - return JsonResponse(serializer.data, safe=False) - - class QuestionViewSet(viewsets.ModelViewSet, RealtimeMixin): """ @@ -799,41 +778,11 @@ def list(self, request, *args, **kwargs): def get_queryset(self): return Occurrence.objects.filter(pk=self.kwargs["pk"]) -class ReviewViewSet(viewsets.ModelViewSet): - """ - retrieve: - Return a review based on type of user. All reviews are anonymous. - Students can retrieve review made by themselves. - TAs can retrieve reviews made for themselves. - Head TAs/Professor can retrieve any review. - - list: - Return a list of reviews based on type of user. All reviews are anonymous. - Students can retrieve reviews made by themselves. - TAs can retrieve reviews made for themselves. - Head TAs/Professor can retrieve anyreview. - - update: - Update all fields in a review. - You must specify all of the fields or use a patch request. - - partial_update: - Update certain fields in a review. - Only specify the fields that you want to change. - destroy: - Delete a review. - reviewId is required. - """ +class ReviewViewSet(viewsets.ModelViewSet): + # permission_classes = [ReviewPermission | IsSuperuser] serializer_class = ReviewSerializer - permission_classes = [ReviewPermission | IsSuperuser] + queryset = Review.objects.none() def get_queryset(self): - membership = Membership.objects.get(course=self.kwargs["course_pk"], user=self.request.user) - if membership.kind == "TA": - return Review.objects.filter(question=self.kwargs["question_pk"], question__responded_to_by=self.request.user) - - if membership.kind == "STUDENT": - return Review.objects.filter(question=self.kwargs["question_pk"], question__asked_by=self.request.user) - return Review.objects.filter(question=self.kwargs["question_pk"]) diff --git a/backend/requirements.txt b/backend/requirements.txt deleted file mode 100644 index 2bf92101..00000000 --- a/backend/requirements.txt +++ /dev/null @@ -1,88 +0,0 @@ -amqp==5.1.1 -anyio==3.6.2 -asgiref==3.5.2 -async-timeout==4.0.2 -attrs==22.1.0 -autobahn==22.7.1 -Automat==22.10.0 -beautifulsoup4==4.11.1 -billiard==3.6.4.0 -celery==5.2.7 -certifi==2022.9.24 -cffi==1.15.1 -channels==2.4.0 -channels-redis==4.0.0 -charset-normalizer==2.1.1 -click==8.1.3 -click-didyoumean==0.3.0 -click-plugins==1.1.1 -click-repl==0.2.0 -constantly==15.1.0 -cryptography==38.0.3 -daphne==2.5.0 -Deprecated==1.2.13 -dj-database-url==0.5.0 -Django==3.1.7 -django-auto-prefetching==0.2.11 -django-cors-headers==3.11.0 -django-email-tools==0.1.1 -django-extensions==3.1.5 -django-filter==21.1 -django-labs-accounts==0.7.1 -django-phonenumber-field==6.3.0 -django-rest-live==0.7.0 -django-scheduler==0.9.6 -djangorestframework==3.14.0 -djangorestframework-camel-case==1.3.0 -drf-excel==2.2.0 -drf-nested-routers==0.93.4 -et-xmlfile==1.1.0 -gunicorn==20.1.0 -h11==0.14.0 -httptools==0.5.0 -hyperlink==21.0.0 -icalendar==5.0.2 -idna==3.4 -incremental==22.10.0 -kombu==5.2.4 -msgpack==1.0.4 -oauthlib==3.2.2 -openpyxl==3.0.10 -packaging==21.3 -phonenumbers==8.12.57 -prompt-toolkit==3.0.32 -psycopg2==2.9.5 -pyasn1==0.4.8 -pyasn1-modules==0.2.8 -pycparser==2.21 -PyJWT==2.6.0 -pyOpenSSL==22.1.0 -pyparsing==3.0.9 -python-dateutil==2.8.2 -python-dotenv==0.21.0 -pytz==2022.6 -PyYAML==6.0 -redis==4.3.4 -requests==2.28.1 -requests-oauthlib==1.3.1 -sentry-sdk==1.10.1 -service-identity==21.1.0 -six==1.16.0 -sniffio==1.3.0 -soupsieve==2.3.2.post1 -sqlparse==0.4.3 -twilio==7.15.1 -Twisted==22.10.0 -txaio==22.2.1 -typing_extensions==4.4.0 -uritemplate==4.1.1 -urllib3==1.26.12 -uvicorn==0.19.0 -uvloop==0.17.0 -uWSGI==2.0.21 -vine==5.0.0 -watchfiles==0.18.0 -wcwidth==0.2.5 -websockets==10.4 -wrapt==1.14.1 -zope.interface==5.5.1 diff --git a/backend/tests/ohq/test_permissions.py b/backend/tests/ohq/test_permissions.py index 2a5166bd..8326a156 100644 --- a/backend/tests/ohq/test_permissions.py +++ b/backend/tests/ohq/test_permissions.py @@ -18,7 +18,6 @@ Queue, Semester, Tag, - Review, ) @@ -1323,105 +1322,3 @@ def test_modify(self, user): reverse("ohq:occurrence-detail", args=[self.occurrence.id]), {"title": self.new_title, "courseId": self.course.id}, ) - -class ReviewTestCase(TestCase): - def setUp(self): - setUp(self) - self.semester = Semester.objects.create(year=2020, term=Semester.TERM_SUMMER) - self.course = Course.objects.create( - course_code="000", department="Penn Labs", semester=self.semester - ) - self.head_ta = User.objects.create(username="head_ta") - self.ta = User.objects.create(username="ta") - self.other_ta = User.objects.create(username="other_ta") - self.student = User.objects.create(username="student") - self.other_student = User.objects.create(username="other_student") - Membership.objects.create( - course=self.course, user=self.head_ta, kind=Membership.KIND_HEAD_TA - ) - Membership.objects.create(course=self.course, user=self.ta, kind=Membership.KIND_TA) - Membership.objects.create( - course=self.course, user=self.student, kind=Membership.KIND_STUDENT - ) - Membership.objects.create( - course=self.course, user=self.other_student, kind=Membership.KIND_STUDENT - ) - self.queue = Queue.objects.create(name="Queue", course=self.course) - self.question_text = "This is a question" - self.question_1 = Question.objects.create( - queue=self.queue, asked_by=self.student, text=self.question_text, status="ANSWERED" - ) - self.review_1 = Review.objects.create(content="TA was helpful", rating=5) - self.question_1.review = self.review_1 - se;f.question_1.save() - self.question_2 = Question.objects.create( - queue=self.queue, asked_by=self.student, text=self.question_text, status="ANSWERED" - ) - self.review_2 = Review.objects.create(content="TA was mid", rating=2) - self.question_2.review = self.review_2 - se;f.question_2.save() - - - # Expected results - self.expected = { - "list": { - "professor": 200, - "head_ta": 200, - "ta": 200, - "student": 200, - "non_member": 403, - "anonymous": 403, - }, - "retrieve": { - "professor": 200, - "head_ta": 200, - "ta": 200, - "student": 200, - "non_member": 403, - "anonymous": 403, - }, - "modify": { - "professor": 403, - "head_ta": 403, - "ta": 403, - "student": 200, - "non_member": 403, - "anonymous": 403, - }, - } - - @parameterized.expand(users, name_func=get_test_name) - def test_list(self, user): - test( - self, - user, - "list", - "get", - "/api/occurrences/?course=" - + str(self.course.id) - + "&filter_start=" - + self.filter_start - + "&filter_end=" - + self.filter_end, - ) - - @parameterized.expand(users, name_func=get_test_name) - def test_retrieve(self, user): - test( - self, - user, - "retrieve", - "get", - reverse("ohq:occurrence-detail", args=[self.occurrence.id]), - ) - - @parameterized.expand(users, name_func=get_test_name) - def test_modify(self, user): - test( - self, - user, - "modify", - "patch", - reverse("ohq:occurrence-detail", args=[self.occurrence.id]), - {"title": self.new_title, "courseId": self.course.id}, - ) diff --git a/backend/tests/ohq/test_serializers.py b/backend/tests/ohq/test_serializers.py index eace13d7..f70f354d 100644 --- a/backend/tests/ohq/test_serializers.py +++ b/backend/tests/ohq/test_serializers.py @@ -10,13 +10,12 @@ from rest_framework.test import APIClient from schedule.models import Event -from ohq.models import Announcement, Course, Membership, Question, Queue, Semester, Tag, Review +from ohq.models import Announcement, Course, Membership, Question, Queue, Semester, Tag from ohq.serializers import ( CourseCreateSerializer, MembershipSerializer, SemesterSerializer, UserPrivateSerializer, - ReviewSerializer, ) @@ -304,7 +303,7 @@ def setUp(self): def test_create(self, mock_delay): self.client.force_authenticate(user=self.student2) - response = self.client.post( + self.client.post( reverse("ohq:question-list", args=[self.course.id, self.queue.id]), {"text": "Help me", "tags": [{"name": "Tag"}]}, ) @@ -725,205 +724,3 @@ def test_list(self): self.assertEquals(2, len(data)) self.assertEquals(self.course.id, data[0]["course_id"]) self.assertEquals(self.course.id, data[1]["course_id"]) - -class ReviewSerializerTestCase(TestCase): - def setUp(self): - self.client = APIClient() - self.semester = Semester.objects.create(year=2020, term=Semester.TERM_SUMMER) - self.course = Course.objects.create( - course_code="000", department="Penn Labs", semester=self.semester - ) - self.head_ta = User.objects.create(username="head_ta") - self.ta = User.objects.create(username="ta") - self.other_ta = User.objects.create(username="other_ta") - self.student = User.objects.create(username="student") - self.other_student = User.objects.create(username="other_student") - Membership.objects.create( - course=self.course, user=self.head_ta, kind=Membership.KIND_HEAD_TA - ) - Membership.objects.create(course=self.course, user=self.ta, kind=Membership.KIND_TA) - Membership.objects.create( - course=self.course, user=self.student, kind=Membership.KIND_STUDENT - ) - Membership.objects.create( - course=self.course, user=self.other_student, kind=Membership.KIND_STUDENT - ) - self.queue = Queue.objects.create(name="Queue", course=self.course) - self.question_text = "This is a question" - self.question_1 = Question.objects.create( - queue=self.queue, asked_by=self.student, text=self.question_text, status="ANSWERED" - ) - self.review_content_1 = "TA was helpful" - self.review_rating_1 = 5 - self.question_2 = Question.objects.create( - queue=self.queue, asked_by=self.student, text=self.question_text, status="ANSWERED" - ) - self.review_content_2 = "TA was mid" - self.review_rating_2 = 2 - self.question_3 = Question.objects.create( - queue=self.queue, asked_by=self.student, text=self.question_text, status="ANSWERED" - ) - self.review_content_3 = "TA was decent" - self.review_rating_3 = 4 - - def test_create(self): - """ - Ensure students can create a review - """ - self.client.force_authenticate(user=self.student) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_1.id]), - { - "content": self.review_content_1, - "rating": self.review_rating_1 - }, - ) - self.assertEqual(1, Review.objects.all().count()) - - # Ensure only one review can be made for one question - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_1.id]), - { - "content": self.review_content_2, - "rating": self.review_rating_2 - }, - ) - self.assertEqual(1, Review.objects.all().count()) - - # Ensure review cannot be made to unanswered question - unanswered_question = Question.objects.create( - queue=self.queue, asked_by=self.student, text=self.question_text - ) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, unanswered_question.id]), - { - "content": self.review_content_2, - "rating": self.review_rating_2 - }, - ) - self.assertEqual(1, Review.objects.all().count()) - - # Ensure TA cannot create a review - self.client.force_authenticate(user=self.ta) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_2.id]), - { - "content": self.review_content_2, - "rating": self.review_rating_2 - }, - ) - self.assertEqual(1, Review.objects.all().count()) - - # Ensure creating a review without rating does not work - self.client.force_authenticate(user=self.student) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_2.id]), - { - "content": self.review_content_2, - }, - ) - self.assertEqual(1, Review.objects.all().count()) - - # Ensure creating a review without content does work - self.client.force_authenticate(user=self.student) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_2.id]), - { - "rating": self.review_rating_2, - }, - ) - self.assertEqual(2, Review.objects.all().count()) - - # Ensure creating a review of rating less than 1 or more than 5 does not work - self.client.force_authenticate(user=self.student) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_3.id]), - { - "rating": 0, - }, - ) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_3.id]), - { - "rating": 6, - }, - ) - self.assertEqual(2, Review.objects.all().count()) - - def test_list(self): - """ - Ensure students can get a list of their own review - """ - self.client.force_authenticate(user=self.student) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_1.id]), - { - "content": self.review_content_1, - "rating": self.review_rating_1 - }, - ) - response = self.client.get("/api/courses/" +str( self.course.id) - + "/queues/" + str(self.queue.id) - + "/questions/" + str(self.question_1.id) + "/reviews/") - data = json.loads(response.content) - self.assertEqual(1, len(data)) - - # Student cannot get access to review made by other students - self.client.force_authenticate(user=self.other_student) - response = self.client.get("/api/courses/" + str( self.course.id) - + "/queues/" + str(self.queue.id) - + "/questions/" + str(self.question_1.id) + "/reviews/") - data = json.loads(response.content) - self.assertEqual(0, len(data)) - - def test_update(self): - """ - Ensure students can update reviews - """ - self.client.force_authenticate(user=self.student) - self.client.post( - reverse("ohq:review-list", args=[self.course.id, self.queue.id, self.question_1.id]), - { - "content": self.review_content_1, - "rating": self.review_rating_1 - }, - ) - review = Review.objects.first() - response = self.client.patch( - reverse("ohq:review-detail", args=[self.course.id, self.queue.id, self.question_1.id, review.id]), - {"content": "hello"}, - ) - review = Review.objects.first() - self.assertEqual("hello", review.content) - response = self.client.patch( - reverse("ohq:review-detail", args=[self.course.id, self.queue.id, self.question_1.id, review.id]), - {"rating": 2}, - ) - review = Review.objects.first() - self.assertEqual(2, review.rating) - - # Updating reviews without appropriate key will not work - response = self.client.patch( - reverse("ohq:review-detail", args=[self.course.id, self.queue.id, self.question_1.id, review.id]), - {"something": 3}, - ) - self.assertEqual("hello", review.content) - self.assertEqual(2, review.rating) - - # Ensure TAs cannot update reviews - self.client.force_authenticate(user=self.ta) - review = Review.objects.first() - response = self.client.patch( - reverse("ohq:review-detail", args=[self.course.id, self.queue.id, self.question_1.id, review.id]), - {"content": "The best ta ever", "rating": 5}, - ) - review = Review.objects.first() - self.assertEqual("hello", review.content) - self.assertEqual(2, review.rating) - - - - - - -