Files
training-software/cms/views.py
Paperclip CTO b087a63b56
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
feat(TRA-237): CMS content blocks, media upload pipeline, and course theme
- MediaAsset model with file metadata and AV scan status tracking
- ContentBlock model (richtext/image/video/embed/download) with ordered
  blocks per page and unique constraint on (page, order)
- CourseTheme one-to-one per course with primary/secondary color and logo
- validate_upload/save_upload helpers with extension and size enforcement
- scan_media_asset_task Celery stub (marks clean; replace with ClamAV)
- REST API: media upload, page content blocks CRUD, block patch/delete
- Admin registrations for all three models
- Factory-boy factories and pytest test suite for views, upload, and task

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:28:54 +02:00

65 lines
2.4 KiB
Python

import uuid
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import ContentBlock, MediaAsset
from .serializers import ContentBlockSerializer, MediaAssetSerializer
from .upload import UploadValidationError, save_upload, validate_upload
class MediaUploadView(APIView):
permission_classes = [IsAuthenticated]
parser_classes = [MultiPartParser]
def post(self, request):
file = request.FILES.get("file")
if not file:
return Response({"detail": "No file provided."}, status=status.HTTP_400_BAD_REQUEST)
org_id = request.data.get("org_id") or str(uuid.uuid4())
try:
validate_upload(file)
except UploadValidationError as exc:
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
asset = save_upload(file, org_id=org_id, uploader=request.user)
return Response(MediaAssetSerializer(asset).data, status=status.HTTP_201_CREATED)
class PageContentBlocksView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, page_id):
blocks = ContentBlock.objects.filter(page_id=page_id).select_related("media_asset")
return Response(ContentBlockSerializer(blocks, many=True).data)
def post(self, request, page_id):
from courses.models import Page
get_object_or_404(Page, pk=page_id)
data = {**request.data, "page": page_id}
serializer = ContentBlockSerializer(data=data)
serializer.is_valid(raise_exception=True)
block = serializer.save(page_id=page_id)
return Response(ContentBlockSerializer(block).data, status=status.HTTP_201_CREATED)
class ContentBlockDetailView(APIView):
permission_classes = [IsAuthenticated]
def patch(self, request, block_id):
block = get_object_or_404(ContentBlock, pk=block_id)
serializer = ContentBlockSerializer(block, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(ContentBlockSerializer(block).data)
def delete(self, request, block_id):
block = get_object_or_404(ContentBlock, pk=block_id)
block.delete()
return Response(status=status.HTTP_204_NO_CONTENT)