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