@@ -0,0 +1,181 @@
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .models import ContentInteraction, ContentItem, VideoProgress
|
||||
from .services import complete_video
|
||||
|
||||
|
||||
ABILITY_META = {
|
||||
ContentItem.Ability.VISION: {"label": "数学眼光", "icon": "◉", "color": "#5d73e8"},
|
||||
ContentItem.Ability.HUMANITIES: {"label": "数学人文", "icon": "▤", "color": "#b76d38"},
|
||||
ContentItem.Ability.DETECTION: {"label": "数学侦探", "icon": "⌕", "color": "#287f78"},
|
||||
ContentItem.Ability.MODELING: {"label": "数学建模", "icon": "⌁", "color": "#d98628"},
|
||||
ContentItem.Ability.CONNECTION: {"label": "数学联结", "icon": "⛓", "color": "#8667b5"},
|
||||
}
|
||||
|
||||
|
||||
def serialize_item(item, progress=None):
|
||||
return {
|
||||
"slug": item.slug,
|
||||
"title": item.title,
|
||||
"kind": item.kind,
|
||||
"summary": item.summary,
|
||||
"body": item.body,
|
||||
"cover_url": item.cover_url,
|
||||
"media_url": item.media_url,
|
||||
"topics": item.topics,
|
||||
"source": item.source,
|
||||
"ability_dimension": item.ability_dimension,
|
||||
"ability": ABILITY_META.get(item.ability_dimension),
|
||||
"module": item.module,
|
||||
"sub_category": item.sub_category,
|
||||
"discipline": item.discipline,
|
||||
"discipline_icon": item.discipline_icon,
|
||||
"author": item.author,
|
||||
"duration_seconds": item.duration_seconds,
|
||||
"view_count": item.view_count,
|
||||
"comment_count": item.comment_count,
|
||||
"published_at": item.published_at,
|
||||
"progress": (
|
||||
{
|
||||
"position_seconds": progress.position_seconds,
|
||||
"completed": progress.completed,
|
||||
"reward_granted": progress.reward_granted,
|
||||
}
|
||||
if progress
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class ContentListView(APIView):
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get(self, request):
|
||||
queryset = ContentItem.objects.filter(is_published=True)
|
||||
kind = request.query_params.get("kind")
|
||||
ability = request.query_params.get("ability")
|
||||
discipline = request.query_params.get("discipline")
|
||||
if kind:
|
||||
queryset = queryset.filter(kind=kind)
|
||||
if ability:
|
||||
queryset = queryset.filter(ability_dimension=ability)
|
||||
if discipline:
|
||||
queryset = queryset.filter(discipline=discipline)
|
||||
|
||||
progress_by_content = {}
|
||||
if request.user.is_authenticated:
|
||||
progress_by_content = {
|
||||
item.content_id: item
|
||||
for item in VideoProgress.objects.filter(
|
||||
user=request.user,
|
||||
content__in=queryset,
|
||||
)
|
||||
}
|
||||
return Response(
|
||||
[
|
||||
serialize_item(item, progress_by_content.get(item.id))
|
||||
for item in queryset[:100]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class VideoCatalogView(APIView):
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get(self, request):
|
||||
videos = ContentItem.objects.filter(
|
||||
kind=ContentItem.Kind.VIDEO,
|
||||
is_published=True,
|
||||
)
|
||||
counts = {
|
||||
row["ability_dimension"]: row["count"]
|
||||
for row in videos.values("ability_dimension").annotate(count=Count("id"))
|
||||
}
|
||||
abilities = [
|
||||
{
|
||||
"id": ability,
|
||||
**meta,
|
||||
"count": counts.get(ability, 0),
|
||||
}
|
||||
for ability, meta in ABILITY_META.items()
|
||||
]
|
||||
disciplines = [
|
||||
{
|
||||
"name": row["discipline"],
|
||||
"icon": row["discipline_icon"],
|
||||
"count": row["count"],
|
||||
}
|
||||
for row in videos.values("discipline", "discipline_icon")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("discipline")
|
||||
]
|
||||
return Response(
|
||||
{
|
||||
"total": videos.count(),
|
||||
"abilities": abilities,
|
||||
"disciplines": disciplines,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ContentDetailView(APIView):
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get(self, request, slug):
|
||||
item = get_object_or_404(ContentItem, slug=slug, is_published=True)
|
||||
progress = None
|
||||
if request.user.is_authenticated:
|
||||
ContentInteraction.objects.get_or_create(
|
||||
user=request.user,
|
||||
content=item,
|
||||
action=ContentInteraction.Action.VIEW,
|
||||
)
|
||||
progress, _ = VideoProgress.objects.get_or_create(
|
||||
user=request.user,
|
||||
content=item,
|
||||
)
|
||||
return Response(serialize_item(item, progress))
|
||||
|
||||
|
||||
class VideoCompleteView(APIView):
|
||||
def post(self, request, slug):
|
||||
item = get_object_or_404(
|
||||
ContentItem,
|
||||
slug=slug,
|
||||
kind=ContentItem.Kind.VIDEO,
|
||||
is_published=True,
|
||||
)
|
||||
progress, reward = complete_video(request.user, item)
|
||||
return Response(
|
||||
{
|
||||
"completed": progress.completed,
|
||||
"reward_granted": progress.reward_granted,
|
||||
"reward": reward,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class FavoriteView(APIView):
|
||||
def post(self, request, slug):
|
||||
item = get_object_or_404(ContentItem, slug=slug, is_published=True)
|
||||
_, created = ContentInteraction.objects.get_or_create(
|
||||
user=request.user,
|
||||
content=item,
|
||||
action=ContentInteraction.Action.FAVORITE,
|
||||
)
|
||||
return Response(
|
||||
{"favorite": True},
|
||||
status=status.HTTP_201_CREATED if created else status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def delete(self, request, slug):
|
||||
ContentInteraction.objects.filter(
|
||||
user=request.user,
|
||||
content__slug=slug,
|
||||
action=ContentInteraction.Action.FAVORITE,
|
||||
).delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
Reference in New Issue
Block a user