Browse Source

authors library api update

master
Mohsen Taba 5 days ago
parent
commit
72beedbeb2
  1. 5
      apps/library/admin.py
  2. 24
      apps/library/doc.py
  3. 79
      apps/library/management/commands/populate_author_fields.py
  4. 73
      apps/library/migrations/0005_author_date_of_birth_author_date_of_death_and_more.py
  5. 18
      apps/library/migrations/0006_alter_author_number_of_volumes.py
  6. 18
      apps/library/models.py
  7. 34
      apps/library/serializers.py
  8. 15
      apps/library/serializers_admin.py
  9. 2
      apps/library/views.py
  10. 2
      entrypoint.sh

5
apps/library/admin.py

@ -253,7 +253,10 @@ class AuthorAdmin(ModelAdmin):
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': ('full_name', 'slug', 'thumbnail', 'birth_date', 'death_date')
'fields': ('full_name', 'slug', 'thumbnail', 'image', 'birth_date', 'death_date', 'date_of_birth', 'date_of_death')
}),
(_('Publication Info'), {
'fields': ('researcher_editor', 'publisher', 'publication_place', 'edition_statement', 'year', 'number_of_volumes', 'online_source', 'tag', 'notes')
}), }),
(_('Timestamps'), { (_('Timestamps'), {
'fields': ('created_at', 'updated_at'), 'fields': ('created_at', 'updated_at'),

24
apps/library/doc.py

@ -57,6 +57,14 @@ search_param = openapi.Parameter(
required=False required=False
) )
author_search_param = openapi.Parameter(
'search',
openapi.IN_QUERY,
description="Search authors by full name",
type=openapi.TYPE_STRING,
required=False
)
thumbs_schema = openapi.Schema( thumbs_schema = openapi.Schema(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
@ -75,6 +83,7 @@ author_summary_schema = openapi.Schema(
'full_name': openapi.Schema(type=openapi.TYPE_STRING), 'full_name': openapi.Schema(type=openapi.TYPE_STRING),
'slug': openapi.Schema(type=openapi.TYPE_STRING), 'slug': openapi.Schema(type=openapi.TYPE_STRING),
'thumbnail': thumbs_schema, 'thumbnail': thumbs_schema,
'image': thumbs_schema,
} }
) )
@ -143,8 +152,20 @@ author_schema = openapi.Schema(
'full_name': openapi.Schema(type=openapi.TYPE_STRING, description="Full name of the author"), 'full_name': openapi.Schema(type=openapi.TYPE_STRING, description="Full name of the author"),
'slug': openapi.Schema(type=openapi.TYPE_STRING, description="URL-friendly slug for the author"), 'slug': openapi.Schema(type=openapi.TYPE_STRING, description="URL-friendly slug for the author"),
'thumbnail': thumbs_schema, 'thumbnail': thumbs_schema,
'image': thumbs_schema,
'birth_date': openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_DATE, nullable=True, description="Birth date"), 'birth_date': openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_DATE, nullable=True, description="Birth date"),
'death_date': openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_DATE, nullable=True, description="Death date"), 'death_date': openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_DATE, nullable=True, description="Death date"),
'date_of_birth': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Date of birth (text/historical format)"),
'date_of_death': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Date of death (text/historical format)"),
'researcher_editor': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Researcher / Editor"),
'publisher': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Publisher"),
'publication_place': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Publication Place"),
'edition_statement': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Edition Statement"),
'year': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Year"),
'number_of_volumes': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Number of Volumes"),
'notes': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Notes"),
'online_source': openapi.Schema(type=openapi.TYPE_STRING, format=openapi.FORMAT_URI, nullable=True, description="Online Source"),
'tag': openapi.Schema(type=openapi.TYPE_STRING, nullable=True, description="Tag"),
'books_count': openapi.Schema(type=openapi.TYPE_INTEGER, description="Number of active books"), 'books_count': openapi.Schema(type=openapi.TYPE_INTEGER, description="Number of active books"),
}, },
required=['id', 'full_name', 'slug', 'books_count'] required=['id', 'full_name', 'slug', 'books_count']
@ -263,9 +284,12 @@ author_list_swagger = swagger_auto_schema(
Retrieve a paginated list of library authors. Retrieve a paginated list of library authors.
Each author includes thumbnail, full name, and number of active books. Each author includes thumbnail, full name, and number of active books.
You can search authors by their full name using the `search` query parameter.
""", """,
operation_summary="List Authors", operation_summary="List Authors",
tags=["Dobodbi - Library"], tags=["Dobodbi - Library"],
manual_parameters=[author_search_param],
responses={ responses={
200: authors_response, 200: authors_response,
401: "Authentication credentials were not provided or are invalid.", 401: "Authentication credentials were not provided or are invalid.",

79
apps/library/management/commands/populate_author_fields.py

@ -0,0 +1,79 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from apps.library.models import Author
class Command(BaseCommand):
help = 'Populates the newly added fields for all existing authors with sample English data'
def handle(self, *args, **options):
self.stdout.write(self.style.WARNING('--- Starting Author Fields Population ---'))
authors = Author.objects.all()
if not authors.exists():
self.stdout.write(self.style.ERROR('No authors found in the database.'))
return
self.stdout.write(self.style.SUCCESS(f'Found {authors.count()} authors. Commencing update...'))
updated_count = 0
with transaction.atomic():
for author in authors:
# 1. Researcher / Editor
if not author.researcher_editor:
author.researcher_editor = f"Edited and researched by {author.full_name}"
# 2. Publisher
if not author.publisher:
author.publisher = "Imam Javad Publishing Institute"
# 3. Publication Place
if not author.publication_place:
author.publication_place = "Qom, Iran"
# 4. Edition Statement
if not author.edition_statement:
author.edition_statement = "First Edition (Revised)"
# 5. Year
if not author.year:
author.year = "1448 AH"
# 6. Number of Volumes
if not author.number_of_volumes:
author.number_of_volumes = 5
# 7. Notes
if not author.notes:
author.notes = f"This edition contains annotations and key commentaries regarding the works of {author.full_name}."
# 8. Online Source
if not author.online_source:
author.online_source = "https://imamjavad.nwhco.ir"
# 9. Tag
if not author.tag:
author.tag = "Reference Books"
# 10. Copy thumbnail to image if thumbnail exists and image doesn't
if author.thumbnail and not author.image:
author.image = author.thumbnail
# 11. Date of Birth (text)
if not author.date_of_birth:
if author.birth_date:
author.date_of_birth = f"{author.birth_date.year} AD"
else:
author.date_of_birth = "Around 4th Century AH"
# 12. Date of Death (text)
if not author.date_of_death:
if author.death_date:
author.date_of_death = f"{author.death_date.year} AD"
else:
author.date_of_death = "Around 5th Century AH"
author.save()
updated_count += 1
self.stdout.write(f"Updated author: {author.full_name}")
self.stdout.write(self.style.SUCCESS(f'--- Finished! Successfully updated {updated_count} authors ---'))

73
apps/library/migrations/0005_author_date_of_birth_author_date_of_death_and_more.py

@ -0,0 +1,73 @@
# Generated by Django 4.2.30 on 2026-07-16 13:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0004_alter_author_slug_alter_book_slug_and_more'),
]
operations = [
migrations.AddField(
model_name='author',
name='date_of_birth',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Date of Birth'),
),
migrations.AddField(
model_name='author',
name='date_of_death',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Date of Death'),
),
migrations.AddField(
model_name='author',
name='edition_statement',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Edition Statement'),
),
migrations.AddField(
model_name='author',
name='image',
field=models.ImageField(blank=True, help_text='image allowed', null=True, upload_to='author_images/', verbose_name='Image'),
),
migrations.AddField(
model_name='author',
name='notes',
field=models.TextField(blank=True, null=True, verbose_name='Notes'),
),
migrations.AddField(
model_name='author',
name='number_of_volumes',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Number of Volumes'),
),
migrations.AddField(
model_name='author',
name='online_source',
field=models.URLField(blank=True, max_length=512, null=True, verbose_name='Online Source'),
),
migrations.AddField(
model_name='author',
name='publication_place',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Publication Place'),
),
migrations.AddField(
model_name='author',
name='publisher',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Publisher'),
),
migrations.AddField(
model_name='author',
name='researcher_editor',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Researcher / Editor'),
),
migrations.AddField(
model_name='author',
name='tag',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Tag'),
),
migrations.AddField(
model_name='author',
name='year',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Year'),
),
]

18
apps/library/migrations/0006_alter_author_number_of_volumes.py

@ -0,0 +1,18 @@
# Generated by Django 4.2.30 on 2026-07-16 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('library', '0005_author_date_of_birth_author_date_of_death_and_more'),
]
operations = [
migrations.AlterField(
model_name='author',
name='number_of_volumes',
field=models.IntegerField(blank=True, null=True, verbose_name='Number of Volumes'),
),
]

18
apps/library/models.py

@ -97,6 +97,24 @@ class Author(LowercaseSlugMixin, models.Model):
) )
birth_date = models.DateField(null=True, blank=True, verbose_name=_('Birth Date')) birth_date = models.DateField(null=True, blank=True, verbose_name=_('Birth Date'))
death_date = models.DateField(null=True, blank=True, verbose_name=_('Death Date')) death_date = models.DateField(null=True, blank=True, verbose_name=_('Death Date'))
researcher_editor = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Researcher / Editor'))
publisher = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Publisher'))
publication_place = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Publication Place'))
edition_statement = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Edition Statement'))
year = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Year'))
number_of_volumes = models.IntegerField(null=True, blank=True, verbose_name=_('Number of Volumes'))
notes = models.TextField(null=True, blank=True, verbose_name=_('Notes'))
online_source = models.URLField(max_length=512, null=True, blank=True, verbose_name=_('Online Source'))
tag = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Tag'))
image = models.ImageField(
upload_to='author_images/',
null=True,
blank=True,
help_text=_('image allowed'),
verbose_name=_('Image')
)
date_of_birth = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Date of Birth'))
date_of_death = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('Date of Death'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'))

34
apps/library/serializers.py

@ -49,30 +49,47 @@ class PinnedBookCollectionSerializer(serializers.ModelSerializer):
class AuthorSummarySerializer(serializers.ModelSerializer): class AuthorSummarySerializer(serializers.ModelSerializer):
thumbnail = serializers.SerializerMethodField() thumbnail = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
class Meta: class Meta:
model = Author model = Author
fields = ('id', 'full_name', 'slug', 'thumbnail')
fields = ('id', 'full_name', 'slug', 'thumbnail', 'image')
def get_thumbnail(self, obj): def get_thumbnail(self, obj):
if obj.thumbnail: if obj.thumbnail:
return get_thumbs(obj.thumbnail, self.context.get('request')) return get_thumbs(obj.thumbnail, self.context.get('request'))
return None return None
def get_image(self, obj):
if obj.image:
return get_thumbs(obj.image, self.context.get('request'))
return None
class AuthorListSerializer(serializers.ModelSerializer): class AuthorListSerializer(serializers.ModelSerializer):
thumbnail = serializers.SerializerMethodField() thumbnail = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
books_count = serializers.SerializerMethodField() books_count = serializers.SerializerMethodField()
class Meta: class Meta:
model = Author model = Author
fields = ('id', 'full_name', 'slug', 'thumbnail', 'books_count')
fields = (
'id', 'full_name', 'slug', 'thumbnail', 'image', 'books_count',
'birth_date', 'death_date', 'date_of_birth', 'date_of_death',
'researcher_editor', 'publisher', 'publication_place', 'edition_statement',
'year', 'number_of_volumes', 'notes', 'online_source', 'tag'
)
def get_thumbnail(self, obj): def get_thumbnail(self, obj):
if obj.thumbnail: if obj.thumbnail:
return get_thumbs(obj.thumbnail, self.context.get('request')) return get_thumbs(obj.thumbnail, self.context.get('request'))
return None return None
def get_image(self, obj):
if obj.image:
return get_thumbs(obj.image, self.context.get('request'))
return None
def get_books_count(self, obj): def get_books_count(self, obj):
if hasattr(obj, 'books_count_annotation'): if hasattr(obj, 'books_count_annotation'):
return obj.books_count_annotation return obj.books_count_annotation
@ -81,17 +98,28 @@ class AuthorListSerializer(serializers.ModelSerializer):
class AuthorDetailSerializer(serializers.ModelSerializer): class AuthorDetailSerializer(serializers.ModelSerializer):
thumbnail = serializers.SerializerMethodField() thumbnail = serializers.SerializerMethodField()
image = serializers.SerializerMethodField()
books_count = serializers.SerializerMethodField() books_count = serializers.SerializerMethodField()
class Meta: class Meta:
model = Author model = Author
fields = ('id', 'full_name', 'slug', 'thumbnail', 'birth_date', 'death_date', 'books_count')
fields = (
'id', 'full_name', 'slug', 'thumbnail', 'image', 'birth_date', 'death_date',
'date_of_birth', 'date_of_death', 'researcher_editor', 'publisher',
'publication_place', 'edition_statement', 'year', 'number_of_volumes',
'notes', 'online_source', 'tag', 'books_count'
)
def get_thumbnail(self, obj): def get_thumbnail(self, obj):
if obj.thumbnail: if obj.thumbnail:
return get_thumbs(obj.thumbnail, self.context.get('request')) return get_thumbs(obj.thumbnail, self.context.get('request'))
return None return None
def get_image(self, obj):
if obj.image:
return get_thumbs(obj.image, self.context.get('request'))
return None
def get_books_count(self, obj): def get_books_count(self, obj):
if hasattr(obj, 'books_count_annotation'): if hasattr(obj, 'books_count_annotation'):
return obj.books_count_annotation return obj.books_count_annotation

15
apps/library/serializers_admin.py

@ -63,12 +63,20 @@ class AdminLibraryCollectionSerializer(serializers.ModelSerializer):
class AdminAuthorSerializer(serializers.ModelSerializer): class AdminAuthorSerializer(serializers.ModelSerializer):
thumbnail = AbsoluteImageField(required=False, allow_null=True) thumbnail = AbsoluteImageField(required=False, allow_null=True)
image = AbsoluteImageField(required=False, allow_null=True)
books_count = serializers.SerializerMethodField() books_count = serializers.SerializerMethodField()
remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False) remove_thumbnail = serializers.BooleanField(write_only=True, required=False, default=False)
remove_image = serializers.BooleanField(write_only=True, required=False, default=False)
class Meta: class Meta:
model = Author model = Author
fields = ["id", "full_name", "slug", "thumbnail", "birth_date", "death_date", "books_count", "created_at", "updated_at", "remove_thumbnail"]
fields = [
"id", "full_name", "slug", "thumbnail", "image", "birth_date", "death_date",
"date_of_birth", "date_of_death", "researcher_editor", "publisher",
"publication_place", "edition_statement", "year", "number_of_volumes",
"notes", "online_source", "tag", "books_count", "created_at", "updated_at",
"remove_thumbnail", "remove_image"
]
read_only_fields = ["id", "created_at", "updated_at", "books_count"] read_only_fields = ["id", "created_at", "updated_at", "books_count"]
def get_books_count(self, obj): def get_books_count(self, obj):
@ -78,13 +86,18 @@ class AdminAuthorSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
validated_data.pop("remove_thumbnail", False) validated_data.pop("remove_thumbnail", False)
validated_data.pop("remove_image", False)
return super().create(validated_data) return super().create(validated_data)
def update(self, instance, validated_data): def update(self, instance, validated_data):
remove_thumbnail = validated_data.pop("remove_thumbnail", False) remove_thumbnail = validated_data.pop("remove_thumbnail", False)
remove_image = validated_data.pop("remove_image", False)
if remove_thumbnail and instance.thumbnail: if remove_thumbnail and instance.thumbnail:
instance.thumbnail.delete(save=False) instance.thumbnail.delete(save=False)
instance.thumbnail = None instance.thumbnail = None
if remove_image and instance.image:
instance.image.delete(save=False)
instance.image = None
return super().update(instance, validated_data) return super().update(instance, validated_data)

2
apps/library/views.py

@ -256,6 +256,8 @@ class AuthorListView(ListAPIView):
serializer_class = AuthorListSerializer serializer_class = AuthorListSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
authentication_classes = [TokenAuthentication] authentication_classes = [TokenAuthentication]
filter_backends = [SearchFilter]
search_fields = ['full_name']
pagination_class = StandardResultsSetPagination pagination_class = StandardResultsSetPagination
@author_list_swagger @author_list_swagger

2
entrypoint.sh

@ -6,7 +6,7 @@ python manage.py migrate
# python manage.py compilemessages # python manage.py compilemessages
python manage.py collectstatic --noinput python manage.py collectstatic --noinput
python manage.py assign_volume_images --force python manage.py assign_volume_images --force
python manage.py send_test_notifications
# python manage.py send_test_notifications
# python manage.py set_default_category_icons # python manage.py set_default_category_icons
# python manage.py set_hadiscorrection_fixed_text # python manage.py set_hadiscorrection_fixed_text
# Seed Russian data (only runs once, skips if data exists) # Seed Russian data (only runs once, skips if data exists)

Loading…
Cancel
Save