16 changed files with 599 additions and 16 deletions
-
140apps/bookmark/admin.py
-
35apps/bookmark/migrations/0002_rate.py
-
2apps/bookmark/models/__init__.py
-
2apps/bookmark/models/bookmark.py
-
117apps/bookmark/models/rate.py
-
2apps/bookmark/serializers/__init__.py
-
2apps/bookmark/serializers/bookmark.py
-
86apps/bookmark/serializers/rate.py
-
9apps/bookmark/urls.py
-
2apps/bookmark/views/__init__.py
-
4apps/bookmark/views/bookmark.py
-
89apps/bookmark/views/rate.py
-
10apps/library/admin.py
-
52apps/library/migrations/0006_remove_book_author_book_isbn_book_numnber_of_volume_and_more.py
-
13apps/library/models.py
-
48apps/library/serializers.py
@ -1,11 +1,143 @@ |
|||||
from django.contrib import admin |
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') |
list_filter = ('service', 'status', 'created_at') |
||||
search_fields = ('user__username', 'user__email', 'content_id') |
search_fields = ('user__username', 'user__email', 'content_id') |
||||
readonly_fields = ('created_at', 'updated_at') |
readonly_fields = ('created_at', 'updated_at') |
||||
list_per_page = 20 |
list_per_page = 20 |
||||
date_hierarchy = 'created_at' |
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( |
||||
|
'<span class="badge badge-{}">{}</span>', |
||||
|
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( |
||||
|
'<span class="badge badge-{}">{}</span>', |
||||
|
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( |
||||
|
'<span style="color: var(--bs-{});">{}</span>', |
||||
|
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) |
||||
@ -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')}, |
||||
|
}, |
||||
|
), |
||||
|
] |
||||
@ -0,0 +1,2 @@ |
|||||
|
from .bookmark import * |
||||
|
from .rate import * |
||||
@ -1,3 +1,5 @@ |
|||||
|
|
||||
|
|
||||
from django.db import models |
from django.db import models |
||||
from django.contrib.auth import get_user_model |
from django.contrib.auth import get_user_model |
||||
|
|
||||
@ -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'] |
||||
|
|
||||
@ -0,0 +1,2 @@ |
|||||
|
from .bookmark import * |
||||
|
from .rate import * |
||||
@ -1,5 +1,5 @@ |
|||||
from rest_framework import serializers |
from rest_framework import serializers |
||||
from .models import Bookmark |
|
||||
|
from apps.bookmark.models import Bookmark |
||||
|
|
||||
|
|
||||
class BookmarkSerializer(serializers.ModelSerializer): |
class BookmarkSerializer(serializers.ModelSerializer): |
||||
@ -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 |
||||
@ -1,10 +1,17 @@ |
|||||
from django.urls import path |
from django.urls import path |
||||
from .views import AddBookmarkView, RemoveBookmarkView, BookmarkStatusView |
|
||||
|
from .views import AddBookmarkView, RemoveBookmarkView, BookmarkStatusView, AddRateView, RemoveRateView, RateStatusView, AverageRateView |
||||
|
|
||||
app_name = 'bookmark' |
app_name = 'bookmark' |
||||
|
|
||||
urlpatterns = [ |
urlpatterns = [ |
||||
|
# Bookmark URLs |
||||
path('add/', AddBookmarkView.as_view(), name='add_bookmark'), |
path('add/', AddBookmarkView.as_view(), name='add_bookmark'), |
||||
path('remove/', RemoveBookmarkView.as_view(), name='remove_bookmark'), |
path('remove/', RemoveBookmarkView.as_view(), name='remove_bookmark'), |
||||
path('status/', BookmarkStatusView.as_view(), name='bookmark_status'), |
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'), |
||||
] |
] |
||||
@ -0,0 +1,2 @@ |
|||||
|
from .bookmark import * |
||||
|
from .rate import * |
||||
@ -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) |
||||
@ -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), |
||||
|
), |
||||
|
] |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue