Browse Source

feat(sync): add content version management endpoints and fix author serializer

master
Mohsen Taba 3 weeks ago
parent
commit
af2f3d61b4
  1. 63
      apps/hadis/serializers/serializers_admin.py
  2. 4
      apps/hadis/urls.py
  3. 90
      apps/hadis/views_admin.py

63
apps/hadis/serializers/serializers_admin.py

@ -10,7 +10,8 @@ from ..models import (
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText,
CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage,
OriginalTextReference, OriginalTextReferenceImage
OriginalTextReference, OriginalTextReferenceImage,
ContentRelease
)
from .category import LocalizedField
@ -1098,6 +1099,11 @@ class AdminBookTypeSerializer(serializers.ModelSerializer):
class AdminBookAuthorSerializer(serializers.ModelSerializer):
image = AbsoluteImageField(required=False, allow_null=True)
remove_image = serializers.BooleanField(write_only=True, required=False, default=False)
book_references = serializers.PrimaryKeyRelatedField(
many=True,
required=False,
queryset=BookReference.objects.all(),
)
class Meta:
model = BookAuthor
@ -1125,28 +1131,47 @@ class AdminBookAuthorSerializer(serializers.ModelSerializer):
import json
mutable_data = safe_copy_data(data)
for json_field in ["name", "known_as", "scholarity", "tabaqa", "rutba", "features", "book_references"]:
val = mutable_data.get(json_field)
if isinstance(val, str):
try:
mutable_data[json_field] = json.loads(val)
except ValueError:
pass
if json_field in mutable_data:
val = mutable_data.get(json_field)
if isinstance(val, str):
try:
mutable_data[json_field] = json.loads(val)
except ValueError:
pass
for int_field in ["birth_year_hijri", "death_year_hijri", "birth_year_miladi", "death_year_miladi", "related_narrator"]:
val = mutable_data.get(int_field)
if val == "" or val == "null" or val == "undefined":
mutable_data[int_field] = None
if int_field in mutable_data:
val = mutable_data.get(int_field)
if val == "" or val == "null" or val == "undefined":
mutable_data[int_field] = None
return super().to_internal_value(mutable_data)
def create(self, validated_data):
validated_data.pop("remove_image", False)
return super().create(validated_data)
book_references = validated_data.pop("book_references", None)
author = super().create(validated_data)
if book_references:
for book in book_references:
book.author = author
book.save(update_fields=["author"])
return author
def update(self, instance, validated_data):
remove_image = validated_data.pop("remove_image", False)
book_references = validated_data.pop("book_references", None)
if remove_image and instance.image:
instance.image.delete(save=False)
instance.image = None
return super().update(instance, validated_data)
author = super().update(instance, validated_data)
if book_references is not None:
current_ids = set(author.book_references.values_list('id', flat=True))
new_ids = {b.id for b in book_references}
to_remove = current_ids - new_ids
if to_remove:
BookReference.objects.filter(id__in=to_remove, author=author).update(author=None)
to_add = new_ids - current_ids
if to_add:
BookReference.objects.filter(id__in=to_add).update(author=author)
return author
class AdminBookAttributeSerializer(serializers.ModelSerializer):
@ -1775,3 +1800,17 @@ class AdminBookEditionSerializer(serializers.ModelSerializer):
author_links_dict["editor_author_id"] = ed_author_id
self._handle_author_links(edition, author_links_dict)
return edition
class AdminContentReleaseSerializer(serializers.ModelSerializer):
class Meta:
model = ContentRelease
fields = [
"id",
"version_name",
"published_at",
"description",
"is_active",
]
read_only_fields = ["id", "published_at"]

4
apps/hadis/urls.py

@ -19,7 +19,8 @@ from .views_admin import (
AdminTransmitterViewSet, AdminTransmitterReliabilityViewSet, DovodiBookSubjectAreaViewSet, DovodiBookTypeViewSet,
DovodiNarratorLayerViewSet, DovodiTransmitterReliabilityViewSet, DovodiOpinionStatusViewSet,
DovodiHadisSectViewSet, DovodiHadisCategoryViewSet, AdminCategoryQuranVerseViewSet, AdminHadisTransmitterViewSet,
AdminTransmitterOpinionViewSet, AdminTransmitterOriginalTextViewSet, AdminBookEditionViewSet, AdminBookVolumeViewSet
AdminTransmitterOpinionViewSet, AdminTransmitterOriginalTextViewSet, AdminBookEditionViewSet, AdminBookVolumeViewSet,
AdminContentReleaseViewSet
)
from .views.reference_v2 import BookAuthorListView, BookAuthorDetailView, AuthorReferencesListView, BookAuthorSyncView
from django.views.decorators.cache import cache_page
@ -60,6 +61,7 @@ admin_router.register(r'transmitter-opinions', AdminTransmitterOpinionViewSet, b
admin_router.register(r'transmitter-original-texts', AdminTransmitterOriginalTextViewSet, basename='admin-transmitter-original-texts')
admin_router.register(r'book-editions', AdminBookEditionViewSet, basename='admin-book-editions')
admin_router.register(r'book-volumes', AdminBookVolumeViewSet, basename='admin-book-volumes')
admin_router.register(r'content-version', AdminContentReleaseViewSet, basename='admin-content-version')
# Hide admin viewsets from swagger
for prefix, viewset, basename in admin_router.registry:

90
apps/hadis/views_admin.py

@ -12,7 +12,8 @@ from utils.pagination import StandardResultsSetPagination, OptionalPagination
from .models import (
Hadis, HadisCategory, HadisCollection, HadisInCollection, HadisCorrection, HadisInterpretation, HadisStatus, HadisTag, HadisSect, HadisTransmitter,
BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume,
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText
Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, TransmitterOpinion, TransmitterOriginalText,
ContentRelease
)
from .serializers.serializers_admin import (
AdminHadisCategorySerializer,
@ -46,6 +47,7 @@ from .serializers.serializers_admin import (
AdminTransmitterOriginalTextSerializer,
AdminBookEditionSerializer,
AdminBookVolumeSerializer,
AdminContentReleaseSerializer,
)
@ -805,3 +807,89 @@ class AdminBookVolumeViewSet(ModelViewSet):
else:
queryset = queryset.filter(edition_id=edition_id)
return queryset.order_by("title", "id")
import re
def compute_next_version(current_version_str: str) -> str:
"""
Increments the last integer part of a version string.
Examples:
'V 0.51' -> 'V 0.52'
'V0.5' -> 'V0.6'
'1.0.0' -> '1.0.1'
'1' -> '2'
'v1.9' -> 'v1.10'
"""
if not current_version_str or not str(current_version_str).strip():
return "V 1.0"
matches = list(re.finditer(r'\d+', str(current_version_str)))
if not matches:
return f"{str(current_version_str).strip()} 2"
last_match = matches[-1]
start, end = last_match.span()
num_str = last_match.group()
next_num = int(num_str) + 1
if len(num_str) > 1 and num_str.startswith('0'):
formatted_num = str(next_num).zfill(len(num_str))
else:
formatted_num = str(next_num)
return str(current_version_str)[:start] + formatted_num + str(current_version_str)[end:]
class AdminContentReleaseViewSet(ModelViewSet):
serializer_class = AdminContentReleaseSerializer
permission_classes = [IsAuthenticated, IsSuperAdmin]
authentication_classes = [TokenAuthentication]
pagination_class = None
def get_queryset(self):
return ContentRelease.objects.all().order_by("-published_at", "-id")
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
current_release = queryset.filter(is_active=True).first()
current_data = self.get_serializer(current_release).data if current_release else None
current_ver_name = current_release.version_name if current_release else "V 0.5"
next_suggested = compute_next_version(current_ver_name)
history_data = self.get_serializer(queryset[:50], many=True).data
return Response({
"current_version": current_data,
"next_suggested_version": next_suggested,
"history": history_data
})
@action(detail=False, methods=["post"], url_path="increment")
def increment(self, request):
current_release = self.get_queryset().filter(is_active=True).first()
current_ver_name = current_release.version_name if current_release else "V 0.5"
version_name = request.data.get("version_name")
if not version_name or not str(version_name).strip():
version_name = compute_next_version(current_ver_name)
else:
version_name = str(version_name).strip()
description = request.data.get("description", "") or ""
# Create new content release
new_release = ContentRelease.objects.create(
version_name=version_name,
description=description,
is_active=True
)
return Response({
"status": "success",
"message": f"Version incremented to {version_name}",
"release": self.get_serializer(new_release).data,
"next_suggested_version": compute_next_version(version_name)
})
Loading…
Cancel
Save