diff --git a/customk/classes/urls.py b/customk/classes/urls.py index b66c998..5575250 100644 --- a/customk/classes/urls.py +++ b/customk/classes/urls.py @@ -4,5 +4,4 @@ urlpatterns = [ path("", ClassListView.as_view(), name="class-list"), - ] diff --git a/customk/common/admin.py b/customk/common/admin.py index 8c38f3f..a23ff81 100644 --- a/customk/common/admin.py +++ b/customk/common/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - -# Register your models here. +from django.contrib import admin \ No newline at end of file diff --git a/customk/config/settings.py b/customk/config/settings.py index 7731353..3682cdd 100644 --- a/customk/config/settings.py +++ b/customk/config/settings.py @@ -39,7 +39,7 @@ "django.contrib.staticfiles", ] -CUSTOM_USER_APPS = ["users", "common", "classes", "questions", "reviews"] +CUSTOM_USER_APPS = ["users", "common", "classes", "questions", "reviews", "reactions"] THIRD_PARTY_APPS = ["rest_framework", "channels", "corsheaders"] diff --git a/customk/reactions/__init__.py b/customk/reactions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/customk/reactions/admin.py b/customk/reactions/admin.py new file mode 100644 index 0000000..98ff78f --- /dev/null +++ b/customk/reactions/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from .models import Reaction + + +@admin.register(Reaction) +class ReactionModel(admin.ModelAdmin): + list_display = ('id', 'review', 'reaction', 'get_review_reactions') + + def get_review_reactions(self, obj): + reactions = Reaction.get_review_reactions(obj.review) + return f"Likes: {reactions['likes_count']}, Dislikes: {reactions['dislikes_count']}" + + get_review_reactions.short_description = 'Review Reactions' diff --git a/customk/reactions/apps.py b/customk/reactions/apps.py new file mode 100644 index 0000000..6896154 --- /dev/null +++ b/customk/reactions/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ReactionsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "reactions" diff --git a/customk/reactions/migrations/0001_initial.py b/customk/reactions/migrations/0001_initial.py new file mode 100644 index 0000000..7cd8d84 --- /dev/null +++ b/customk/reactions/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 5.1 on 2024-08-20 07:59 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('reviews', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Reaction', + fields=[ + ('id', models.BigAutoField(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)), + ('reaction', models.IntegerField(choices=[(1, 'Like'), (-1, 'Dislike'), (0, 'No Reaction')], default=0)), + ('review', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='reviews.review')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/customk/reactions/migrations/__init__.py b/customk/reactions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/customk/reactions/models.py b/customk/reactions/models.py new file mode 100644 index 0000000..6f84f2b --- /dev/null +++ b/customk/reactions/models.py @@ -0,0 +1,24 @@ +from django.db import models +from common.models import CommonModel +from django.db.models import Count, Q + + +class Reaction(CommonModel): + user = models.ForeignKey("users.User", on_delete=models.CASCADE) + review = models.ForeignKey("reviews.Review", on_delete=models.CASCADE) + + LIKE = 1 + DISLIKE = -1 + NO_REACTION = 0 + + REACTON_CHOICES = ((LIKE, "Like"), (DISLIKE, "Dislike"), (NO_REACTION, "No Reaction")) + + reaction = models.IntegerField(choices=REACTON_CHOICES, default=NO_REACTION) + + @staticmethod + def get_review_reactions(review): + reactions = Reaction.objects.filter(review=review).aggregate( + likes_count=Count("pk", filter=Q(reaction=Reaction.LIKE)), + dislikes_count=Count("pk", filter=Q(reaction=Reaction.DISLIKE)), + ) + return reactions diff --git a/customk/reactions/tests.py b/customk/reactions/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/customk/reactions/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/customk/reactions/views.py b/customk/reactions/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/customk/reactions/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/customk/reviews/migrations/0001_initial.py b/customk/reviews/migrations/0001_initial.py index 01709ea..1c96f21 100644 --- a/customk/reviews/migrations/0001_initial.py +++ b/customk/reviews/migrations/0001_initial.py @@ -8,31 +8,67 @@ class Migration(migrations.Migration): - initial = True dependencies = [ - ('classes', '0003_classdate_person'), + ("classes", "0003_classdate_person"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Review', + name="Review", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('review', models.TextField()), - ('rating', models.DecimalField(decimal_places=1, max_digits=2, validators=[django.core.validators.MinValueValidator(Decimal('0.5')), django.core.validators.MaxValueValidator(Decimal('5.0'))])), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='classes.class')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ("review", models.TextField()), + ( + "rating", + models.DecimalField( + decimal_places=1, + max_digits=2, + validators=[ + django.core.validators.MinValueValidator(Decimal("0.5")), + django.core.validators.MaxValueValidator(Decimal("5.0")), + ], + ), + ), + ( + "course", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="classes.class" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), ], ), migrations.CreateModel( - name='ReviewImage', + name="ReviewImage", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('image_url', models.URLField(blank=True, null=True)), - ('review', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='reviews.review')), + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ("image_url", models.URLField(blank=True, null=True)), + ( + "review", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="images", + to="reviews.review", + ), + ), ], ), ] diff --git a/customk/reviews/serializers.py b/customk/reviews/serializers.py index 10279c7..0764bca 100644 --- a/customk/reviews/serializers.py +++ b/customk/reviews/serializers.py @@ -5,7 +5,7 @@ class ReviewImageSerializer(serializers.ModelSerializer): class Meta: model = ReviewImage - fields = ['id', 'image_url'] + fields = ["id", "image_url"] class ReviewSerializer(serializers.ModelSerializer): @@ -13,10 +13,10 @@ class ReviewSerializer(serializers.ModelSerializer): class Meta: model = Review - fields = ['id', 'review', 'rating', 'images'] + fields = ["id", "review", "rating", "images"] def create(self, validated_data): - images_data = validated_data.pop('images', []) + images_data = validated_data.pop("images", []) review = Review.objects.create(**validated_data) for image_data in images_data: diff --git a/customk/reviews/urls.py b/customk/reviews/urls.py index 7be1e93..3c8e887 100644 --- a/customk/reviews/urls.py +++ b/customk/reviews/urls.py @@ -2,6 +2,10 @@ from .views import ReviewListView urlpatterns = [ - path('//', ReviewListView.as_view(), name='review-list-create'), - path('//delete//', ReviewListView.as_view(), name='review-list-delete') + path("//", ReviewListView.as_view(), name="review-list-create"), + path( + "//delete//", + ReviewListView.as_view(), + name="review-list-delete", + ), ] diff --git a/customk/reviews/views.py b/customk/reviews/views.py index 5904988..6c2005c 100644 --- a/customk/reviews/views.py +++ b/customk/reviews/views.py @@ -12,11 +12,11 @@ def get(self, request, class_id, *args, **kwargs): try: course = Class.objects.get(id=class_id) except Class.DoesNotExist: - return Response({'class_id': 'Invalid class ID.'}, status=400) + return Response({"class_id": "Invalid class ID."}, status=400) reviews = Review.objects.filter(course=course) if not reviews.exists(): - return Response({'message': 'No reviews found for this class.'}, status=404) + return Response({"message": "No reviews found for this class."}, status=404) serializer = ReviewSerializer(reviews, many=True) @@ -26,36 +26,34 @@ def post(self, request, class_id, *args, **kwargs): try: course = Class.objects.get(id=class_id) except Class.DoesNotExist: - return Response({'class_id': 'Invalid class ID.'}, status=400) + return Response({"class_id": "Invalid class ID."}, status=400) data = request.data.copy() - data['course'] = course.id + data["course"] = course.id serializer = ReviewSerializer(data=request.data) if serializer.is_valid(): serializer.save(user=request.user, course=course) - return Response({ - 'message': 'Review successfully created.', - 'review': serializer.data - }, status=201) + return Response( + {"message": "Review successfully created.", "review": serializer.data}, status=201 + ) return Response(serializer.errors, status=400) def patch(self, request, class_id, *args, **kwargs): - review_id = request.data.get('id') + review_id = request.data.get("id") if not review_id: - return Response({'error': 'Review ID is required for update.'}, status=400) + return Response({"error": "Review ID is required for update."}, status=400) review = get_object_or_404(Review, id=review_id, course_id=class_id) serializer = ReviewSerializer(review, data=request.data, partial=True) if serializer.is_valid(): serializer.save() - return Response({ - 'message': 'Review successfully updated.', - 'review': serializer.data - }, status=200) + return Response( + {"message": "Review successfully updated.", "review": serializer.data}, status=200 + ) return Response(serializer.errors, status=400) @@ -63,8 +61,4 @@ def delete(self, request, class_id, review_id, *args, **kwargs): review = get_object_or_404(Review, id=review_id, course_id=class_id) review.delete() - return Response({'message': 'Review successfully deleted.'}, status=204) - - - - + return Response({"message": "Review successfully deleted."}, status=204)