123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407 |
- from datetime import datetime, timedelta
- from celery.task.control import revoke
- from django.conf import settings
- from django.contrib import messages
- from django.contrib.auth.decorators import login_required
- from django.contrib.postgres.search import SearchQuery
- from django.core.mail import EmailMessage
- from django.db.models import Q
- from django.http import HttpResponseRedirect
- from django.shortcuts import get_object_or_404, render
- from django.template.defaultfilters import slugify
- from drf_yasg import openapi as openapi
- from drf_yasg.utils import swagger_auto_schema
- from rest_framework import permissions, status
- from rest_framework.exceptions import PermissionDenied
- from rest_framework.parsers import (
- FileUploadParser,
- FormParser,
- JSONParser,
- MultiPartParser,
- )
- from rest_framework.response import Response
- from rest_framework.settings import api_settings
- from rest_framework.views import APIView
- from actions.models import USER_MEDIA_ACTIONS, MediaAction
- from cms.custom_pagination import FastPaginationWithoutCount
- from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor, user_allowed_to_upload
- from users.models import User
- from .forms import ContactForm, MediaForm, SubtitleForm
- from .helpers import clean_query, produce_ffmpeg_commands
- from .methods import (
- check_comment_for_mention,
- get_user_or_session,
- is_mediacms_editor,
- is_mediacms_manager,
- list_tasks,
- notify_user_on_comment,
- show_recommended_media,
- show_related_media,
- update_user_ratings,
- )
- from .models import (
- Category,
- Comment,
- EncodeProfile,
- Encoding,
- Media,
- Playlist,
- PlaylistMedia,
- Tag,
- )
- from .serializers import (
- CategorySerializer,
- CommentSerializer,
- EncodeProfileSerializer,
- MediaSearchSerializer,
- MediaSerializer,
- PlaylistDetailSerializer,
- PlaylistSerializer,
- SingleMediaSerializer,
- TagSerializer,
- )
- from .stop_words import STOP_WORDS
- from .tasks import save_user_action
- VALID_USER_ACTIONS = [action for action, name in USER_MEDIA_ACTIONS]
- def about(request):
- """About view"""
- context = {}
- return render(request, "cms/about.html", context)
- @login_required
- def add_subtitle(request):
- """Add subtitle view"""
- friendly_token = request.GET.get("m", "").strip()
- if not friendly_token:
- return HttpResponseRedirect("/")
- media = Media.objects.filter(friendly_token=friendly_token).first()
- if not media:
- return HttpResponseRedirect("/")
- if not (request.user == media.user or is_mediacms_editor(request.user) or is_mediacms_manager(request.user)):
- return HttpResponseRedirect("/")
- if request.method == "POST":
- form = SubtitleForm(media, request.POST, request.FILES)
- if form.is_valid():
- subtitle = form.save()
- messages.add_message(request, messages.INFO, "Subtitle was added!")
- return HttpResponseRedirect(subtitle.media.get_absolute_url())
- else:
- form = SubtitleForm(media_item=media)
- return render(request, "cms/add_subtitle.html", {"form": form})
- def categories(request):
- """List categories view"""
- context = {}
- return render(request, "cms/categories.html", context)
- def contact(request):
- """Contact view"""
- context = {}
- if request.method == "GET":
- form = ContactForm(request.user)
- context["form"] = form
- else:
- form = ContactForm(request.user, request.POST)
- if form.is_valid():
- if request.user.is_authenticated:
- from_email = request.user.email
- name = request.user.name
- else:
- from_email = request.POST.get("from_email")
- name = request.POST.get("name")
- message = request.POST.get("message")
- title = "[{}] - Contact form message received".format(settings.PORTAL_NAME)
- msg = """
- You have received a message through the contact form\n
- Sender name: %s
- Sender email: %s\n
- \n %s
- """ % (
- name,
- from_email,
- message,
- )
- email = EmailMessage(
- title,
- msg,
- settings.DEFAULT_FROM_EMAIL,
- settings.ADMIN_EMAIL_LIST,
- reply_to=[from_email],
- )
- email.send(fail_silently=True)
- success_msg = "Message was sent! Thanks for contacting"
- context["success_msg"] = success_msg
- return render(request, "cms/contact.html", context)
- def history(request):
- """Show personal history view"""
- context = {}
- return render(request, "cms/history.html", context)
- @login_required
- def edit_media(request):
- """Edit a media view"""
- friendly_token = request.GET.get("m", "").strip()
- if not friendly_token:
- return HttpResponseRedirect("/")
- media = Media.objects.filter(friendly_token=friendly_token).first()
- if not media:
- return HttpResponseRedirect("/")
- if not (request.user == media.user or is_mediacms_editor(request.user) or is_mediacms_manager(request.user)):
- return HttpResponseRedirect("/")
- if request.method == "POST":
- form = MediaForm(request.user, request.POST, request.FILES, instance=media)
- if form.is_valid():
- media = form.save()
- for tag in media.tags.all():
- media.tags.remove(tag)
- if form.cleaned_data.get("new_tags"):
- for tag in form.cleaned_data.get("new_tags").split(","):
- tag = slugify(tag)
- if tag:
- try:
- tag = Tag.objects.get(title=tag)
- except Tag.DoesNotExist:
- tag = Tag.objects.create(title=tag, user=request.user)
- if tag not in media.tags.all():
- media.tags.add(tag)
- messages.add_message(request, messages.INFO, "Media was edited!")
- return HttpResponseRedirect(media.get_absolute_url())
- else:
- form = MediaForm(request.user, instance=media)
- return render(
- request,
- "cms/edit_media.html",
- {"form": form, "add_subtitle_url": media.add_subtitle_url},
- )
- def embed_media(request):
- """Embed media view"""
- friendly_token = request.GET.get("m", "").strip()
- if not friendly_token:
- return HttpResponseRedirect("/")
- media = Media.objects.values("title").filter(friendly_token=friendly_token).first()
- if not media:
- return HttpResponseRedirect("/")
- context = {}
- context["media"] = friendly_token
- return render(request, "cms/embed.html", context)
- def featured_media(request):
- """List featured media view"""
- context = {}
- context["media"] = list(Media.objects.filter(Q(listable=True), featured=True))[:50]
- return render(request, "cms/featured-media.html", context)
- def index(request):
- """Index view"""
- context = {}
- context["media_featured"] = list(Media.objects.filter(Q(listable=True), featured=True))[:10]
- context["media_recommended"] = list(show_recommended_media(request, limit=10))
- context["media_latest"] = list(Media.objects.filter(Q(listable=True)).order_by("-add_date"))[:50]
- return render(request, "cms/index.html", context)
- def latest_media(request):
- """List latest media view"""
- context = {}
- context["media"] = list(Media.objects.filter(Q(listable=True)).order_by("-add_date"))[:50]
- return render(request, "cms/latest-media.html", context)
- def liked_media(request):
- """List user's liked media view"""
- context = {}
- return render(request, "cms/liked_media.html", context)
- @login_required
- def manage_users(request):
- """List users management view"""
- context = {}
- return render(request, "cms/manage_users.html", context)
- @login_required
- def manage_media(request):
- """List media management view"""
- context = {}
- return render(request, "cms/manage_media.html", context)
- @login_required
- def manage_comments(request):
- """List comments management view"""
- context = {}
- return render(request, "cms/manage_comments.html", context)
- def members(request):
- """List members view"""
- context = {}
- return render(request, "cms/members.html", context)
- def recommended_media(request):
- """List recommended media view"""
- context = {}
- context["media"] = list(show_recommended_media(request, limit=50))
- return render(request, "cms/recommended-media.html", context)
- def search(request):
- """Search view"""
- context = {}
- RSS_URL = f"/rss{request.environ['REQUEST_URI']}"
- context["RSS_URL"] = RSS_URL
- return render(request, "cms/search.html", context)
- def tags(request):
- """List tags view"""
- context = {}
- return render(request, "cms/tags.html", context)
- def tos(request):
- """Terms of service view"""
- context = {}
- return render(request, "cms/tos.html", context)
- def upload_media(request):
- """Upload media view"""
- from allauth.account.forms import LoginForm
- form = LoginForm()
- context = {}
- context["form"] = form
- context["can_add"] = user_allowed_to_upload(request)
- can_upload_exp = settings.CANNOT_ADD_MEDIA_MESSAGE
- context["can_upload_exp"] = can_upload_exp
- return render(request, "cms/add-media.html", context)
- def view_media(request):
- """View media view"""
- friendly_token = request.GET.get("m", "").strip()
- context = {}
- media = Media.objects.filter(friendly_token=friendly_token).first()
- if not media:
- context["media"] = None
- return render(request, "cms/media.html", context)
- user_or_session = get_user_or_session(request)
- save_user_action.delay(user_or_session, friendly_token=friendly_token, action="watch")
- context = {}
- context["media"] = friendly_token
- context["media_object"] = media
- context["CAN_DELETE_MEDIA"] = False
- context["CAN_EDIT_MEDIA"] = False
- context["CAN_DELETE_COMMENTS"] = False
- if request.user.is_authenticated:
- if (media.user.id == request.user.id) or is_mediacms_editor(request.user) or is_mediacms_manager(request.user):
- context["CAN_DELETE_MEDIA"] = True
- context["CAN_EDIT_MEDIA"] = True
- context["CAN_DELETE_COMMENTS"] = True
- return render(request, "cms/media.html", context)
- def view_playlist(request, friendly_token):
- """View playlist view"""
- try:
- playlist = Playlist.objects.get(friendly_token=friendly_token)
- except BaseException:
- playlist = None
- context = {}
- context["playlist"] = playlist
- context["media"] = [c.media for c in PlaylistMedia.objects.filter(playlist=playlist)]
- return render(request, "cms/playlist.html", context)
- class MediaList(APIView):
- """Media listings views"""
- permission_classes = (IsAuthorizedToAdd,)
- parser_classes = (MultiPartParser, FormParser, FileUploadParser)
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
- openapi.Parameter(name='author', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='username'),
- openapi.Parameter(name='show', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='show', enum=['recommended', 'featured', 'latest']),
- ],
- tags=['Media'],
- operation_summary='List Media',
- operation_description='Lists all media',
- responses={200: MediaSerializer(many=True)},
- )
- def get(self, request, format=None):
- # Show media
- params = self.request.query_params
- show_param = params.get("show", "")
- author_param = params.get("author", "").strip()
- if author_param:
- user_queryset = User.objects.all()
- user = get_object_or_404(user_queryset, username=author_param)
- if show_param == "recommended":
- pagination_class = FastPaginationWithoutCount
- media = show_recommended_media(request, limit=50)
- else:
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- if author_param:
- # in case request.user is the user here, show
- # all media independant of state
- if self.request.user == user:
- basic_query = Q(user=user)
- else:
- basic_query = Q(listable=True, user=user)
- else:
- # base listings should show safe content
- basic_query = Q(listable=True)
- if show_param == "featured":
- media = Media.objects.filter(basic_query, featured=True)
- else:
- media = Media.objects.filter(basic_query).order_by("-add_date")
- paginator = pagination_class()
- if show_param != "recommended":
- media = media.prefetch_related("user")
- page = paginator.paginate_queryset(media, request)
- serializer = MediaSerializer(page, many=True, context={"request": request})
- return paginator.get_paginated_response(serializer.data)
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name="media_file", in_=openapi.IN_FORM, type=openapi.TYPE_FILE, required=True, description="media_file"),
- openapi.Parameter(name="description", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="description"),
- openapi.Parameter(name="title", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="title"),
- ],
- tags=['Media'],
- operation_summary='Add new Media',
- operation_description='Adds a new media, for authenticated users',
- responses={201: openapi.Response('response description', MediaSerializer), 401: 'bad request'},
- )
- def post(self, request, format=None):
- # Add new media
- serializer = MediaSerializer(data=request.data, context={"request": request})
- if serializer.is_valid():
- media_file = request.data["media_file"]
- serializer.save(user=request.user, media_file=media_file)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- class MediaDetail(APIView):
- """
- Retrieve, update or delete a media instance.
- """
- permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor)
- parser_classes = (MultiPartParser, FormParser, FileUploadParser)
- def get_object(self, friendly_token, password=None):
- try:
- media = Media.objects.select_related("user").prefetch_related("encodings__profile").get(friendly_token=friendly_token)
- # this need be explicitly called, and will call
- # has_object_permission() after has_permission has succeeded
- self.check_object_permissions(self.request, media)
- if media.state == "private" and not (self.request.user == media.user or is_mediacms_editor(self.request.user)):
- if (not password) or (not media.password) or (password != media.password):
- return Response(
- {"detail": "media is private"},
- status=status.HTTP_401_UNAUTHORIZED,
- )
- return media
- except PermissionDenied:
- return Response({"detail": "bad permissions"}, status=status.HTTP_401_UNAUTHORIZED)
- except BaseException:
- return Response(
- {"detail": "media file does not exist"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name='friendly_token', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='unique identifier', required=True),
- ],
- tags=['Media'],
- operation_summary='Get information for Media',
- operation_description='Get information for a media',
- responses={200: SingleMediaSerializer(), 400: 'bad request'},
- )
- def get(self, request, friendly_token, format=None):
- # Get media details
- password = request.GET.get("password")
- media = self.get_object(friendly_token, password=password)
- if isinstance(media, Response):
- return media
- serializer = SingleMediaSerializer(media, context={"request": request})
- if media.state == "private":
- related_media = []
- else:
- related_media = show_related_media(media, request=request, limit=100)
- related_media_serializer = MediaSerializer(related_media, many=True, context={"request": request})
- related_media = related_media_serializer.data
- ret = serializer.data
- # update rattings info with user specific ratings
- # eg user has already rated for this media
- # this only affects user rating and only if enabled
- if settings.ALLOW_RATINGS and ret.get("ratings_info") and not request.user.is_anonymous:
- ret["ratings_info"] = update_user_ratings(request.user, media, ret.get("ratings_info"))
- ret["related_media"] = related_media
- return Response(ret)
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name='friendly_token', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='unique identifier', required=True),
- openapi.Parameter(name='type', type=openapi.TYPE_STRING, in_=openapi.IN_FORM, description='action to perform', enum=['encode', 'review']),
- openapi.Parameter(
- name='encoding_profiles',
- type=openapi.TYPE_ARRAY,
- items=openapi.Items(type=openapi.TYPE_STRING),
- in_=openapi.IN_FORM,
- description='if action to perform is encode, need to specify list of ids of encoding profiles',
- ),
- openapi.Parameter(name='result', type=openapi.TYPE_BOOLEAN, in_=openapi.IN_FORM, description='if action is review, this is the result (True for reviewed, False for not reviewed)'),
- ],
- tags=['Media'],
- operation_summary='Run action on Media',
- operation_description='Actions for a media, for MediaCMS editors and managers',
- responses={201: 'action created', 400: 'bad request'},
- operation_id='media_manager_actions',
- )
- def post(self, request, friendly_token, format=None):
- """superuser actions
- Available only to MediaCMS editors and managers
- Action is a POST variable, review and encode are implemented
- """
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- if not (is_mediacms_editor(request.user) or is_mediacms_manager(request.user)):
- return Response({"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST)
- action = request.data.get("type")
- profiles_list = request.data.get("encoding_profiles")
- result = request.data.get("result", True)
- if action == "encode":
- # Create encoding tasks for specific profiles
- valid_profiles = []
- if profiles_list:
- if isinstance(profiles_list, list):
- for p in profiles_list:
- p = EncodeProfile.objects.filter(id=p).first()
- if p:
- valid_profiles.append(p)
- elif isinstance(profiles_list, str):
- try:
- p = EncodeProfile.objects.filter(id=int(profiles_list)).first()
- valid_profiles.append(p)
- except ValueError:
- return Response(
- {"detail": "encoding_profiles must be int or list of ints of valid encode profiles"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- media.encode(profiles=valid_profiles)
- return Response({"detail": "media will be encoded"}, status=status.HTTP_201_CREATED)
- elif action == "review":
- if result:
- media.is_reviewed = True
- elif result is False:
- media.is_reviewed = False
- media.save(update_fields=["is_reviewed"])
- return Response({"detail": "media reviewed set"}, status=status.HTTP_201_CREATED)
- return Response(
- {"detail": "not valid action or no action specified"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name="description", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="description"),
- openapi.Parameter(name="title", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="title"),
- openapi.Parameter(name="media_file", in_=openapi.IN_FORM, type=openapi.TYPE_FILE, required=False, description="media_file"),
- ],
- tags=['Media'],
- operation_summary='Update Media',
- operation_description='Update a Media, for Media uploader',
- responses={201: openapi.Response('response description', MediaSerializer), 401: 'bad request'},
- )
- def put(self, request, friendly_token, format=None):
- # Update a media object
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- serializer = MediaSerializer(media, data=request.data, context={"request": request})
- if serializer.is_valid():
- if request.data.get('media_file'):
- media_file = request.data["media_file"]
- serializer.save(user=request.user, media_file=media_file)
- else:
- serializer.save(user=request.user)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name='friendly_token', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='unique identifier', required=True),
- ],
- tags=['Media'],
- operation_summary='Delete Media',
- operation_description='Delete a Media, for MediaCMS editors and managers',
- responses={
- 204: 'no content',
- },
- )
- def delete(self, request, friendly_token, format=None):
- # Delete a media object
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- media.delete()
- return Response(status=status.HTTP_204_NO_CONTENT)
- class MediaActions(APIView):
- """
- Retrieve, update or delete a media action instance.
- """
- permission_classes = (permissions.AllowAny,)
- parser_classes = (JSONParser,)
- def get_object(self, friendly_token):
- try:
- media = Media.objects.select_related("user").prefetch_related("encodings__profile").get(friendly_token=friendly_token)
- if media.state == "private" and self.request.user != media.user:
- return Response({"detail": "media is private"}, status=status.HTTP_400_BAD_REQUEST)
- return media
- except PermissionDenied:
- return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
- except BaseException:
- return Response(
- {"detail": "media file does not exist"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Media'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def get(self, request, friendly_token, format=None):
- # show date and reason for each time media was reported
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- ret = {}
- reported = MediaAction.objects.filter(media=media, action="report")
- ret["reported"] = []
- for rep in reported:
- item = {"reported_date": rep.action_date, "reason": rep.extra_info}
- ret["reported"].append(item)
- return Response(ret, status=status.HTTP_200_OK)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Media'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def post(self, request, friendly_token, format=None):
- # perform like/dislike/report actions
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- action = request.data.get("type")
- extra = request.data.get("extra_info")
- if request.user.is_anonymous:
- # there is a list of allowed actions for
- # anonymous users, specified in settings
- if action not in settings.ALLOW_ANONYMOUS_ACTIONS:
- return Response(
- {"detail": "action allowed on logged in users only"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- if action:
- user_or_session = get_user_or_session(request)
- save_user_action.delay(
- user_or_session,
- friendly_token=media.friendly_token,
- action=action,
- extra_info=extra,
- )
- return Response({"detail": "action received"}, status=status.HTTP_201_CREATED)
- else:
- return Response({"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Media'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def delete(self, request, friendly_token, format=None):
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- if not request.user.is_superuser:
- return Response({"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST)
- action = request.data.get("type")
- if action:
- if action == "report": # delete reported actions
- MediaAction.objects.filter(media=media, action="report").delete()
- media.reported_times = 0
- media.save(update_fields=["reported_times"])
- return Response(
- {"detail": "reset reported times counter"},
- status=status.HTTP_201_CREATED,
- )
- else:
- return Response({"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST)
- class MediaSearch(APIView):
- """
- Retrieve results for searc
- Only GET is implemented here
- """
- parser_classes = (JSONParser,)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Search'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def get(self, request, format=None):
- params = self.request.query_params
- query = params.get("q", "").strip().lower()
- category = params.get("c", "").strip()
- tag = params.get("t", "").strip()
- ordering = params.get("ordering", "").strip()
- sort_by = params.get("sort_by", "").strip()
- media_type = params.get("media_type", "").strip()
- author = params.get("author", "").strip()
- upload_date = params.get('upload_date', '').strip()
- sort_by_options = ["title", "add_date", "edit_date", "views", "likes"]
- if sort_by not in sort_by_options:
- sort_by = "add_date"
- if ordering == "asc":
- ordering = ""
- else:
- ordering = "-"
- if media_type not in ["video", "image", "audio", "pdf"]:
- media_type = None
- if not (query or category or tag):
- ret = {}
- return Response(ret, status=status.HTTP_200_OK)
- media = Media.objects.filter(state="public", is_reviewed=True)
- if query:
- # move this processing to a prepare_query function
- query = clean_query(query)
- q_parts = [q_part.rstrip("y") for q_part in query.split() if q_part not in STOP_WORDS]
- if q_parts:
- query = SearchQuery(q_parts[0] + ":*", search_type="raw")
- for part in q_parts[1:]:
- query &= SearchQuery(part + ":*", search_type="raw")
- else:
- query = None
- if query:
- media = media.filter(search=query)
- if tag:
- media = media.filter(tags__title=tag)
- if category:
- media = media.filter(category__title__contains=category)
- if media_type:
- media = media.filter(media_type=media_type)
- if author:
- media = media.filter(user__username=author)
- if upload_date:
- gte = None
- if upload_date == 'today':
- gte = datetime.now().date()
- if upload_date == 'this_week':
- gte = datetime.now() - timedelta(days=7)
- if upload_date == 'this_month':
- year = datetime.now().date().year
- month = datetime.now().date().month
- gte = datetime(year, month, 1)
- if upload_date == 'this_year':
- year = datetime.now().date().year
- gte = datetime(year, 1, 1)
- if gte:
- media = media.filter(add_date__gte=gte)
- media = media.order_by(f"{ordering}{sort_by}")
- if self.request.query_params.get("show", "").strip() == "titles":
- media = media.values("title")[:40]
- return Response(media, status=status.HTTP_200_OK)
- else:
- media = media.prefetch_related("user")
- if category or tag:
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- else:
- # pagination_class = FastPaginationWithoutCount
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- paginator = pagination_class()
- page = paginator.paginate_queryset(media, request)
- serializer = MediaSearchSerializer(page, many=True, context={"request": request})
- return paginator.get_paginated_response(serializer.data)
- class PlaylistList(APIView):
- """Playlists listings and creation views"""
- permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd)
- parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Playlists'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- responses={
- 200: openapi.Response('response description', PlaylistSerializer(many=True)),
- },
- )
- def get(self, request, format=None):
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- paginator = pagination_class()
- playlists = Playlist.objects.filter().prefetch_related("user")
- if "author" in self.request.query_params:
- author = self.request.query_params["author"].strip()
- playlists = playlists.filter(user__username=author)
- page = paginator.paginate_queryset(playlists, request)
- serializer = PlaylistSerializer(page, many=True, context={"request": request})
- return paginator.get_paginated_response(serializer.data)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Playlists'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def post(self, request, format=None):
- serializer = PlaylistSerializer(data=request.data, context={"request": request})
- if serializer.is_valid():
- serializer.save(user=request.user)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- class PlaylistDetail(APIView):
- """Playlist related views"""
- permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor)
- parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
- def get_playlist(self, friendly_token):
- try:
- playlist = Playlist.objects.get(friendly_token=friendly_token)
- self.check_object_permissions(self.request, playlist)
- return playlist
- except PermissionDenied:
- return Response({"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST)
- except BaseException:
- return Response(
- {"detail": "Playlist does not exist"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Playlists'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def get(self, request, friendly_token, format=None):
- playlist = self.get_playlist(friendly_token)
- if isinstance(playlist, Response):
- return playlist
- serializer = PlaylistDetailSerializer(playlist, context={"request": request})
- playlist_media = PlaylistMedia.objects.filter(playlist=playlist).prefetch_related("media__user")
- playlist_media = [c.media for c in playlist_media]
- playlist_media_serializer = MediaSerializer(playlist_media, many=True, context={"request": request})
- ret = serializer.data
- ret["playlist_media"] = playlist_media_serializer.data
- return Response(ret)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Playlists'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def post(self, request, friendly_token, format=None):
- playlist = self.get_playlist(friendly_token)
- if isinstance(playlist, Response):
- return playlist
- serializer = PlaylistDetailSerializer(playlist, data=request.data, context={"request": request})
- if serializer.is_valid():
- serializer.save(user=request.user)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Playlists'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def put(self, request, friendly_token, format=None):
- playlist = self.get_playlist(friendly_token)
- if isinstance(playlist, Response):
- return playlist
- action = request.data.get("type")
- media_friendly_token = request.data.get("media_friendly_token")
- ordering = 0
- if request.data.get("ordering"):
- try:
- ordering = int(request.data.get("ordering"))
- except ValueError:
- pass
- if action in ["add", "remove", "ordering"]:
- media = Media.objects.filter(friendly_token=media_friendly_token).first()
- if media:
- if action == "add":
- media_in_playlist = PlaylistMedia.objects.filter(playlist=playlist).count()
- if media_in_playlist >= settings.MAX_MEDIA_PER_PLAYLIST:
- return Response(
- {"detail": "max number of media for a Playlist reached"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- else:
- obj, created = PlaylistMedia.objects.get_or_create(
- playlist=playlist,
- media=media,
- ordering=media_in_playlist + 1,
- )
- obj.save()
- return Response(
- {"detail": "media added to Playlist"},
- status=status.HTTP_201_CREATED,
- )
- elif action == "remove":
- PlaylistMedia.objects.filter(playlist=playlist, media=media).delete()
- return Response(
- {"detail": "media removed from Playlist"},
- status=status.HTTP_201_CREATED,
- )
- elif action == "ordering":
- if ordering:
- playlist.set_ordering(media, ordering)
- return Response(
- {"detail": "new ordering set"},
- status=status.HTTP_201_CREATED,
- )
- else:
- return Response({"detail": "media is not valid"}, status=status.HTTP_400_BAD_REQUEST)
- return Response(
- {"detail": "invalid or not specified action"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Playlists'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def delete(self, request, friendly_token, format=None):
- playlist = self.get_playlist(friendly_token)
- if isinstance(playlist, Response):
- return playlist
- playlist.delete()
- return Response(status=status.HTTP_204_NO_CONTENT)
- class EncodingDetail(APIView):
- """Experimental. This View is used by remote workers
- Needs heavy testing and documentation.
- """
- permission_classes = (permissions.IsAdminUser,)
- parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
- @swagger_auto_schema(auto_schema=None)
- def post(self, request, encoding_id):
- ret = {}
- force = request.data.get("force", False)
- task_id = request.data.get("task_id", False)
- action = request.data.get("action", "")
- chunk = request.data.get("chunk", False)
- chunk_file_path = request.data.get("chunk_file_path", "")
- encoding_status = request.data.get("status", "")
- progress = request.data.get("progress", "")
- commands = request.data.get("commands", "")
- logs = request.data.get("logs", "")
- retries = request.data.get("retries", "")
- worker = request.data.get("worker", "")
- temp_file = request.data.get("temp_file", "")
- total_run_time = request.data.get("total_run_time", "")
- if action == "start":
- try:
- encoding = Encoding.objects.get(id=encoding_id)
- media = encoding.media
- profile = encoding.profile
- except BaseException:
- Encoding.objects.filter(id=encoding_id).delete()
- return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
- # TODO: break chunk True/False logic here
- if (
- Encoding.objects.filter(
- media=media,
- profile=profile,
- chunk=chunk,
- chunk_file_path=chunk_file_path,
- ).count()
- > 1 # noqa
- and force is False # noqa
- ):
- Encoding.objects.filter(id=encoding_id).delete()
- return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
- else:
- Encoding.objects.filter(
- media=media,
- profile=profile,
- chunk=chunk,
- chunk_file_path=chunk_file_path,
- ).exclude(id=encoding.id).delete()
- encoding.status = "running"
- if task_id:
- encoding.task_id = task_id
- encoding.save()
- if chunk:
- original_media_path = chunk_file_path
- original_media_md5sum = encoding.md5sum
- original_media_url = settings.SSL_FRONTEND_HOST + encoding.media_chunk_url
- else:
- original_media_path = media.media_file.path
- original_media_md5sum = media.md5sum
- original_media_url = settings.SSL_FRONTEND_HOST + media.original_media_url
- ret["original_media_url"] = original_media_url
- ret["original_media_path"] = original_media_path
- ret["original_media_md5sum"] = original_media_md5sum
- # generating the commands here, and will replace these with temporary
- # files created on the remote server
- tf = "TEMP_FILE_REPLACE"
- tfpass = "TEMP_FPASS_FILE_REPLACE"
- ffmpeg_commands = produce_ffmpeg_commands(
- original_media_path,
- media.media_info,
- resolution=profile.resolution,
- codec=profile.codec,
- output_filename=tf,
- pass_file=tfpass,
- chunk=chunk,
- )
- if not ffmpeg_commands:
- encoding.delete()
- return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
- ret["duration"] = media.duration
- ret["ffmpeg_commands"] = ffmpeg_commands
- ret["profile_extension"] = profile.extension
- return Response(ret, status=status.HTTP_201_CREATED)
- elif action == "update_fields":
- try:
- encoding = Encoding.objects.get(id=encoding_id)
- except BaseException:
- return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
- to_update = ["size", "update_date"]
- if encoding_status:
- encoding.status = encoding_status
- to_update.append("status")
- if progress:
- encoding.progress = progress
- to_update.append("progress")
- if logs:
- encoding.logs = logs
- to_update.append("logs")
- if commands:
- encoding.commands = commands
- to_update.append("commands")
- if task_id:
- encoding.task_id = task_id
- to_update.append("task_id")
- if total_run_time:
- encoding.total_run_time = total_run_time
- to_update.append("total_run_time")
- if worker:
- encoding.worker = worker
- to_update.append("worker")
- if temp_file:
- encoding.temp_file = temp_file
- to_update.append("temp_file")
- if retries:
- encoding.retries = retries
- to_update.append("retries")
- try:
- encoding.save(update_fields=to_update)
- except BaseException:
- return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
- return Response({"status": "success"}, status=status.HTTP_201_CREATED)
- @swagger_auto_schema(auto_schema=None)
- def put(self, request, encoding_id, format=None):
- encoding_file = request.data["file"]
- encoding = Encoding.objects.filter(id=encoding_id).first()
- if not encoding:
- return Response(
- {"detail": "encoding does not exist"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- encoding.media_file = encoding_file
- encoding.save()
- return Response({"detail": "ok"}, status=status.HTTP_201_CREATED)
- class CommentList(APIView):
- permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd)
- parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
- openapi.Parameter(name='author', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='username'),
- ],
- tags=['Comments'],
- operation_summary='Lists Comments',
- operation_description='Paginated listing of all comments',
- responses={
- 200: openapi.Response('response description', CommentSerializer(many=True)),
- },
- )
- def get(self, request, format=None):
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- paginator = pagination_class()
- comments = Comment.objects.filter()
- comments = comments.prefetch_related("user")
- comments = comments.prefetch_related("media")
- params = self.request.query_params
- if "author" in params:
- author_param = params["author"].strip()
- user_queryset = User.objects.all()
- user = get_object_or_404(user_queryset, username=author_param)
- comments = comments.filter(user=user)
- page = paginator.paginate_queryset(comments, request)
- serializer = CommentSerializer(page, many=True, context={"request": request})
- return paginator.get_paginated_response(serializer.data)
- class CommentDetail(APIView):
- """Comments related views
- Listings of comments for a media (GET)
- Create comment (POST)
- Delete comment (DELETE)
- """
- permission_classes = (IsAuthorizedToAdd,)
- parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
- def get_object(self, friendly_token):
- try:
- media = Media.objects.select_related("user").get(friendly_token=friendly_token)
- self.check_object_permissions(self.request, media)
- if media.state == "private" and self.request.user != media.user:
- return Response({"detail": "media is private"}, status=status.HTTP_400_BAD_REQUEST)
- return media
- except PermissionDenied:
- return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
- except BaseException:
- return Response(
- {"detail": "media file does not exist"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Media'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def get(self, request, friendly_token):
- # list comments for a media
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- comments = media.comments.filter().prefetch_related("user")
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- paginator = pagination_class()
- page = paginator.paginate_queryset(comments, request)
- serializer = CommentSerializer(page, many=True, context={"request": request})
- return paginator.get_paginated_response(serializer.data)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Media'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def delete(self, request, friendly_token, uid=None):
- """Delete a comment
- Administrators, MediaCMS editors and managers,
- media owner, and comment owners, can delete a comment
- """
- if uid:
- try:
- comment = Comment.objects.get(uid=uid)
- except BaseException:
- return Response(
- {"detail": "comment does not exist"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- if (comment.user == self.request.user) or comment.media.user == self.request.user or is_mediacms_editor(self.request.user):
- comment.delete()
- else:
- return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
- return Response(status=status.HTTP_204_NO_CONTENT)
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Media'],
- operation_summary='to_be_written',
- operation_description='to_be_written',
- )
- def post(self, request, friendly_token):
- """Create a comment"""
- media = self.get_object(friendly_token)
- if isinstance(media, Response):
- return media
- if not media.enable_comments:
- return Response(
- {"detail": "comments not allowed here"},
- status=status.HTTP_400_BAD_REQUEST,
- )
- serializer = CommentSerializer(data=request.data, context={"request": request})
- if serializer.is_valid():
- serializer.save(user=request.user, media=media)
- if request.user != media.user:
- notify_user_on_comment(friendly_token=media.friendly_token)
- # here forward the comment to check if a user was mentioned
- if settings.ALLOW_MENTION_IN_COMMENTS:
- check_comment_for_mention(friendly_token=media.friendly_token, comment_text=serializer.data['text'])
- return Response(serializer.data, status=status.HTTP_201_CREATED)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- class UserActions(APIView):
- parser_classes = (JSONParser,)
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name='action', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='action', required=True, enum=VALID_USER_ACTIONS),
- ],
- tags=['Users'],
- operation_summary='List user actions',
- operation_description='Lists user actions',
- )
- def get(self, request, action):
- media = []
- if action in VALID_USER_ACTIONS:
- if request.user.is_authenticated:
- media = Media.objects.select_related("user").filter(mediaactions__user=request.user, mediaactions__action=action).order_by("-mediaactions__action_date")
- elif request.session.session_key:
- media = (
- Media.objects.select_related("user")
- .filter(
- mediaactions__session_key=request.session.session_key,
- mediaactions__action=action,
- )
- .order_by("-mediaactions__action_date")
- )
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- paginator = pagination_class()
- page = paginator.paginate_queryset(media, request)
- serializer = MediaSerializer(page, many=True, context={"request": request})
- return paginator.get_paginated_response(serializer.data)
- class CategoryList(APIView):
- """List categories"""
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Categories'],
- operation_summary='Lists Categories',
- operation_description='Lists all categories',
- responses={
- 200: openapi.Response('response description', CategorySerializer),
- },
- )
- def get(self, request, format=None):
- categories = Category.objects.filter().order_by("title")
- serializer = CategorySerializer(categories, many=True, context={"request": request})
- ret = serializer.data
- return Response(ret)
- class TagList(APIView):
- """List tags"""
- @swagger_auto_schema(
- manual_parameters=[
- openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
- ],
- tags=['Tags'],
- operation_summary='Lists Tags',
- operation_description='Paginated listing of all tags',
- responses={
- 200: openapi.Response('response description', TagSerializer),
- },
- )
- def get(self, request, format=None):
- tags = Tag.objects.filter().order_by("-media_count")
- pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
- paginator = pagination_class()
- page = paginator.paginate_queryset(tags, request)
- serializer = TagSerializer(page, many=True, context={"request": request})
- return paginator.get_paginated_response(serializer.data)
- class EncodeProfileList(APIView):
- """List encode profiles"""
- @swagger_auto_schema(
- manual_parameters=[],
- tags=['Encoding Profiles'],
- operation_summary='List Encoding Profiles',
- operation_description='Lists all encoding profiles for videos',
- responses={200: EncodeProfileSerializer(many=True)},
- )
- def get(self, request, format=None):
- profiles = EncodeProfile.objects.all()
- serializer = EncodeProfileSerializer(profiles, many=True, context={"request": request})
- return Response(serializer.data)
- class TasksList(APIView):
- """List tasks"""
- swagger_schema = None
- permission_classes = (permissions.IsAdminUser,)
- def get(self, request, format=None):
- ret = list_tasks()
- return Response(ret)
- class TaskDetail(APIView):
- """Cancel a task"""
- swagger_schema = None
- permission_classes = (permissions.IsAdminUser,)
- def delete(self, request, uid, format=None):
- revoke(uid, terminate=True)
- return Response(status=status.HTTP_204_NO_CONTENT)
|