From 298392ab130690014e7dc00581e55ca4beba1b5f Mon Sep 17 00:00:00 2001 From: Mads Nylund Date: Tue, 26 Sep 2023 18:28:04 +0200 Subject: [PATCH] added model, serializer and view for comment --- app/content/admin/admin.py | 5 ++ app/content/factories/__init__.py | 1 + app/content/factories/comment_factory.py | 16 ++++++ .../0054_event_allow_comments_comment.py | 37 ++++++++++++++ .../migrations/0055_alter_comment_options.py | 17 +++++++ app/content/models/__init__.py | 1 + app/content/models/comment.py | 47 +++++++++++++++++ app/content/models/event.py | 5 ++ app/content/serializers/comment.py | 50 +++++++++++++++++++ app/content/serializers/event.py | 5 ++ app/content/urls.py | 2 + app/content/views/__init__.py | 1 + app/content/views/comment.py | 34 +++++++++++++ app/tests/conftest.py | 6 +++ app/tests/content/test_comment_integration.py | 31 ++++++++++++ 15 files changed, 258 insertions(+) create mode 100644 app/content/factories/comment_factory.py create mode 100644 app/content/migrations/0054_event_allow_comments_comment.py create mode 100644 app/content/migrations/0055_alter_comment_options.py create mode 100644 app/content/models/comment.py create mode 100644 app/content/serializers/comment.py create mode 100644 app/content/views/comment.py create mode 100644 app/tests/content/test_comment_integration.py diff --git a/app/content/admin/admin.py b/app/content/admin/admin.py index 48c5a47ae..234060b44 100644 --- a/app/content/admin/admin.py +++ b/app/content/admin/admin.py @@ -249,3 +249,8 @@ def object_link(self, obj): object_link.admin_order_field = "object_repr" object_link.short_description = "object" + + +@admin.register(models.Comment) +class CommentAdmin(admin.ModelAdmin): + search_fields = ["author__first_name", "author__last_name"] \ No newline at end of file diff --git a/app/content/factories/__init__.py b/app/content/factories/__init__.py index 49b1a5b41..64bd85da0 100644 --- a/app/content/factories/__init__.py +++ b/app/content/factories/__init__.py @@ -9,3 +9,4 @@ from app.content.factories.strike_factory import StrikeFactory from app.content.factories.toddel_factory import ToddelFactory from app.content.factories.priority_pool_factory import PriorityPoolFactory +from app.content.factories.comment_factory import CommentFactory diff --git a/app/content/factories/comment_factory.py b/app/content/factories/comment_factory.py new file mode 100644 index 000000000..ef2fbc149 --- /dev/null +++ b/app/content/factories/comment_factory.py @@ -0,0 +1,16 @@ +import factory + +from factory.django import DjangoModelFactory + +from app.content.models.comment import Comment +from app.content.factories.user_factory import UserFactory + + +class CommentFactory(DjangoModelFactory): + class Meta: + model = Comment + + body = factory.Faker("paragraph", nb_sentences=10) + author = factory.SubFactory(UserFactory) + parent = None + \ No newline at end of file diff --git a/app/content/migrations/0054_event_allow_comments_comment.py b/app/content/migrations/0054_event_allow_comments_comment.py new file mode 100644 index 000000000..107cccea2 --- /dev/null +++ b/app/content/migrations/0054_event_allow_comments_comment.py @@ -0,0 +1,37 @@ +# Generated by Django 4.0.8 on 2023-09-26 15:11 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('content', '0053_event_contact_person'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='allow_comments', + field=models.BooleanField(default=True), + ), + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('body', models.TextField()), + ('object_id', models.PositiveIntegerField()), + ('author', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='comments', to=settings.AUTH_USER_MODEL)), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('parent', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='content.comment', verbose_name='parent')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/app/content/migrations/0055_alter_comment_options.py b/app/content/migrations/0055_alter_comment_options.py new file mode 100644 index 000000000..309757139 --- /dev/null +++ b/app/content/migrations/0055_alter_comment_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0.8 on 2023-09-26 15:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('content', '0054_event_allow_comments_comment'), + ] + + operations = [ + migrations.AlterModelOptions( + name='comment', + options={'ordering': ('-created_at',)}, + ), + ] diff --git a/app/content/models/__init__.py b/app/content/models/__init__.py index 0e49d600a..0581b84c4 100644 --- a/app/content/models/__init__.py +++ b/app/content/models/__init__.py @@ -13,3 +13,4 @@ get_strike_description, get_strike_strike_size, ) +from app.content.models.comment import Comment diff --git a/app/content/models/comment.py b/app/content/models/comment.py new file mode 100644 index 000000000..7f223bb43 --- /dev/null +++ b/app/content/models/comment.py @@ -0,0 +1,47 @@ +from django.db import models +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType + +from app.util.models import BaseModel +from app.common.permissions import BasePermissionModel +from app.common.enums import Groups +from app.content.models.user import User + + +class Comment(BaseModel, BasePermissionModel): + write_access = (Groups.TIHLDE, ) + read_access = (Groups.TIHLDE, ) + + body = models.TextField() + + author = models.ForeignKey( + User, + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + related_name="comments" + ) + + parent = models.ForeignKey( + "self", + blank=True, + null=True, + default=None, + on_delete=models.SET_NULL, + related_name="children", + verbose_name="parent" + ) + + content_type = models.ForeignKey( + ContentType, + on_delete=models.CASCADE + ) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey() + + class Meta: + ordering = ("-created_at",) + + def __str__(self): + return f"Comment by: {self.author.first_name} {self.author.last_name} - created: {self.created_at}" \ No newline at end of file diff --git a/app/content/models/event.py b/app/content/models/event.py index 9cef512b2..69e6b7969 100644 --- a/app/content/models/event.py +++ b/app/content/models/event.py @@ -2,6 +2,7 @@ from django.core.exceptions import ValidationError from django.db import models +from django.contrib.contenttypes.fields import GenericRelation from app.common.enums import AdminGroup, Groups from app.common.permissions import ( @@ -10,6 +11,7 @@ set_user_id, ) from app.content.models import Category +from app.content.models.comment import Comment from app.content.models.user import User from app.forms.enums import EventFormType from app.group.models.group import Group @@ -47,6 +49,9 @@ class Event(BaseModel, OptionalImage, BasePermissionModel): related_name="contact_events", ) + allow_comments = models.BooleanField(default=True) + comments = GenericRelation(Comment) + favorite_users = models.ManyToManyField( User, related_name="favorite_events", blank=True ) diff --git a/app/content/serializers/comment.py b/app/content/serializers/comment.py new file mode 100644 index 000000000..68e47c41c --- /dev/null +++ b/app/content/serializers/comment.py @@ -0,0 +1,50 @@ +from rest_framework import serializers + +from app.content.models.comment import Comment +from app.content.models.event import Event +from app.content.serializers.user import DefaultUserSerializer + +class CommentSerializer(serializers.ModelSerializer): + author = DefaultUserSerializer(read_only=True, required=False) + + class Meta: + model = Comment + fields = ( + "id", + "body", + "created_at", + "updated_at", + "author", + "parent" + ) + + +class CommentCreateAndUpdateSerializer(serializers.ModelSerializer): + content_type = serializers.CharField() + content_id = serializers.FloatField() + + class Meta: + model = Comment + fields = ( + "body", + "author", + "parent", + "content_type", + "content_id" + ) + + def create(self, validated_data): + print("inside create") + print(validated_data) + content_type = validated_data.pop("content_type") + content_id = validated_data.pop("content_id") + author = validated_data.pop("author") + body = validated_data.pop("body") + + if content_type == "event": + event = Event.objects.get(id=int(content_id)) + created_comment = event.comments.create( + author=author, + body=body + ) + print(created_comment) \ No newline at end of file diff --git a/app/content/serializers/event.py b/app/content/serializers/event.py index b18230a8e..42d228488 100644 --- a/app/content/serializers/event.py +++ b/app/content/serializers/event.py @@ -11,6 +11,7 @@ PriorityPoolSerializer, ) from app.content.serializers.user import DefaultUserSerializer +from app.content.serializers.comment import CommentSerializer from app.group.models.group import Group from app.group.serializers.group import SimpleGroupSerializer from app.payment.models.paid_event import PaidEvent @@ -28,6 +29,7 @@ class EventSerializer(serializers.ModelSerializer): required=False, allow_null=True ) contact_person = DefaultUserSerializer(read_only=True, required=False) + comments = CommentSerializer(read_only=True, required=False, many=True) class Meta: model = Event @@ -62,6 +64,8 @@ class Meta: "paid_information", "is_paid_event", "contact_person", + "allow_comments", + "comments" ) def get_paid_information(self, obj): @@ -149,6 +153,7 @@ class Meta: "paid_information", "is_paid_event", "contact_person", + "allow_comments" ) def to_internal_value(self, data): diff --git a/app/content/urls.py b/app/content/urls.py index 786312c5d..cac234282 100644 --- a/app/content/urls.py +++ b/app/content/urls.py @@ -13,6 +13,7 @@ ToddelViewSet, UserCalendarEvents, UserViewSet, + CommentViewSet, accept_form, upload, ) @@ -23,6 +24,7 @@ router.register("toddel", ToddelViewSet) router.register("news", NewsViewSet) router.register("events", EventViewSet, basename="event") +router.register("comments", CommentViewSet, basename="comments") router.register("categories", CategoryViewSet) router.register("short-links", ShortLinkViewSet, basename="short-link") router.register("users", UserViewSet, basename="user") diff --git a/app/content/views/__init__.py b/app/content/views/__init__.py index 2f237d78f..973ecf003 100644 --- a/app/content/views/__init__.py +++ b/app/content/views/__init__.py @@ -11,3 +11,4 @@ from app.content.views.upload import upload from app.content.views.strike import StrikeViewSet from app.content.views.toddel import ToddelViewSet +from app.content.views.comment import CommentViewSet diff --git a/app/content/views/comment.py b/app/content/views/comment.py new file mode 100644 index 000000000..7b47b800d --- /dev/null +++ b/app/content/views/comment.py @@ -0,0 +1,34 @@ +from app.common.viewsets import BaseViewSet +from app.common.mixins import ActionMixin +from app.common.pagination import BasePagination +from app.content.serializers.comment import CommentSerializer, CommentCreateAndUpdateSerializer +from app.content.models.comment import Comment +from app.common.permissions import BasicViewPermission + + +class CommentViewSet(BaseViewSet, ActionMixin): + serializer_class = CommentSerializer + # permission_classes = [BasicViewPermission] + queryset = Comment.objects.all() + pagination_class = BasePagination + + def retrieve(self, request, pk): + try: + comment = self.get_object() + print(comment) + except Exception as e: + print(e) + + def create(self, request, *args, **kwargs): + data = request.data + print(data) + + serializer = CommentCreateAndUpdateSerializer( + data=data, context={"request": request} + ) + + if serializer.is_valid(): + print("valid") + comment = super().perform_create(serializer) + print("perform create good") + print(comment) \ No newline at end of file diff --git a/app/tests/conftest.py b/app/tests/conftest.py index dea934478..b2abe8c51 100644 --- a/app/tests/conftest.py +++ b/app/tests/conftest.py @@ -20,6 +20,7 @@ RegistrationFactory, ShortLinkFactory, UserFactory, + CommentFactory ) from app.content.factories.toddel_factory import ToddelFactory from app.forms.tests.form_factories import FormFactory, SubmissionFactory @@ -63,6 +64,11 @@ def user(): return UserFactory() +@pytest.fixture() +def comment(): + return CommentFactory() + + @pytest.fixture def token(user): return Token.objects.get(user_id=user.user_id) diff --git a/app/tests/content/test_comment_integration.py b/app/tests/content/test_comment_integration.py new file mode 100644 index 000000000..71488f301 --- /dev/null +++ b/app/tests/content/test_comment_integration.py @@ -0,0 +1,31 @@ +import pytest + +from app.util.test_utils import get_api_client + + +API_COMMENTS_BASE_URL = "/comments/" + +def get_comments_url_detail(comment=None): + return f"{API_COMMENTS_BASE_URL}{comment.id}/" + +def get_comment_data(content_type, content_id, user=None, parent=None): + return { + "body": "test comment body text", + "author": user.user_id, + "parent": None, + "content_type": content_type, + "content_id": content_id + } + + +@pytest.mark.django_db +def test_retrieve_comment_as_user(user): + pass + + +@pytest.mark.django_db +def test_create_comment_as_user(user, event): + client = get_api_client(user=user) + data = get_comment_data("event", event.id, user=user) + + response = client.post(API_COMMENTS_BASE_URL, data) \ No newline at end of file