From bced7c6b24f34ec2ebed51893925f5ae81a369cb Mon Sep 17 00:00:00 2001 From: alireza Date: Sun, 4 May 2025 15:52:44 +0330 Subject: [PATCH] feat: rate service --- apps/bookmark/admin.py | 140 +++++++++++++++++- apps/bookmark/migrations/0002_rate.py | 35 +++++ apps/bookmark/models/__init__.py | 2 + .../{models.py => models/bookmark.py} | 2 + apps/bookmark/models/rate.py | 117 +++++++++++++++ apps/bookmark/serializers/__init__.py | 2 + .../bookmark.py} | 2 +- apps/bookmark/serializers/rate.py | 86 +++++++++++ apps/bookmark/urls.py | 9 +- apps/bookmark/views/__init__.py | 2 + apps/bookmark/{views.py => views/bookmark.py} | 4 +- apps/bookmark/views/rate.py | 89 +++++++++++ apps/library/admin.py | 10 +- ...ok_isbn_book_numnber_of_volume_and_more.py | 52 +++++++ apps/library/models.py | 15 +- apps/library/serializers.py | 48 +++++- 16 files changed, 599 insertions(+), 16 deletions(-) create mode 100644 apps/bookmark/migrations/0002_rate.py create mode 100644 apps/bookmark/models/__init__.py rename apps/bookmark/{models.py => models/bookmark.py} (99%) create mode 100644 apps/bookmark/models/rate.py create mode 100644 apps/bookmark/serializers/__init__.py rename apps/bookmark/{serializers.py => serializers/bookmark.py} (97%) create mode 100644 apps/bookmark/serializers/rate.py create mode 100644 apps/bookmark/views/__init__.py rename apps/bookmark/{views.py => views/bookmark.py} (97%) create mode 100644 apps/bookmark/views/rate.py create mode 100644 apps/library/migrations/0006_remove_book_author_book_isbn_book_numnber_of_volume_and_more.py diff --git a/apps/bookmark/admin.py b/apps/bookmark/admin.py index 1f250fa..3ee137d 100644 --- a/apps/bookmark/admin.py +++ b/apps/bookmark/admin.py @@ -1,11 +1,143 @@ from django.contrib import admin -from .models import Bookmark +from django.utils.translation import gettext_lazy as _ +from unfold.admin import ModelAdmin +from unfold.decorators import display, action +from django.utils.html import format_html +from django.urls import reverse -@admin.register(Bookmark) -class BookmarkAdmin(admin.ModelAdmin): - list_display = ('user', 'service', 'content_id', 'status', 'created_at', 'updated_at') +from apps.bookmark.models import Bookmark +from apps.bookmark.models import Rate +from utils.admin import project_admin_site + +class BookmarkAdmin(ModelAdmin): + list_display = ('user', 'display_service', 'content_id', 'status', 'created_at') list_filter = ('service', 'status', 'created_at') search_fields = ('user__username', 'user__email', 'content_id') readonly_fields = ('created_at', 'updated_at') list_per_page = 20 date_hierarchy = 'created_at' + list_filter_submit = True + warn_unsaved_form = True + change_form_show_cancel_button = True + + @display(description=_('Service')) + def display_service(self, obj): + service_colors = { + 'library': 'primary', + 'podcast': 'success', + 'hadith': 'warning', + 'video': 'danger' + } + color = service_colors.get(obj.service, 'secondary') + return format_html( + '{}', + color, + obj.get_service_display() + ) + + fieldsets = ( + (None, { + 'fields': ('user', 'service', 'content_id') + }), + (_('Status'), { + 'fields': ('status',) + }), + (_('Timestamps'), { + 'fields': ('created_at', 'updated_at') + }), + ) + + @action(description=_("View Content")) + def view_content(self, request, obj): + """ + Action to view the related content based on service type + """ + service = obj.service + content_id = obj.content_id + + if service == 'library': + url = reverse('admin:library_book_change', args=[content_id]) + elif service == 'podcast': + url = reverse('admin:podcast_podcast_change', args=[content_id]) + elif service == 'hadith': + url = reverse('admin:hadith_hadith_change', args=[content_id]) + elif service == 'video': + url = reverse('admin:video_video_change', args=[content_id]) + else: + return None + + return url + +class RateAdmin(ModelAdmin): + list_display = ('user', 'display_service', 'content_id', 'display_rate', 'status', 'created_at') + list_filter = ('service', 'rate', 'status', 'created_at') + search_fields = ('user__username', 'user__email', 'content_id') + readonly_fields = ('created_at', 'updated_at') + list_per_page = 20 + date_hierarchy = 'created_at' + list_filter_submit = True + warn_unsaved_form = True + change_form_show_cancel_button = True + + @display(description=_('Service')) + def display_service(self, obj): + service_colors = { + 'library': 'primary', + 'podcast': 'success', + 'hadith': 'warning', + 'video': 'danger' + } + color = service_colors.get(obj.service, 'secondary') + return format_html( + '{}', + color, + obj.get_service_display() + ) + + @display(description=_('Rate')) + def display_rate(self, obj): + # Display stars based on rate value + stars = '★' * obj.rate + '☆' * (5 - obj.rate) + color = 'warning' # Yellow color for stars + return format_html( + '{}', + color, + stars + ) + + fieldsets = ( + (None, { + 'fields': ('user', 'service', 'content_id', 'rate') + }), + (_('Status'), { + 'fields': ('status',) + }), + (_('Timestamps'), { + 'fields': ('created_at', 'updated_at') + }), + ) + + @action(description=_("View Content")) + def view_content(self, request, obj): + """ + Action to view the related content based on service type + """ + service = obj.service + content_id = obj.content_id + + if service == 'library': + url = reverse('admin:library_book_change', args=[content_id]) + elif service == 'podcast': + url = reverse('admin:podcast_podcast_change', args=[content_id]) + elif service == 'hadith': + url = reverse('admin:hadith_hadith_change', args=[content_id]) + elif service == 'video': + url = reverse('admin:video_video_change', args=[content_id]) + else: + return None + + return url + +# Register with project_admin_site +project_admin_site.register(Bookmark, BookmarkAdmin) +project_admin_site.register(Rate, RateAdmin) diff --git a/apps/bookmark/migrations/0002_rate.py b/apps/bookmark/migrations/0002_rate.py new file mode 100644 index 0000000..b651f5d --- /dev/null +++ b/apps/bookmark/migrations/0002_rate.py @@ -0,0 +1,35 @@ +# Generated by Django 5.1.8 on 2025-05-04 15:36 + +import django.core.validators +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookmark', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Rate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('service', models.CharField(choices=[('library', 'Library'), ('podcast', 'Podcast'), ('hadith', 'Hadith'), ('video', 'Video')], max_length=20, verbose_name='Service')), + ('content_id', models.PositiveIntegerField(verbose_name='Content ID')), + ('rate', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)], verbose_name='Rate')), + ('status', models.BooleanField(default=True, verbose_name='Status')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rates', to=settings.AUTH_USER_MODEL, verbose_name='User')), + ], + options={ + 'verbose_name': 'Rate', + 'verbose_name_plural': 'Rates', + 'unique_together': {('user', 'service', 'content_id')}, + }, + ), + ] diff --git a/apps/bookmark/models/__init__.py b/apps/bookmark/models/__init__.py new file mode 100644 index 0000000..d5d868a --- /dev/null +++ b/apps/bookmark/models/__init__.py @@ -0,0 +1,2 @@ +from .bookmark import * +from .rate import * \ No newline at end of file diff --git a/apps/bookmark/models.py b/apps/bookmark/models/bookmark.py similarity index 99% rename from apps/bookmark/models.py rename to apps/bookmark/models/bookmark.py index 54a75fc..108a02d 100644 --- a/apps/bookmark/models.py +++ b/apps/bookmark/models/bookmark.py @@ -1,3 +1,5 @@ + + from django.db import models from django.contrib.auth import get_user_model diff --git a/apps/bookmark/models/rate.py b/apps/bookmark/models/rate.py new file mode 100644 index 0000000..8f8cd07 --- /dev/null +++ b/apps/bookmark/models/rate.py @@ -0,0 +1,117 @@ + +from django.db import models +from django.contrib.auth import get_user_model +from django.core.validators import MinValueValidator, MaxValueValidator +from django.db.models import Avg + +User = get_user_model() + +class Rate(models.Model): + """ + Rate model for different services like library, podcast, hadith, and video. + Users can rate content from 1 to 5. + """ + + class ServiceChoices(models.TextChoices): + LIBRARY = 'library', 'Library' + PODCAST = 'podcast', 'Podcast' + HADITH = 'hadith', 'Hadith' + VIDEO = 'video', 'Video' + + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='rates', verbose_name='User') + service = models.CharField(max_length=20, choices=ServiceChoices.choices, verbose_name='Service') + content_id = models.PositiveIntegerField(verbose_name='Content ID') + rate = models.PositiveSmallIntegerField( + validators=[MinValueValidator(1), MaxValueValidator(5)], + verbose_name='Rate' + ) + status = models.BooleanField(default=True, verbose_name='Status') + created_at = models.DateTimeField(auto_now_add=True, verbose_name='Created At') + updated_at = models.DateTimeField(auto_now=True, verbose_name='Updated At') + + class Meta: + verbose_name = 'Rate' + verbose_name_plural = 'Rates' + unique_together = ('user', 'service', 'content_id') + + def __str__(self): + return f"{self.user.username} - {self.get_service_display()} - {self.content_id} - {self.rate}" + + @classmethod + def get_user_rate(cls, user, service, content_id): + """ + Get the rate information for a specific content by the user. + + Args: + user: User instance + service: Service name (library, podcast, hadith, video) + content_id: ID of the content + + Returns: + Dictionary containing: + - is_rated: Boolean indicating if the content is rated by the user + - rate: The rate value given by the user (1-5) or None if not rated + """ + try: + rate_obj = cls.objects.get( + user=user, + service=service, + content_id=content_id, + status=True + ) + return { + 'is_rated': True, + 'rate': rate_obj.rate + } + except cls.DoesNotExist: + return { + 'is_rated': False, + 'rate': None + } + + @classmethod + def validate_content_exists(cls, service, content_id): + """ + Validate if content with the given ID exists in the specified service. + + Args: + service: Service name (library, podcast, hadith, video) + content_id: ID of the content to validate + + Returns: + Boolean indicating if the content exists + """ + if service == cls.ServiceChoices.LIBRARY: + from apps.library.models import Book + return Book.objects.filter(id=content_id).exists() + elif service == cls.ServiceChoices.PODCAST: + from apps.podcast.models import Podcast + return Podcast.objects.filter(id=content_id).exists() + elif service == cls.ServiceChoices.HADITH: + from apps.hadith.models import Hadith + return Hadith.objects.filter(id=content_id).exists() + elif service == cls.ServiceChoices.VIDEO: + from apps.video.models import Video + return Video.objects.filter(id=content_id).exists() + return False + + @classmethod + def get_average_rate(cls, service, content_id): + """ + Get the average rate for a specific content. + + Args: + service: Service name (library, podcast, hadith, video) + content_id: ID of the content + + Returns: + Float representing the average rate (1-5) or None if no rates + """ + result = cls.objects.filter( + service=service, + content_id=content_id, + status=True + ).aggregate(avg_rate=Avg('rate')) + + return result['avg_rate'] + diff --git a/apps/bookmark/serializers/__init__.py b/apps/bookmark/serializers/__init__.py new file mode 100644 index 0000000..d5d868a --- /dev/null +++ b/apps/bookmark/serializers/__init__.py @@ -0,0 +1,2 @@ +from .bookmark import * +from .rate import * \ No newline at end of file diff --git a/apps/bookmark/serializers.py b/apps/bookmark/serializers/bookmark.py similarity index 97% rename from apps/bookmark/serializers.py rename to apps/bookmark/serializers/bookmark.py index f2b9da2..ce4fefe 100644 --- a/apps/bookmark/serializers.py +++ b/apps/bookmark/serializers/bookmark.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import Bookmark +from apps.bookmark.models import Bookmark class BookmarkSerializer(serializers.ModelSerializer): diff --git a/apps/bookmark/serializers/rate.py b/apps/bookmark/serializers/rate.py new file mode 100644 index 0000000..559702b --- /dev/null +++ b/apps/bookmark/serializers/rate.py @@ -0,0 +1,86 @@ +from rest_framework import serializers +from ..models.rate import Rate + +class RateSerializer(serializers.ModelSerializer): + """ + Serializer for the Rate model. + """ + class Meta: + model = Rate + fields = ('id', 'service', 'content_id', 'rate', 'status', 'created_at', 'updated_at') + read_only_fields = ('id', 'created_at', 'updated_at') + + def validate(self, data): + """ + Validate that the content exists in the specified service. + """ + service = data.get('service') + content_id = data.get('content_id') + + if not Rate.validate_content_exists(service, content_id): + raise serializers.ValidationError(f"Content with ID {content_id} does not exist in {service} service.") + + return data + + def create(self, validated_data): + """ + Create or update a rate. + If a rate already exists for the user, service, and content_id, update it. + """ + user = self.context['request'].user + service = validated_data.get('service') + content_id = validated_data.get('content_id') + + # Try to get an existing rate + try: + rate_obj = Rate.objects.get( + user=user, + service=service, + content_id=content_id + ) + # Update existing rate + for attr, value in validated_data.items(): + setattr(rate_obj, attr, value) + rate_obj.save() + return rate_obj + except Rate.DoesNotExist: + # Create new rate + return Rate.objects.create(user=user, **validated_data) + +class RateStatusSerializer(serializers.Serializer): + """ + Serializer for checking if a user has rated a content and getting the rate value. + """ + service = serializers.ChoiceField(choices=Rate.ServiceChoices.choices) + content_id = serializers.IntegerField(min_value=1) + + def validate(self, data): + """ + Validate that the content exists in the specified service. + """ + service = data.get('service') + content_id = data.get('content_id') + + if not Rate.validate_content_exists(service, content_id): + raise serializers.ValidationError(f"Content with ID {content_id} does not exist in {service} service.") + + return data + +class AverageRateSerializer(serializers.Serializer): + """ + Serializer for getting the average rate of a content. + """ + service = serializers.ChoiceField(choices=Rate.ServiceChoices.choices) + content_id = serializers.IntegerField(min_value=1) + + def validate(self, data): + """ + Validate that the content exists in the specified service. + """ + service = data.get('service') + content_id = data.get('content_id') + + if not Rate.validate_content_exists(service, content_id): + raise serializers.ValidationError(f"Content with ID {content_id} does not exist in {service} service.") + + return data \ No newline at end of file diff --git a/apps/bookmark/urls.py b/apps/bookmark/urls.py index 3e973bb..8117567 100644 --- a/apps/bookmark/urls.py +++ b/apps/bookmark/urls.py @@ -1,10 +1,17 @@ from django.urls import path -from .views import AddBookmarkView, RemoveBookmarkView, BookmarkStatusView +from .views import AddBookmarkView, RemoveBookmarkView, BookmarkStatusView, AddRateView, RemoveRateView, RateStatusView, AverageRateView app_name = 'bookmark' urlpatterns = [ + # Bookmark URLs path('add/', AddBookmarkView.as_view(), name='add_bookmark'), path('remove/', RemoveBookmarkView.as_view(), name='remove_bookmark'), path('status/', BookmarkStatusView.as_view(), name='bookmark_status'), + + # Rate URLs + path('rate/add/', AddRateView.as_view(), name='add_rate'), + path('rate/remove/', RemoveRateView.as_view(), name='remove_rate'), + # path('rate/status/', RateStatusView.as_view(), name='rate_status'), + # path('rate/average/', AverageRateView.as_view(), name='average_rate'), ] \ No newline at end of file diff --git a/apps/bookmark/views/__init__.py b/apps/bookmark/views/__init__.py new file mode 100644 index 0000000..d5d868a --- /dev/null +++ b/apps/bookmark/views/__init__.py @@ -0,0 +1,2 @@ +from .bookmark import * +from .rate import * \ No newline at end of file diff --git a/apps/bookmark/views.py b/apps/bookmark/views/bookmark.py similarity index 97% rename from apps/bookmark/views.py rename to apps/bookmark/views/bookmark.py index 34ab88c..a624165 100644 --- a/apps/bookmark/views.py +++ b/apps/bookmark/views/bookmark.py @@ -5,8 +5,8 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.generics import CreateAPIView, DestroyAPIView from rest_framework.exceptions import ValidationError -from .models import Bookmark -from .serializers import BookmarkSerializer +from apps.bookmark.models import Bookmark +from apps.bookmark.serializers import BookmarkSerializer class AddBookmarkView(CreateAPIView): diff --git a/apps/bookmark/views/rate.py b/apps/bookmark/views/rate.py new file mode 100644 index 0000000..b79500f --- /dev/null +++ b/apps/bookmark/views/rate.py @@ -0,0 +1,89 @@ +from rest_framework import status +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from apps.bookmark.models import Rate +from apps.bookmark.serializers import RateSerializer, RateStatusSerializer, AverageRateSerializer + +class AddRateView(APIView): + """ + API view for adding or updating a rate. + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + """ + Add or update a rate for a content. + """ + serializer = RateSerializer(data=request.data, context={'request': request}) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class RemoveRateView(APIView): + """ + API view for removing a rate. + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + """ + Remove a rate by setting its status to False. + """ + serializer = RateStatusSerializer(data=request.data) + if serializer.is_valid(): + service = serializer.validated_data['service'] + content_id = serializer.validated_data['content_id'] + + try: + rate = Rate.objects.get( + user=request.user, + service=service, + content_id=content_id + ) + rate.status = False + rate.save() + return Response({'message': 'Rate removed successfully'}, status=status.HTTP_200_OK) + except Rate.DoesNotExist: + return Response({'message': 'Rate not found'}, status=status.HTTP_404_NOT_FOUND) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class RateStatusView(APIView): + """ + API view for checking if a user has rated a content and getting the rate value. + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + """ + Check if a user has rated a content and get the rate value. + """ + serializer = RateStatusSerializer(data=request.data) + if serializer.is_valid(): + service = serializer.validated_data['service'] + content_id = serializer.validated_data['content_id'] + + rate_info = Rate.get_user_rate(request.user, service, content_id) + return Response(rate_info, status=status.HTTP_200_OK) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class AverageRateView(APIView): + """ + API view for getting the average rate of a content. + """ + def post(self, request): + """ + Get the average rate of a content. + """ + serializer = AverageRateSerializer(data=request.data) + if serializer.is_valid(): + service = serializer.validated_data['service'] + content_id = serializer.validated_data['content_id'] + + avg_rate = Rate.get_average_rate(service, content_id) + return Response({'average_rate': avg_rate}, status=status.HTTP_200_OK) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/apps/library/admin.py b/apps/library/admin.py index 355f5a5..87407b3 100644 --- a/apps/library/admin.py +++ b/apps/library/admin.py @@ -57,13 +57,19 @@ class BookAdmin(ModelAdmin): fieldsets = ( (None, { - 'fields': ('title', 'summary', 'description', 'thumbnail', 'pages_count') + 'fields': () + }), + ('Detail', { + 'fields': ('title', 'slogan', 'thumbnail', 'pages_count', 'publisher', 'year_of_publication', 'isbn', 'numnber_of_volume') }), + ("Summary", { + 'fields': ('summary_title', 'summary') + }), (_('Status'), { 'fields': ('status', 'pin') }), (_('File Information'), { - 'fields': ('file_type', 'book_file') + 'fields': ('file_type', 'book_file', ) }), (_('Relations'), { 'fields': ('categories', 'collections') diff --git a/apps/library/migrations/0006_remove_book_author_book_isbn_book_numnber_of_volume_and_more.py b/apps/library/migrations/0006_remove_book_author_book_isbn_book_numnber_of_volume_and_more.py new file mode 100644 index 0000000..5a6c7ad --- /dev/null +++ b/apps/library/migrations/0006_remove_book_author_book_isbn_book_numnber_of_volume_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 5.1.8 on 2025-05-04 15:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('library', '0005_bookdownload_delete_bottombookcollection_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='book', + name='author', + ), + migrations.AddField( + model_name='book', + name='isbn', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='numnber_of_volume', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='publisher', + field=models.CharField(blank=True, max_length=655, null=True), + ), + migrations.AddField( + model_name='book', + name='slogan', + field=models.CharField(blank=True, max_length=300, null=True), + ), + migrations.AddField( + model_name='book', + name='summary_title', + field=models.CharField(blank=True, help_text='Summary Title', max_length=512, null=True), + ), + migrations.AddField( + model_name='book', + name='year_of_publication', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='book', + name='summary', + field=models.CharField(blank=True, help_text='Summary', max_length=512, null=True), + ), + ] diff --git a/apps/library/models.py b/apps/library/models.py index 5deba22..1ebe42a 100644 --- a/apps/library/models.py +++ b/apps/library/models.py @@ -98,11 +98,20 @@ class Book(models.Model): title = models.CharField(max_length=255) slug = models.SlugField(max_length=255, unique=True) - - summary = models.CharField(max_length=512, null=True, blank=True, help_text=_('could be null')) + slogan = models.CharField(max_length=300, blank=True, null=True) + + summary_title = models.CharField(max_length=512, null=True, blank=True, help_text=_('Summary Title')) + summary = models.CharField(max_length=512, null=True, blank=True, help_text=_('Summary')) + description = models.TextField(null=True, blank=True, help_text=_('could be null')) thumbnail = models.ImageField(upload_to='book_thumbnails/', null=True, blank=True, help_text=_('image allowed')) - author = models.CharField(max_length=255, null=True, blank=True) + + publisher = models.CharField(max_length=655, null=True, blank=True) + year_of_publication = models.CharField(max_length=255, null=True, blank=True) + # author = models.CharField(max_length=255, null=True, blank=True) + isbn = models.CharField(max_length=255, null=True, blank=True) + numnber_of_volume = models.CharField(max_length=255, null=True, blank=True) + pages_count = models.CharField(verbose_name=_('Number of Pages'), max_length=255, help_text=_('eg. 34'), null=True) status = models.BooleanField(default=True, verbose_name=_('status')) pin = models.BooleanField(default=True, verbose_name=_('Pin to top')) diff --git a/apps/library/serializers.py b/apps/library/serializers.py index cfda41f..740e138 100644 --- a/apps/library/serializers.py +++ b/apps/library/serializers.py @@ -48,6 +48,8 @@ class PinnedBookCollectionSerializer(serializers.ModelSerializer): class BookSerializer(serializers.ModelSerializer): thumbnail = serializers.SerializerMethodField() bookmark = serializers.SerializerMethodField() + user_rate = serializers.SerializerMethodField() + average_rate = serializers.SerializerMethodField() def get_thumbnail(self, obj): if obj.thumbnail: @@ -57,9 +59,10 @@ class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ( - 'id', 'title', 'slug', 'summary', 'description', 'thumbnail', - 'author', 'status', 'pin', 'view_count', 'download_count', - 'file_type', 'book_file', 'created_at', 'bookmark' + 'id', 'title', 'slug', 'summary', 'summary_title', 'thumbnail', 'slogan', + 'status', 'pin', 'view_count', 'download_count', 'publisher', 'year_of_publication', 'isbn', 'numnber_of_volume', + 'file_type', 'book_file', 'created_at', 'bookmark', 'user_rate', + 'average_rate' ) def get_bookmark(self, obj): @@ -75,6 +78,45 @@ class BookSerializer(serializers.ModelSerializer): service='library' ) return book_mark.get('is_bookmarked', False) + + def get_user_rate(self, obj): + """ + Get rate information for this book from the current user. + """ + from apps.bookmark.models.rate import Rate + + # Get the current user from the request context + request = self.context.get('request') + user = request.user if request and request.user.is_authenticated else None + + if not user: + return { + 'is_rated': False, + 'rate': None + } + + # Get rate information using the Rate model's method + rate_info = Rate.get_user_rate( + user=user, + service='library', + content_id=obj.id + ) + + return rate_info + + def get_average_rate(self, obj): + """ + Get the average rate for this book. + """ + from apps.bookmark.models.rate import Rate + + # Get average rate using the Rate model's method + avg_rate = Rate.get_average_rate( + service='library', + content_id=obj.id + ) + + return avg_rate