feat: ADRF async views (phase1) + native async serializers (phase2) + async cache infra

This commit is contained in:
async-upgrade
2026-09-06 14:26:17 +08:00
parent 9a6577f71e
commit 8f488fcaaa
55 changed files with 2224 additions and 1513 deletions
+49 -47
View File
@@ -1,16 +1,18 @@
from rest_framework import serializers
from adrf.serializers import (
ModelSerializer, SerializerMethodField,
)
from .models import Course, Chapter, ChapterContent, CourseMaterial
class ChapterSerializer(serializers.ModelSerializer):
video_poster_url = serializers.SerializerMethodField()
video_local_url = serializers.SerializerMethodField()
class ChapterSerializer(ModelSerializer):
video_poster_url = SerializerMethodField()
video_local_url = SerializerMethodField()
class Meta:
model = Chapter
fields = ['id', 'title', 'sort_order', 'duration', 'is_free', 'video_url', 'video_source', 'video_local_url', 'video_poster_url', 'created_at', 'updated_at']
def get_video_poster_url(self, obj):
async def get_video_poster_url(self, obj):
if obj.video_poster:
request = self.context.get('request')
if request:
@@ -18,7 +20,7 @@ class ChapterSerializer(serializers.ModelSerializer):
return obj.video_poster.url
return ''
def get_video_local_url(self, obj):
async def get_video_local_url(self, obj):
if obj.video_local:
request = self.context.get('request')
if request:
@@ -27,22 +29,22 @@ class ChapterSerializer(serializers.ModelSerializer):
return ''
class ChapterContentSerializer(serializers.ModelSerializer):
class ChapterContentSerializer(ModelSerializer):
class Meta:
model = ChapterContent
fields = ['id', 'chapter', 'content_md', 'content_html', 'md_file_path', 'updated_at']
class MaterialSerializer(serializers.ModelSerializer):
file_url = serializers.SerializerMethodField()
file_size_display = serializers.SerializerMethodField()
file_type_display = serializers.SerializerMethodField()
class MaterialSerializer(ModelSerializer):
file_url = SerializerMethodField()
file_size_display = SerializerMethodField()
file_type_display = SerializerMethodField()
class Meta:
model = CourseMaterial
fields = ['id', 'title', 'file_url', 'file_type', 'file_type_display', 'file_size', 'file_size_display', 'download_count', 'sort_order', 'created_at']
def get_file_url(self, obj):
async def get_file_url(self, obj):
if obj.file:
request = self.context.get('request')
if request:
@@ -50,7 +52,7 @@ class MaterialSerializer(serializers.ModelSerializer):
return obj.file.url
return ''
def get_file_size_display(self, obj):
async def get_file_size_display(self, obj):
size = obj.file_size
if size < 1024:
return f'{size} B'
@@ -59,7 +61,7 @@ class MaterialSerializer(serializers.ModelSerializer):
else:
return f'{size / (1024 * 1024):.1f} MB'
def get_file_type_display(self, obj):
async def get_file_type_display(self, obj):
type_map = {
'pdf': 'PDF',
'code': '代码',
@@ -72,14 +74,14 @@ class MaterialSerializer(serializers.ModelSerializer):
return type_map.get(obj.file_type, '其他')
class ChapterSerializer(serializers.ModelSerializer):
video_poster_url = serializers.SerializerMethodField()
class ChapterSerializer(ModelSerializer):
video_poster_url = SerializerMethodField()
class Meta:
model = Chapter
fields = ['id', 'title', 'sort_order', 'duration', 'is_free', 'video_url', 'video_poster_url', 'created_at', 'updated_at']
def get_video_poster_url(self, obj):
async def get_video_poster_url(self, obj):
if obj.video_poster:
request = self.context.get('request')
if request:
@@ -88,17 +90,17 @@ class ChapterSerializer(serializers.ModelSerializer):
return ''
class ChapterContentSerializer(serializers.ModelSerializer):
class ChapterContentSerializer(ModelSerializer):
class Meta:
model = ChapterContent
fields = ['id', 'chapter', 'content_md', 'content_html', 'md_file_path', 'updated_at']
class CourseListSerializer(serializers.ModelSerializer):
author_name = serializers.SerializerMethodField()
chapters_count = serializers.SerializerMethodField()
students_count = serializers.SerializerMethodField()
cover_image_url = serializers.SerializerMethodField()
class CourseListSerializer(ModelSerializer):
author_name = SerializerMethodField()
chapters_count = SerializerMethodField()
students_count = SerializerMethodField()
cover_image_url = SerializerMethodField()
class Meta:
model = Course
@@ -109,13 +111,13 @@ class CourseListSerializer(serializers.ModelSerializer):
'sort_order', 'is_hot', 'is_new', 'created_at', 'updated_at',
]
def get_chapters_count(self, obj):
return obj.chapters.count()
async def get_chapters_count(self, obj):
return await obj.chapters.acount()
def get_students_count(self, obj):
async def get_students_count(self, obj):
return 0
def get_cover_image_url(self, obj):
async def get_cover_image_url(self, obj):
if obj.cover_image:
request = self.context.get('request')
if request:
@@ -123,18 +125,18 @@ class CourseListSerializer(serializers.ModelSerializer):
return obj.cover_image.url
return ''
def get_author_name(self, obj):
async def get_author_name(self, obj):
if obj.author:
return obj.author.get_full_name() or obj.author.username
return ''
class CourseDetailSerializer(serializers.ModelSerializer):
author_name = serializers.SerializerMethodField()
class CourseDetailSerializer(ModelSerializer):
author_name = SerializerMethodField()
chapters = ChapterSerializer(many=True, read_only=True)
chapters_count = serializers.SerializerMethodField()
students_count = serializers.SerializerMethodField()
cover_image_url = serializers.SerializerMethodField()
chapters_count = SerializerMethodField()
students_count = SerializerMethodField()
cover_image_url = SerializerMethodField()
class Meta:
model = Course
@@ -145,13 +147,13 @@ class CourseDetailSerializer(serializers.ModelSerializer):
'sort_order', 'is_hot', 'is_new', 'created_at', 'updated_at',
]
def get_chapters_count(self, obj):
return obj.chapters.count()
async def get_chapters_count(self, obj):
return await obj.chapters.acount()
def get_students_count(self, obj):
async def get_students_count(self, obj):
return 0
def get_cover_image_url(self, obj):
async def get_cover_image_url(self, obj):
if obj.cover_image:
request = self.context.get('request')
if request:
@@ -159,25 +161,25 @@ class CourseDetailSerializer(serializers.ModelSerializer):
return obj.cover_image.url
return ''
def get_author_name(self, obj):
async def get_author_name(self, obj):
if obj.author:
return obj.author.get_full_name() or obj.author.username
return ''
class CourseCreateUpdateSerializer(serializers.ModelSerializer):
class CourseCreateUpdateSerializer(ModelSerializer):
class Meta:
model = Course
fields = ['title', 'description', 'category', 'level', 'cover_image', 'color', 'icon_name', 'status', 'sort_order', 'is_hot', 'is_new']
def create(self, validated_data):
async def acreate(self, validated_data):
validated_data['author'] = self.context['request'].user
return super().create(validated_data)
return await super().acreate(validated_data)
class CourseManageSerializer(serializers.ModelSerializer):
author_name = serializers.SerializerMethodField()
chapters_count = serializers.SerializerMethodField()
class CourseManageSerializer(ModelSerializer):
author_name = SerializerMethodField()
chapters_count = SerializerMethodField()
class Meta:
model = Course
@@ -187,10 +189,10 @@ class CourseManageSerializer(serializers.ModelSerializer):
'author_name', 'created_at', 'updated_at',
]
def get_chapters_count(self, obj):
return obj.chapters.count()
async def get_chapters_count(self, obj):
return await obj.chapters.acount()
def get_author_name(self, obj):
async def get_author_name(self, obj):
if obj.author:
return obj.author.get_full_name() or obj.author.username
return ''
+136 -103
View File
@@ -3,11 +3,14 @@ from django.conf import settings
from django.db.models import Count
from django.utils import timezone
from django.http import FileResponse, Http404
from django.shortcuts import get_object_or_404
from rest_framework import generics, status
from adrf import generics
from adrf.generics import aget_object_or_404
from adrf.mixins import get_data
from adrf.views import APIView
from asgiref.sync import sync_to_async
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.views import APIView
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
@@ -65,6 +68,12 @@ class CourseListCreateView(generics.ListCreateAPIView):
return qs
async def get(self, request, *args, **kwargs):
return await self.list(request, *args, **kwargs)
async def post(self, request, *args, **kwargs):
return await self.create(request, *args, **kwargs)
@swagger_auto_schema(
tags=['学习'],
operation_summary='获取课程列表',
@@ -76,22 +85,22 @@ class CourseListCreateView(generics.ListCreateAPIView):
],
responses={200: success_response},
)
def list(self, request, *args, **kwargs):
async def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
data = serializer.data
data = await get_data(serializer)
if request.user.is_authenticated:
favorite_course_ids = set(
CourseFavorite.objects.filter(user=request.user).values_list('course_id', flat=True)
)
favorite_course_ids = set([
v async for v in CourseFavorite.objects.filter(user=request.user).values_list('course_id', flat=True)
])
for item in data:
item['is_favorited'] = item['id'] in favorite_course_ids
item['favorites_count'] = CourseFavorite.objects.filter(course_id=item['id']).count()
item['favorites_count'] = await CourseFavorite.objects.filter(course_id=item['id']).acount()
else:
for item in data:
item['is_favorited'] = False
item['favorites_count'] = CourseFavorite.objects.filter(course_id=item['id']).count()
item['favorites_count'] = await CourseFavorite.objects.filter(course_id=item['id']).acount()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@@ -106,12 +115,13 @@ class CourseListCreateView(generics.ListCreateAPIView):
401: unauthorized_response,
},
)
def create(self, request, *args, **kwargs):
async def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save()
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
data = await get_data(serializer)
return create_standardized_response(
data=serializer.data,
data=data,
code=ResponseCode.SUCCESS,
message='课程创建成功',
status_code=status.HTTP_201_CREATED
@@ -140,17 +150,17 @@ class CourseDetailView(APIView):
404: not_found_response,
},
)
def get(self, request, pk):
course = get_object_or_404(Course, pk=pk)
async def get(self, request, pk):
course = await aget_object_or_404(Course, pk=pk)
serializer = CourseDetailSerializer(course, context={'request': request})
data = serializer.data
data = await get_data(serializer)
if request.user.is_authenticated:
data['is_favorited'] = CourseFavorite.objects.filter(
data['is_favorited'] = await CourseFavorite.objects.filter(
user=request.user, course=course
).exists()
).aexists()
else:
data['is_favorited'] = False
data['favorites_count'] = CourseFavorite.objects.filter(course=course).count()
data['favorites_count'] = await CourseFavorite.objects.filter(course=course).acount()
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
@@ -169,8 +179,8 @@ class CourseDetailView(APIView):
404: not_found_response,
},
)
def put(self, request, pk):
course = get_object_or_404(Course, pk=pk)
async def put(self, request, pk):
course = await aget_object_or_404(Course, pk=pk)
if course.author != request.user:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
@@ -178,9 +188,10 @@ class CourseDetailView(APIView):
status_code=status.HTTP_403_FORBIDDEN
)
serializer = CourseCreateUpdateSerializer(course, data=request.data, partial=True, context={'request': request})
if serializer.is_valid():
serializer.save()
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS, message='课程更新成功')
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='课程更新成功')
return create_standardized_error_response(
data=serializer.errors,
code=ResponseCode.VALIDATION_ERROR,
@@ -201,15 +212,15 @@ class CourseDetailView(APIView):
404: not_found_response,
},
)
def delete(self, request, pk):
course = get_object_or_404(Course, pk=pk)
async def delete(self, request, pk):
course = await aget_object_or_404(Course, pk=pk)
if course.author != request.user:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
message='无权操作',
status_code=status.HTTP_403_FORBIDDEN
)
course.delete()
await course.adelete()
return create_standardized_response(code=ResponseCode.SUCCESS, message='课程删除成功', status_code=status.HTTP_204_NO_CONTENT)
@@ -227,6 +238,12 @@ class ChapterListCreateView(generics.ListCreateAPIView):
course_id = self.kwargs['course_id']
return Chapter.objects.filter(course_id=course_id)
async def get(self, request, *args, **kwargs):
return await self.list(request, *args, **kwargs)
async def post(self, request, *args, **kwargs):
return await self.create(request, *args, **kwargs)
@swagger_auto_schema(
tags=['学习'],
operation_summary='获取章节列表',
@@ -236,10 +253,11 @@ class ChapterListCreateView(generics.ListCreateAPIView):
],
responses={200: success_response, 404: not_found_response},
)
def list(self, request, *args, **kwargs):
async def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['学习'],
@@ -256,14 +274,15 @@ class ChapterListCreateView(generics.ListCreateAPIView):
404: not_found_response,
},
)
def create(self, request, *args, **kwargs):
async def create(self, request, *args, **kwargs):
course_id = self.kwargs['course_id']
course = get_object_or_404(Course, pk=course_id)
course = await aget_object_or_404(Course, pk=course_id)
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
serializer.save(course=course)
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)(course=course)
data = await get_data(serializer)
return create_standardized_response(
data=serializer.data,
data=data,
code=ResponseCode.SUCCESS,
message='章节创建成功',
status_code=status.HTTP_201_CREATED
@@ -293,10 +312,11 @@ class ChapterDetailView(APIView):
404: not_found_response,
},
)
def get(self, request, pk):
chapter = get_object_or_404(Chapter, pk=pk)
async def get(self, request, pk):
chapter = await aget_object_or_404(Chapter, pk=pk)
serializer = ChapterSerializer(chapter)
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['学习'],
@@ -313,12 +333,13 @@ class ChapterDetailView(APIView):
404: not_found_response,
},
)
def put(self, request, pk):
chapter = get_object_or_404(Chapter, pk=pk)
async def put(self, request, pk):
chapter = await aget_object_or_404(Chapter, pk=pk)
serializer = ChapterSerializer(chapter, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS, message='章节更新成功')
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='章节更新成功')
return create_standardized_error_response(
data=serializer.errors,
code=ResponseCode.VALIDATION_ERROR,
@@ -338,9 +359,9 @@ class ChapterDetailView(APIView):
404: not_found_response,
},
)
def delete(self, request, pk):
chapter = get_object_or_404(Chapter, pk=pk)
chapter.delete()
async def delete(self, request, pk):
chapter = await aget_object_or_404(Chapter, pk=pk)
await chapter.adelete()
return create_standardized_response(code=ResponseCode.SUCCESS, message='章节删除成功', status_code=status.HTTP_204_NO_CONTENT)
@@ -361,14 +382,15 @@ class ChapterContentView(APIView):
404: not_found_response,
},
)
def get(self, request, chapter_id):
chapter = get_object_or_404(Chapter, pk=chapter_id)
content, created = ChapterContent.objects.get_or_create(
async def get(self, request, chapter_id):
chapter = await aget_object_or_404(Chapter, pk=chapter_id)
content, created = await ChapterContent.objects.aget_or_create(
chapter=chapter,
defaults={'content_md': '', 'content_html': ''}
)
serializer = ChapterContentSerializer(content)
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
@swagger_auto_schema(
tags=['学习'],
@@ -385,16 +407,17 @@ class ChapterContentView(APIView):
404: not_found_response,
},
)
def put(self, request, chapter_id):
chapter = get_object_or_404(Chapter, pk=chapter_id)
content, created = ChapterContent.objects.get_or_create(
async def put(self, request, chapter_id):
chapter = await aget_object_or_404(Chapter, pk=chapter_id)
content, created = await ChapterContent.objects.aget_or_create(
chapter=chapter,
defaults={'content_md': '', 'content_html': ''}
)
serializer = ChapterContentSerializer(content, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS, message='内容保存成功')
if await sync_to_async(serializer.is_valid)():
await sync_to_async(serializer.save)()
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS, message='内容保存成功')
return create_standardized_error_response(
data=serializer.errors,
code=ResponseCode.VALIDATION_ERROR,
@@ -414,6 +437,9 @@ class MyCourseListView(generics.ListAPIView):
qs = qs.filter(status=st)
return qs.order_by('-updated_at')
async def get(self, request, *args, **kwargs):
return await self.list(request, *args, **kwargs)
@swagger_auto_schema(
tags=['学习'],
operation_summary='获取我的课程列表',
@@ -426,10 +452,11 @@ class MyCourseListView(generics.ListAPIView):
401: unauthorized_response,
},
)
def list(self, request, *args, **kwargs):
async def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
class MyCourseBatchView(APIView):
@@ -454,7 +481,7 @@ class MyCourseBatchView(APIView):
401: unauthorized_response,
},
)
def post(self, request):
async def post(self, request):
ids = request.data.get('ids', [])
action = request.data.get('action', '')
if not ids or action not in ('publish', 'draft', 'delete'):
@@ -465,11 +492,11 @@ class MyCourseBatchView(APIView):
)
qs = Course.objects.filter(id__in=ids, author=request.user)
if action == 'delete':
count = qs.delete()[0]
count = (await qs.adelete())[0]
elif action == 'publish':
count = qs.update(status='published')
count = await qs.aupdate(status='published')
elif action == 'draft':
count = qs.update(status='draft')
count = await qs.aupdate(status='draft')
return create_standardized_response(
data={'affected': count},
code=ResponseCode.SUCCESS,
@@ -493,21 +520,26 @@ class CDNStaticFileView(APIView):
404: not_found_response,
},
)
def get(self, request, course_id, chapter_id):
async def get(self, request, course_id, chapter_id):
file_path = os.path.join(
settings.MEDIA_ROOT,
'learn', 'courses', str(course_id), 'chapters', f'{chapter_id}.md'
)
if not os.path.exists(file_path):
raise Http404
fh = open(file_path, "rb")
try:
response = FileResponse(fh, content_type="text/markdown; charset=utf-8")
response["Cache-Control"] = "max-age=86400"
return response
except Exception:
fh.close()
raise
def _open_file():
if not os.path.exists(file_path):
raise Http404
fh = open(file_path, "rb")
try:
response = FileResponse(fh, content_type="text/markdown; charset=utf-8")
response["Cache-Control"] = "max-age=86400"
return response
except Exception:
fh.close()
raise
# TODO: aiohttp 化(当前用 sync_to_async 兜底避免阻塞事件循环)
return await sync_to_async(_open_file)()
class CourseFavoriteToggleView(APIView):
permission_classes = [IsAuthenticated]
@@ -525,22 +557,22 @@ class CourseFavoriteToggleView(APIView):
404: not_found_response,
},
)
def post(self, request, pk):
course = Course.objects.filter(pk=pk, status='published').first()
async def post(self, request, pk):
course = await Course.objects.filter(pk=pk, status='published').afirst()
if not course:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
message='课程不存在',
status_code=status.HTTP_404_NOT_FOUND
)
fav, created = CourseFavorite.objects.get_or_create(user=request.user, course=course)
fav, created = await CourseFavorite.objects.aget_or_create(user=request.user, course=course)
if not created:
fav.delete()
count = CourseFavorite.objects.filter(course=course).count()
await fav.adelete()
count = await CourseFavorite.objects.filter(course=course).acount()
return create_standardized_response(
data={'favorited': False, 'favorites_count': count}
)
count = CourseFavorite.objects.filter(course=course).count()
count = await CourseFavorite.objects.filter(course=course).acount()
return create_standardized_response(
data={'favorited': True, 'favorites_count': count}
)
@@ -568,8 +600,8 @@ class ChapterMarkCompletedView(APIView):
404: not_found_response,
},
)
def post(self, request, pk):
chapter = Chapter.objects.filter(pk=pk).select_related('course').first()
async def post(self, request, pk):
chapter = await Chapter.objects.filter(pk=pk).select_related('course').afirst()
if not chapter:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
@@ -580,7 +612,7 @@ class ChapterMarkCompletedView(APIView):
if not isinstance(completed, bool):
completed = str(completed).lower() in ('true', '1', 'yes')
read_record, created = ChapterRead.objects.get_or_create(
read_record, created = await ChapterRead.objects.aget_or_create(
user=request.user,
chapter=chapter,
defaults={
@@ -592,12 +624,12 @@ class ChapterMarkCompletedView(APIView):
if not created:
read_record.completed = completed
read_record.completed_at = timezone.now() if completed else None
read_record.save(update_fields=['completed', 'completed_at', 'updated_at'])
await read_record.asave(update_fields=['completed', 'completed_at', 'updated_at'])
total_chapters = chapter.course.chapters.count()
completed_chapters = ChapterRead.objects.filter(
total_chapters = await chapter.course.chapters.acount()
completed_chapters = await ChapterRead.objects.filter(
user=request.user, course=chapter.course, completed=True
).count()
).acount()
progress = round((completed_chapters / total_chapters) * 100) if total_chapters > 0 else 0
return create_standardized_response(
@@ -627,20 +659,20 @@ class CourseProgressView(APIView):
404: not_found_response,
},
)
def get(self, request, course_id):
course = Course.objects.filter(pk=course_id, status='published').first()
async def get(self, request, course_id):
course = await Course.objects.filter(pk=course_id, status='published').afirst()
if not course:
return create_standardized_error_response(
code=ResponseCode.VALIDATION_ERROR,
message='课程不存在',
status_code=status.HTTP_404_NOT_FOUND
)
completed_ids = set(
ChapterRead.objects.filter(
completed_ids = set([
v async for v in ChapterRead.objects.filter(
user=request.user, course=course, completed=True
).values_list('chapter_id', flat=True)
)
total_chapters = course.chapters.count()
])
total_chapters = await course.chapters.acount()
completed_count = len(completed_ids)
progress = round((completed_count / total_chapters) * 100) if total_chapters > 0 else 0
@@ -667,7 +699,7 @@ class MaterialListView(APIView):
],
responses={200: success_response},
)
def get(self, request):
async def get(self, request):
chapter_id = request.query_params.get('chapter_id')
if not chapter_id:
return create_standardized_error_response(
@@ -677,7 +709,8 @@ class MaterialListView(APIView):
)
materials = CourseMaterial.objects.filter(chapter_id=chapter_id)
serializer = MaterialSerializer(materials, many=True, context={'request': request})
return create_standardized_response(data=serializer.data, code=ResponseCode.SUCCESS)
data = await get_data(serializer)
return create_standardized_response(data=data, code=ResponseCode.SUCCESS)
class MaterialDownloadView(APIView):
@@ -692,12 +725,12 @@ class MaterialDownloadView(APIView):
],
responses={200: openapi.Response(description='文件流'), 404: not_found_response},
)
def get(self, request, pk):
material = CourseMaterial.objects.filter(pk=pk).first()
async def get(self, request, pk):
material = await CourseMaterial.objects.filter(pk=pk).afirst()
if not material or not material.file:
raise Http404
material.download_count += 1
material.save(update_fields=['download_count'])
await material.asave(update_fields=['download_count'])
response = FileResponse(material.file.open('rb'), content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment; filename="{material.title}"'
return response
@@ -715,8 +748,8 @@ class ChapterVideoDownloadView(APIView):
],
responses={200: openapi.Response(description='文件流'), 404: not_found_response},
)
def get(self, request, pk):
chapter = Chapter.objects.filter(pk=pk).first()
async def get(self, request, pk):
chapter = await Chapter.objects.filter(pk=pk).afirst()
if not chapter or not chapter.video_local:
raise Http404
response = FileResponse(chapter.video_local.open('rb'), content_type='application/octet-stream')
@@ -733,11 +766,11 @@ class MyProgressView(APIView):
operation_description='获取当前用户所有课程的学习进度列表,需要登录',
responses={200: success_response, 401: unauthorized_response},
)
def get(self, request):
async def get(self, request):
user = request.user
reads = ChapterRead.objects.filter(user=user, completed=True).select_related('course', 'chapter')
course_map = {}
for read in reads:
async for read in reads:
course_id = read.course.id
if course_id not in course_map:
course_map[course_id] = {
@@ -745,7 +778,7 @@ class MyProgressView(APIView):
'title': read.course.title,
'cover_image': read.course.cover_image.url if read.course.cover_image else '',
'completed_chapters': 0,
'total_chapters': read.course.chapters.count(),
'total_chapters': await read.course.chapters.acount(),
'last_studied_at': read.completed_at,
}
course_map[course_id]['completed_chapters'] += 1
@@ -772,4 +805,4 @@ class MyProgressView(APIView):
'total_courses': len(courses),
'total_completed': total_completed,
}
)
)