12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400 |
- 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 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, get_alphanumeric_only, 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 = get_alphanumeric_only(tag)
- tag = tag[:99]
- 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 = {}
- return render(request, "cms/featured-media.html", context)
- def index(request):
- """Index view"""
- context = {}
- return render(request, "cms/index.html", context)
- def latest_media(request):
- """List latest media view"""
- context = {}
- 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 = {}
- 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
- 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)
|