Skip to content

Commit f0d2376

Browse files
committed
review backend
1 parent 5036993 commit f0d2376

10 files changed

+286
-5
lines changed

backend/ohq/admin.py

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Semester,
1414
Tag,
1515
UserStatistic,
16+
Review
1617
)
1718

1819

@@ -28,3 +29,4 @@
2829
admin.site.register(Announcement)
2930
admin.site.register(Tag)
3031
admin.site.register(UserStatistic)
32+
admin.site.register(Review)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Generated by Django 5.0.3 on 2024-11-10 18:14
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("ohq", "0021_queue_question_timer_enabled_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="Review",
16+
fields=[
17+
(
18+
"id",
19+
models.AutoField(
20+
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
21+
),
22+
),
23+
("content", models.TextField(blank=True)),
24+
(
25+
"rating",
26+
models.IntegerField(
27+
choices=[(1, "ONE"), (2, "TWO"), (3, "THREE"), (4, "FOUR"), (5, "FIVE")]
28+
),
29+
),
30+
],
31+
),
32+
migrations.AddField(
33+
model_name="question",
34+
name="review",
35+
field=models.OneToOneField(
36+
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to="ohq.review"
37+
),
38+
),
39+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.3 on 2024-11-10 18:28
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("ohq", "0022_review_question_review"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="course",
15+
name="reviews",
16+
field=models.BooleanField(default=True),
17+
),
18+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.3 on 2024-11-15 17:45
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("ohq", "0023_course_reviews"),
10+
]
11+
12+
operations = [
13+
migrations.RenameField(
14+
model_name="course",
15+
old_name="reviews",
16+
new_name="allow_reviews",
17+
),
18+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 5.0.3 on 2024-11-15 17:52
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("ohq", "0024_rename_reviews_course_allow_reviews"),
11+
]
12+
13+
operations = [
14+
migrations.RemoveField(
15+
model_name="question",
16+
name="review",
17+
),
18+
migrations.AddField(
19+
model_name="review",
20+
name="question",
21+
field=models.OneToOneField(
22+
blank=True,
23+
null=True,
24+
on_delete=django.db.models.deletion.CASCADE,
25+
to="ohq.question",
26+
),
27+
),
28+
]

backend/ohq/models.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class Course(models.Model):
7474
archived = models.BooleanField(default=False)
7575
invite_only = models.BooleanField(default=False)
7676
members = models.ManyToManyField(User, through="Membership", through_fields=("course", "user"))
77-
77+
allow_reviews = models.BooleanField(default=True)
7878

7979
# MAX_NUMBER_COURSE_USERS = 1000
8080

@@ -244,7 +244,6 @@ class Meta:
244244
def __str__(self):
245245
return f"{self.course}: {self.name}"
246246

247-
248247
class Question(models.Model):
249248
"""
250249
A question asked within a queue.
@@ -287,7 +286,26 @@ class Question(models.Model):
287286
tags = models.ManyToManyField(Tag, blank=True)
288287
student_descriptor = models.CharField(max_length=255, blank=True, null=True)
289288

289+
class Review(models.Model):
290+
"""
291+
TA reviews
292+
"""
293+
RATING_ONE = 1
294+
RATING_TWO = 2
295+
RATING_THREE = 3
296+
RATING_FOUR = 4
297+
RATING_FIVE = 5
298+
RATING_CHOICES = [
299+
(RATING_ONE, "ONE"),
300+
(RATING_TWO, "TWO"),
301+
(RATING_THREE, "THREE"),
302+
(RATING_FOUR, "FOUR"),
303+
(RATING_FIVE, "FIVE")
304+
]
290305

306+
content = models.TextField(blank=True)
307+
rating = models.IntegerField(choices=RATING_CHOICES)
308+
question = models.OneToOneField(Question, on_delete=models.CASCADE, blank=True, null=True)
291309
class CourseStatistic(models.Model):
292310
"""
293311
Most active students/TAs in the past week for a course

backend/ohq/permissions.py

+31
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,34 @@ def has_permission(self, request, view):
504504
return True
505505

506506
return True
507+
508+
class ReviewPermission(permissions.BasePermission):
509+
def has_permission(self, request, view):
510+
# Anonymous users can't do anything
511+
if not request.user.is_authenticated:
512+
return False
513+
514+
membership = Membership.objects.filter(
515+
course=view.kwargs["course_pk"], user=request.user
516+
).first()
517+
518+
# Non-Students can't do anything
519+
if not membership or membership == "TA":
520+
return False
521+
522+
# Only students can create, modify and delete reviews
523+
if membership.kind == "TA":
524+
return False # Deny access to TAs
525+
526+
# Allow access for Head TAs, Professors, and Students (additional checks can be added here if needed)
527+
return membership.kind in ["HEAD_TA", "PROFESSOR", "STUDENT"]
528+
529+
def has_object_permission(self, request, view, obj):
530+
membership = Membership.objects.filter(course=view.kwargs["course_pk"], user=request.user).first()
531+
532+
if membership and membership.kind == "STUDENT":
533+
# Students can only access their own questions
534+
return obj.question.asked_by == request.user
535+
536+
# Allow access for Head TAs and Professors
537+
return membership.kind in ["HEAD_TA", "PROFESSOR"]

backend/ohq/serializers.py

+40-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.core.exceptions import ObjectDoesNotExist
55
from django.utils import timezone
66
from django.utils.crypto import get_random_string
7+
from django.http import JsonResponse
78
from phonenumber_field.serializerfields import PhoneNumberField
89
from rest_framework import serializers
910
from rest_live.signals import save_handler
@@ -22,6 +23,7 @@
2223
QueueStatistic,
2324
Semester,
2425
Tag,
26+
Review
2527
)
2628
from ohq.sms import sendSMSVerification
2729
from ohq.tasks import sendUpNextNotificationTask
@@ -50,7 +52,6 @@ def save(self):
5052
self.validated_data["queue"] = Queue.objects.get(pk=self.context["view"].kwargs["queue_pk"])
5153
return super().save()
5254

53-
5455
class SemesterSerializer(serializers.ModelSerializer):
5556
pretty = serializers.SerializerMethodField()
5657

@@ -209,8 +210,6 @@ class TagSerializer(CourseRouteMixin):
209210
class Meta:
210211
model = Tag
211212
fields = ("id", "name")
212-
213-
214213
class QuestionSerializer(QueueRouteMixin):
215214
asked_by = UserSerializer(read_only=True)
216215
responded_to_by = UserSerializer(read_only=True)
@@ -246,6 +245,7 @@ class Meta:
246245
"should_send_up_soon_notification",
247246
"resolved_note",
248247
"position",
248+
"review"
249249
)
250250

251251
def update(self, instance, validated_data):
@@ -345,6 +345,43 @@ def create(self, validated_data):
345345
continue
346346
return question
347347

348+
def get_review(self, obj):
349+
review = Review.objects.get(question=obj)
350+
serializer = ReviewSerializer(review)
351+
return serializer.data
352+
353+
354+
class ReviewSerializer(serializers.ModelSerializer):
355+
"""
356+
Serializer for review, allowing input of a question object via a nested serializer.
357+
"""
358+
question = serializers.PrimaryKeyRelatedField(
359+
queryset=Question.objects.none(),
360+
write_only=True
361+
)
362+
class Meta:
363+
model = Review
364+
fields = ("id", "content", "rating", "question")
365+
366+
def __init__(self, *args, **kwargs):
367+
super().__init__(*args, **kwargs)
368+
course_pk = self.context.get('course_pk')
369+
if course_pk:
370+
course = Course.objects.filter(pk=course_pk).first()
371+
self.fields['question'].queryset = Question.objects.filter(queue__course=course)
372+
373+
def create(self, validated_data):
374+
# Extract the nested question data
375+
question = validated_data.pop("question")
376+
validated_data["question"] = question
377+
return super().create(validated_data)
378+
379+
def update(self, instance, validated_data):
380+
# Handle question updates if applicable
381+
question = validated_data.pop("question", None)
382+
validated_data["question"] = question
383+
return super().update(instance, validated_data)
384+
348385

349386
class MembershipPrivateSerializer(CourseRouteMixin):
350387
"""

backend/ohq/urls.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
SemesterViewSet,
2020
TagViewSet,
2121
UserView,
22+
ReviewViewSet,
2223
)
2324

2425

@@ -36,6 +37,7 @@
3637
course_router.register("invites", MembershipInviteViewSet, basename="invite")
3738
course_router.register("announcements", AnnouncementViewSet, basename="announcement")
3839
course_router.register("tags", TagViewSet, basename="tag")
40+
course_router.register("reviews", ReviewViewSet, basename = "review")
3941

4042
queue_router = routers.NestedSimpleRouter(course_router, "queues", lookup="queue")
4143
queue_router.register("questions", QuestionViewSet, basename="question")

0 commit comments

Comments
 (0)