Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scratch Comments Feature #1230

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions backend/coreapp/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib import admin

from .models.comment import Comment
from .models.course import Course, CourseChapter, CourseScenario
from .models.github import GitHubUser
from .models.preset import Preset
Expand All @@ -26,3 +27,4 @@
admin.site.register(Course)
admin.site.register(CourseChapter)
admin.site.register(CourseScenario)
admin.site.register(Comment)
48 changes: 48 additions & 0 deletions backend/coreapp/migrations/0054_comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Generated by Django 5.0.3 on 2024-05-08 19:59

import coreapp.models.comment
import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("coreapp", "0053_rename_mwcps2_compilers"),
]

operations = [
migrations.CreateModel(
name="Comment",
fields=[
(
"slug",
models.SlugField(
default=coreapp.models.comment.gen_comment_id,
primary_key=True,
serialize=False,
),
),
("text", models.TextField(max_length=5000)),
("creation_time", models.DateTimeField(auto_now_add=True)),
(
"owner",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="coreapp.profile",
),
),
(
"scratch",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="coreapp.scratch",
),
),
],
options={
"ordering": ["-creation_time"],
},
),
]
16 changes: 16 additions & 0 deletions backend/coreapp/migrations/0055_alter_comment_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 5.0.3 on 2024-05-10 21:54

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("coreapp", "0054_comment"),
]

operations = [
migrations.AlterModelOptions(
name="comment",
options={"ordering": ["creation_time"]},
),
]
26 changes: 26 additions & 0 deletions backend/coreapp/models/comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db import models
from django.utils.crypto import get_random_string
from .profile import Profile
from .scratch import Scratch


def gen_comment_id() -> str:
return get_random_string(length=32)


class Comment(models.Model):
slug = models.SlugField(primary_key=True, default=gen_comment_id)
scratch = models.ForeignKey(
Scratch, null=True, blank=False, on_delete=models.SET_NULL)
owner = models.ForeignKey(
Profile, null=True, blank=False, on_delete=models.SET_NULL)
text = models.TextField(max_length=5000)
creation_time = models.DateTimeField(auto_now_add=True)
# TODO: Add replies
# TODO: Add last_updated for editing

class Meta:
ordering = ["-creation_time"]

def __str__(self) -> str:
return f'{self.creation_time} - {self.owner} - {self.scratch} - {self.text[:50]}'
26 changes: 26 additions & 0 deletions backend/coreapp/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .models.profile import Profile
from .models.project import Project, ProjectMember
from .models.scratch import Scratch
from .models.comment import Comment


def serialize_profile(request: Request, profile: Profile) -> Dict[str, Any]:
Expand Down Expand Up @@ -316,3 +317,28 @@ class ProjectMemberSerializer(serializers.ModelSerializer[ProjectMember]):
class Meta:
model = ProjectMember
fields = ["username"]


class CommentSerializer(serializers.ModelSerializer[Comment]):
owner = ProfileField(read_only=True)

class Meta:
model = Comment
fields = [
"slug",
"text",
"owner",
"scratch",
"creation_time",
]

def create(self, validated_data: Any) -> Comment:
comment = Comment.objects.create(**validated_data)
return comment

# def validate(self, data: Dict[str, Any]) -> Dict[str, Any]:
# """
# TODO: Validate that the scratch and the user are both valid to allow comment
# creation
# """
# return True
4 changes: 4 additions & 0 deletions backend/coreapp/tests/test_comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from coreapp.tests.common import BaseTestCase

class CommentTest(BaseTestCase):
pass
12 changes: 12 additions & 0 deletions backend/coreapp/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
project,
scratch,
user,
comment,
)

urlpatterns = [
Expand All @@ -24,18 +25,29 @@
*scratch.router.urls,
*preset.router.urls,
*project.router.urls,
*comment.router.urls,
path("user", user.CurrentUser.as_view(), name="current-user"),
path(
"user/scratches",
user.CurrentUserScratchList.as_view(),
name="current-user-scratches",
),
path(
"user/comments",
user.CurrentUserCommentList.as_view(),
name="current-user-comments",
),
path("users/<slug:username>", user.user, name="user-detail"),
path(
"users/<slug:username>/scratches",
user.UserScratchList.as_view(),
name="user-scratches",
),
path(
"users/<slug:username>/comments",
user.UserCommentList.as_view(),
name="user-comments",
),
# TODO: remove
path("compilers", compiler.CompilerDetail.as_view(), name="compilers"),
path("libraries", library.LibraryDetail.as_view(), name="libraries"),
Expand Down
85 changes: 85 additions & 0 deletions backend/coreapp/views/comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from typing import Any, Optional

import django_filters

from rest_framework.pagination import CursorPagination
from rest_framework import mixins, filters, status
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.exceptions import APIException
from rest_framework.routers import DefaultRouter

from ..models.comment import Comment
from ..models.github import GitHubUser
from ..models.profile import Profile
from ..models.scratch import Scratch
from ..serializers import CommentSerializer
from django.contrib.auth.models import User


class GithubLoginException(APIException):
status_code = status.HTTP_403_FORBIDDEN
default_detail = "You must be logged in to Github to perform this action."


class ScratchSlugException(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "Invalid Scratch Slug"


class CommentPagination(CursorPagination):
ordering = "-creation_time"
page_size = 50
page_size_query_param = "page_size"


class CommentViewSet(
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet, # type: ignore
):
queryset = Comment.objects.all()
pagination_class = CommentPagination
filterset_fields = ["scratch", "slug"]
filter_backends = [
django_filters.rest_framework.DjangoFilterBackend,
filters.SearchFilter,
]
search_fields = ["scratch", "owner"]
serializer_class = CommentSerializer

def create(self, request: Request, *args: Any, **kwargs: Any) -> Response:
user: Optional[User] = request.profile.user

if not user:
raise GithubLoginException()
gh_user: Optional[GitHubUser] = user.github
if not gh_user:
raise GithubLoginException()
profile: Profile = Profile.objects.get(user=request.profile.user)
if not profile:
raise GithubLoginException()
scratch = Scratch.objects.get(slug=request.GET.get('scratch_id'))
if not scratch:
raise ScratchSlugException()

serializer = CommentSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
comment = serializer.save()

comment.owner = profile
comment.scratch = scratch
comment.save()

return Response(
CommentSerializer(comment, context={"request": request}).data,
status=status.HTTP_201_CREATED,
)


router = DefaultRouter(trailing_slash=False)
router.register(r"comment", CommentViewSet)
28 changes: 27 additions & 1 deletion backend/coreapp/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from ..models.github import GitHubUser
from ..models.profile import Profile
from ..models.scratch import Scratch
from ..serializers import TerseScratchSerializer, serialize_profile
from ..models.comment import Comment
from ..serializers import TerseScratchSerializer, serialize_profile, CommentSerializer
from .comment import CommentPagination
from .scratch import ScratchPagination


Expand Down Expand Up @@ -71,6 +73,30 @@ def get_queryset(self) -> QuerySet[Scratch]:
return Scratch.objects.filter(owner__user__username=self.kwargs["username"])


class CurrentUserCommentList(generics.ListAPIView): # type: ignore
"""
Gets the current user's comments
"""

pagination_class = CommentPagination
serializer_class = CommentSerializer

def get_queryset(self) -> QuerySet[Comment]:
return Comment.objects.filter(owner=self.request.profile)


class UserCommentList(generics.ListAPIView): # type: ignore
"""
Gets a user's comments
"""

pagination_class = CommentPagination
serializer_class = CommentSerializer

def get_queryset(self) -> QuerySet[Comment]:
return Comment.objects.filter(owner__user__username=self.kwargs["username"])


@api_view(["GET"]) # type: ignore
def user(request: Request, username: str) -> Response:
"""
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"ansi-to-react": "^6.1.6",
"classnames": "^2.5.1",
"codemirror": "^6.0.1",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"downshift": "9.0.0",
"fast-myers-diff": "^3.2.0",
Expand All @@ -46,7 +47,6 @@
"react-contenteditable": "^3.3.7",
"react-dom": "^18.2.0",
"react-laag": "^2.0.5",
"react-timeago": "^7.2.0",
"react-virtualized-auto-sizer": "^1.0.24",
"react-window": "^1.8.10",
"sass": "^1.72.0",
Expand Down
54 changes: 54 additions & 0 deletions frontend/src/components/Dropdown.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

.options {
display: none;
color: var(--g1600);
background: var(--g200);
position: absolute;
overflow-y: auto;
max-height: 320px;
z-index: 20;
width: max-content;
border-radius: 10px;

&.open {
display: block;
}

&.open:empty {
display: none;
}

.option {
color: var(--g1600);
background: var(--g200);
border-radius: 5px;
padding-right: 3px;
padding-left: 3px;
width: 100%;
}

.option:hover {
background: var(--g1600);
color: var(--g200);

}
}

.options::-webkit-scrollbar {
width: 6px;
}

.options::-webkit-scrollbar-track {
box-shadow: inset 0 0 5px var(--g200);
border-radius: 10px;
}

.options::-webkit-scrollbar-thumb {
background: var(--g1600);
border-radius: 10px;
}

.options::-webkit-scrollbar-thumb:hover {
background: gray;
}

Loading
Loading