181 lines
5.9 KiB
Python
181 lines
5.9 KiB
Python
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)
|