views.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. from datetime import datetime, timedelta
  2. from django.shortcuts import render
  3. from django.http import HttpResponseRedirect
  4. from django.conf import settings
  5. from django.shortcuts import get_object_or_404
  6. from django.db.models import Q
  7. from django.contrib.auth.decorators import login_required
  8. from django.contrib import messages
  9. from django.template.defaultfilters import slugify
  10. from django.core.mail import EmailMessage
  11. from django.contrib.postgres.search import SearchQuery
  12. from rest_framework import permissions
  13. from rest_framework.views import APIView
  14. from rest_framework.response import Response
  15. from rest_framework.settings import api_settings
  16. from rest_framework.exceptions import PermissionDenied
  17. from rest_framework import status
  18. from rest_framework.parsers import (
  19. JSONParser,
  20. MultiPartParser,
  21. FileUploadParser,
  22. FormParser,
  23. )
  24. from celery.task.control import revoke
  25. from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor
  26. from cms.permissions import user_allowed_to_upload
  27. from cms.custom_pagination import FastPaginationWithoutCount
  28. from actions.models import MediaAction, USER_MEDIA_ACTIONS
  29. from users.models import User
  30. from .helpers import produce_ffmpeg_commands, clean_query
  31. from .models import (
  32. Media,
  33. EncodeProfile,
  34. Encoding,
  35. Playlist,
  36. PlaylistMedia,
  37. Comment,
  38. Category,
  39. Tag,
  40. )
  41. from .forms import MediaForm, ContactForm, SubtitleForm
  42. from .tasks import save_user_action
  43. from .methods import (
  44. list_tasks,
  45. get_user_or_session,
  46. show_recommended_media,
  47. show_related_media,
  48. is_mediacms_editor,
  49. is_mediacms_manager,
  50. update_user_ratings,
  51. notify_user_on_comment,
  52. )
  53. from .serializers import (
  54. MediaSerializer,
  55. CategorySerializer,
  56. TagSerializer,
  57. SingleMediaSerializer,
  58. EncodeProfileSerializer,
  59. MediaSearchSerializer,
  60. PlaylistSerializer,
  61. PlaylistDetailSerializer,
  62. CommentSerializer,
  63. )
  64. from .stop_words import STOP_WORDS
  65. VALID_USER_ACTIONS = [action for action, name in USER_MEDIA_ACTIONS]
  66. def about(request):
  67. """About view"""
  68. context = {}
  69. return render(request, "cms/about.html", context)
  70. @login_required
  71. def add_subtitle(request):
  72. """Add subtitle view"""
  73. friendly_token = request.GET.get("m", "").strip()
  74. if not friendly_token:
  75. return HttpResponseRedirect("/")
  76. media = Media.objects.filter(friendly_token=friendly_token).first()
  77. if not media:
  78. return HttpResponseRedirect("/")
  79. if not (
  80. request.user == media.user
  81. or is_mediacms_editor(request.user)
  82. or is_mediacms_manager(request.user)
  83. ):
  84. return HttpResponseRedirect("/")
  85. if request.method == "POST":
  86. form = SubtitleForm(media, request.POST, request.FILES)
  87. if form.is_valid():
  88. subtitle = form.save()
  89. messages.add_message(request, messages.INFO, "Subtitle was added!")
  90. return HttpResponseRedirect(subtitle.media.get_absolute_url())
  91. else:
  92. form = SubtitleForm(media_item=media)
  93. return render(request, "cms/add_subtitle.html", {"form": form})
  94. def categories(request):
  95. """List categories view"""
  96. context = {}
  97. return render(request, "cms/categories.html", context)
  98. def contact(request):
  99. """Contact view"""
  100. context = {}
  101. if request.method == "GET":
  102. form = ContactForm(request.user)
  103. context["form"] = form
  104. else:
  105. form = ContactForm(request.user, request.POST)
  106. if form.is_valid():
  107. if request.user.is_authenticated:
  108. from_email = request.user.email
  109. name = request.user.name
  110. else:
  111. from_email = request.POST.get("from_email")
  112. name = request.POST.get("name")
  113. message = request.POST.get("message")
  114. title = "[{}] - Contact form message received".format(settings.PORTAL_NAME)
  115. msg = """
  116. You have received a message through the contact form\n
  117. Sender name: %s
  118. Sender email: %s\n
  119. \n %s
  120. """ % (
  121. name,
  122. from_email,
  123. message,
  124. )
  125. email = EmailMessage(
  126. title,
  127. msg,
  128. settings.DEFAULT_FROM_EMAIL,
  129. settings.ADMIN_EMAIL_LIST,
  130. reply_to=[from_email],
  131. )
  132. email.send(fail_silently=True)
  133. success_msg = "Message was sent! Thanks for contacting"
  134. context["success_msg"] = success_msg
  135. return render(request, "cms/contact.html", context)
  136. def history(request):
  137. """Show personal history view"""
  138. context = {}
  139. return render(request, "cms/history.html", context)
  140. @login_required
  141. def edit_media(request):
  142. """Edit a media view"""
  143. friendly_token = request.GET.get("m", "").strip()
  144. if not friendly_token:
  145. return HttpResponseRedirect("/")
  146. media = Media.objects.filter(friendly_token=friendly_token).first()
  147. if not media:
  148. return HttpResponseRedirect("/")
  149. if not (
  150. request.user == media.user
  151. or is_mediacms_editor(request.user)
  152. or is_mediacms_manager(request.user)
  153. ):
  154. return HttpResponseRedirect("/")
  155. if request.method == "POST":
  156. form = MediaForm(request.user, request.POST, request.FILES, instance=media)
  157. if form.is_valid():
  158. media = form.save()
  159. for tag in media.tags.all():
  160. media.tags.remove(tag)
  161. if form.cleaned_data.get("new_tags"):
  162. for tag in form.cleaned_data.get("new_tags").split(","):
  163. tag = slugify(tag)
  164. if tag:
  165. try:
  166. tag = Tag.objects.get(title=tag)
  167. except Tag.DoesNotExist:
  168. tag = Tag.objects.create(title=tag, user=request.user)
  169. if tag not in media.tags.all():
  170. media.tags.add(tag)
  171. messages.add_message(request, messages.INFO, "Media was edited!")
  172. return HttpResponseRedirect(media.get_absolute_url())
  173. else:
  174. form = MediaForm(request.user, instance=media)
  175. return render(
  176. request,
  177. "cms/edit_media.html",
  178. {"form": form, "add_subtitle_url": media.add_subtitle_url},
  179. )
  180. def embed_media(request):
  181. """Embed media view"""
  182. friendly_token = request.GET.get("m", "").strip()
  183. if not friendly_token:
  184. return HttpResponseRedirect("/")
  185. media = Media.objects.values("title").filter(friendly_token=friendly_token).first()
  186. if not media:
  187. return HttpResponseRedirect("/")
  188. user_or_session = get_user_or_session(request)
  189. context = {}
  190. context["media"] = friendly_token
  191. return render(request, "cms/embed.html", context)
  192. def featured_media(request):
  193. """List featured media view"""
  194. context = {}
  195. return render(request, "cms/featured-media.html", context)
  196. def index(request):
  197. """Index view"""
  198. context = {}
  199. return render(request, "cms/index.html", context)
  200. def latest_media(request):
  201. """List latest media view"""
  202. context = {}
  203. return render(request, "cms/latest-media.html", context)
  204. def liked_media(request):
  205. """List user's liked media view"""
  206. context = {}
  207. return render(request, "cms/liked_media.html", context)
  208. @login_required
  209. def manage_users(request):
  210. """List users management view"""
  211. context = {}
  212. return render(request, "cms/manage_users.html", context)
  213. @login_required
  214. def manage_media(request):
  215. """List media management view"""
  216. context = {}
  217. return render(request, "cms/manage_media.html", context)
  218. @login_required
  219. def manage_comments(request):
  220. """List comments management view"""
  221. context = {}
  222. return render(request, "cms/manage_comments.html", context)
  223. def members(request):
  224. """List members view"""
  225. context = {}
  226. return render(request, "cms/members.html", context)
  227. def recommended_media(request):
  228. """List recommended media view"""
  229. context = {}
  230. return render(request, "cms/recommended-media.html", context)
  231. def search(request):
  232. """Search view"""
  233. context = {}
  234. RSS_URL = f"/rss{request.environ['REQUEST_URI']}"
  235. context["RSS_URL"] = RSS_URL
  236. return render(request, "cms/search.html", context)
  237. def tags(request):
  238. """List tags view"""
  239. context = {}
  240. return render(request, "cms/tags.html", context)
  241. def tos(request):
  242. """Terms of service view"""
  243. context = {}
  244. return render(request, "cms/tos.html", context)
  245. def upload_media(request):
  246. """Upload media view"""
  247. from allauth.account.forms import LoginForm
  248. form = LoginForm()
  249. context = {}
  250. context["form"] = form
  251. context["can_add"] = user_allowed_to_upload(request)
  252. can_upload_exp = settings.CANNOT_ADD_MEDIA_MESSAGE
  253. context["can_upload_exp"] = can_upload_exp
  254. return render(request, "cms/add-media.html", context)
  255. def view_media(request):
  256. """View media view"""
  257. friendly_token = request.GET.get("m", "").strip()
  258. context = {}
  259. media = Media.objects.filter(friendly_token=friendly_token).first()
  260. if not media:
  261. context["media"] = None
  262. return render(request, "cms/media.html", context)
  263. user_or_session = get_user_or_session(request)
  264. save_user_action.delay(
  265. user_or_session, friendly_token=friendly_token, action="watch"
  266. )
  267. context = {}
  268. context["media"] = friendly_token
  269. context["media_object"] = media
  270. context["CAN_DELETE_MEDIA"] = False
  271. context["CAN_EDIT_MEDIA"] = False
  272. context["CAN_DELETE_COMMENTS"] = False
  273. if request.user.is_authenticated:
  274. if (
  275. (media.user.id == request.user.id)
  276. or is_mediacms_editor(request.user)
  277. or is_mediacms_manager(request.user)
  278. ):
  279. context["CAN_DELETE_MEDIA"] = True
  280. context["CAN_EDIT_MEDIA"] = True
  281. context["CAN_DELETE_COMMENTS"] = True
  282. return render(request, "cms/media.html", context)
  283. def view_playlist(request, friendly_token):
  284. """View playlist view"""
  285. try:
  286. playlist = Playlist.objects.get(friendly_token=friendly_token)
  287. except BaseException:
  288. playlist = None
  289. context = {}
  290. context["playlist"] = playlist
  291. return render(request, "cms/playlist.html", context)
  292. class MediaList(APIView):
  293. """Media listings views"""
  294. permission_classes = (IsAuthorizedToAdd,)
  295. parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
  296. def get(self, request, format=None):
  297. # Show media
  298. params = self.request.query_params
  299. show_param = params.get("show", "")
  300. author_param = params.get("author", "").strip()
  301. if author_param:
  302. user_queryset = User.objects.all()
  303. user = get_object_or_404(user_queryset, username=author_param)
  304. if show_param == "recommended":
  305. pagination_class = FastPaginationWithoutCount
  306. media = show_recommended_media(request, limit=50)
  307. else:
  308. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  309. if author_param:
  310. # in case request.user is the user here, show
  311. # all media independant of state
  312. if self.request.user == user:
  313. basic_query = Q(user=user)
  314. else:
  315. basic_query = Q(listable=True, user=user)
  316. else:
  317. # base listings should show safe content
  318. basic_query = Q(listable=True)
  319. if show_param == "featured":
  320. media = Media.objects.filter(basic_query, featured=True)
  321. else:
  322. media = Media.objects.filter(basic_query).order_by("-add_date")
  323. paginator = pagination_class()
  324. if show_param != "recommended":
  325. media = media.prefetch_related("user")
  326. page = paginator.paginate_queryset(media, request)
  327. serializer = MediaSerializer(page, many=True, context={"request": request})
  328. return paginator.get_paginated_response(serializer.data)
  329. def post(self, request, format=None):
  330. # Add new media
  331. serializer = MediaSerializer(data=request.data, context={"request": request})
  332. if serializer.is_valid():
  333. media_file = request.data["media_file"]
  334. serializer.save(user=request.user, media_file=media_file)
  335. return Response(serializer.data, status=status.HTTP_201_CREATED)
  336. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  337. class MediaDetail(APIView):
  338. """
  339. Retrieve, update or delete a media instance.
  340. """
  341. permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor)
  342. parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
  343. def get_object(self, friendly_token, password=None):
  344. try:
  345. media = (
  346. Media.objects.select_related("user")
  347. .prefetch_related("encodings__profile")
  348. .get(friendly_token=friendly_token)
  349. )
  350. # this need be explicitly called, and will call
  351. # has_object_permission() after has_permission has succeeded
  352. self.check_object_permissions(self.request, media)
  353. if media.state == "private" and not (
  354. self.request.user == media.user or is_mediacms_editor(self.request.user)
  355. ):
  356. if (
  357. (not password)
  358. or (not media.password)
  359. or (password != media.password)
  360. ):
  361. return Response(
  362. {"detail": "media is private"},
  363. status=status.HTTP_401_UNAUTHORIZED,
  364. )
  365. return media
  366. except PermissionDenied:
  367. return Response(
  368. {"detail": "bad permissions"}, status=status.HTTP_401_UNAUTHORIZED
  369. )
  370. except BaseException:
  371. return Response(
  372. {"detail": "media file does not exist"},
  373. status=status.HTTP_400_BAD_REQUEST,
  374. )
  375. def get(self, request, friendly_token, format=None):
  376. # Get media details
  377. password = request.GET.get("password")
  378. media = self.get_object(friendly_token, password=password)
  379. if isinstance(media, Response):
  380. return media
  381. serializer = SingleMediaSerializer(media, context={"request": request})
  382. if media.state == "private":
  383. related_media = []
  384. else:
  385. related_media = show_related_media(media, request=request, limit=100)
  386. related_media_serializer = MediaSerializer(
  387. related_media, many=True, context={"request": request}
  388. )
  389. related_media = related_media_serializer.data
  390. ret = serializer.data
  391. # update rattings info with user specific ratings
  392. # eg user has already rated for this media
  393. # this only affects user rating and only if enabled
  394. if (
  395. settings.ALLOW_RATINGS
  396. and ret.get("ratings_info")
  397. and not request.user.is_anonymous
  398. ):
  399. ret["ratings_info"] = update_user_ratings(
  400. request.user, media, ret.get("ratings_info")
  401. )
  402. ret["related_media"] = related_media
  403. return Response(ret)
  404. def post(self, request, friendly_token, format=None):
  405. """superuser actions
  406. Available only to MediaCMS editors and managers
  407. Action is a POST variable, review and encode are implemented
  408. """
  409. media = self.get_object(friendly_token)
  410. if isinstance(media, Response):
  411. return media
  412. if not (is_mediacms_editor(request.user) or is_mediacms_manager(request.user)):
  413. return Response(
  414. {"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST
  415. )
  416. action = request.data.get("type")
  417. profiles_list = request.data.get("encoding_profiles")
  418. result = request.data.get("result", True)
  419. if action == "encode":
  420. # Create encoding tasks for specific profiles
  421. valid_profiles = []
  422. if profiles_list:
  423. if isinstance(profiles_list, list):
  424. for p in profiles_list:
  425. p = EncodeProfile.objects.filter(id=p).first()
  426. if p:
  427. valid_profiles.append(p)
  428. elif isinstance(profiles_list, str):
  429. try:
  430. p = EncodeProfile.objects.filter(id=int(profiles_list)).first()
  431. valid_profiles.append(p)
  432. except ValueError:
  433. return Response(
  434. {
  435. "detail": "encoding_profiles must be int or list of ints of valid encode profiles"
  436. },
  437. status=status.HTTP_400_BAD_REQUEST,
  438. )
  439. media.encode(profiles=valid_profiles)
  440. return Response(
  441. {"detail": "media will be encoded"}, status=status.HTTP_201_CREATED
  442. )
  443. elif action == "review":
  444. if result:
  445. media.is_reviewed = True
  446. elif result == False:
  447. media.is_reviewed = False
  448. media.save(update_fields=["is_reviewed"])
  449. return Response(
  450. {"detail": "media reviewed set"}, status=status.HTTP_201_CREATED
  451. )
  452. return Response(
  453. {"detail": "not valid action or no action specified"},
  454. status=status.HTTP_400_BAD_REQUEST,
  455. )
  456. def put(self, request, friendly_token, format=None):
  457. # Update a media object
  458. media = self.get_object(friendly_token)
  459. if isinstance(media, Response):
  460. return media
  461. serializer = MediaSerializer(
  462. media, data=request.data, context={"request": request}
  463. )
  464. if serializer.is_valid():
  465. media_file = request.data["media_file"]
  466. serializer.save(user=request.user, media_file=media_file)
  467. return Response(serializer.data, status=status.HTTP_201_CREATED)
  468. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  469. def delete(self, request, friendly_token, format=None):
  470. # Delete a media object
  471. media = self.get_object(friendly_token)
  472. if isinstance(media, Response):
  473. return media
  474. media.delete()
  475. return Response(status=status.HTTP_204_NO_CONTENT)
  476. class MediaActions(APIView):
  477. """
  478. Retrieve, update or delete a media action instance.
  479. """
  480. permission_classes = (permissions.AllowAny,)
  481. parser_classes = (JSONParser,)
  482. def get_object(self, friendly_token):
  483. try:
  484. media = (
  485. Media.objects.select_related("user")
  486. .prefetch_related("encodings__profile")
  487. .get(friendly_token=friendly_token)
  488. )
  489. if media.state == "private" and self.request.user != media.user:
  490. return Response(
  491. {"detail": "media is private"}, status=status.HTTP_400_BAD_REQUEST
  492. )
  493. return media
  494. except PermissionDenied:
  495. return Response(
  496. {"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST
  497. )
  498. except BaseException:
  499. return Response(
  500. {"detail": "media file does not exist"},
  501. status=status.HTTP_400_BAD_REQUEST,
  502. )
  503. def get(self, request, friendly_token, format=None):
  504. # show date and reason for each time media was reported
  505. media = self.get_object(friendly_token)
  506. if isinstance(media, Response):
  507. return media
  508. ret = {}
  509. reported = MediaAction.objects.filter(media=media, action="report")
  510. ret["reported"] = []
  511. for rep in reported:
  512. item = {"reported_date": rep.action_date, "reason": rep.extra_info}
  513. ret["reported"].append(item)
  514. return Response(ret, status=status.HTTP_200_OK)
  515. def post(self, request, friendly_token, format=None):
  516. # perform like/dislike/report actions
  517. media = self.get_object(friendly_token)
  518. if isinstance(media, Response):
  519. return media
  520. action = request.data.get("type")
  521. extra = request.data.get("extra_info")
  522. if request.user.is_anonymous:
  523. # there is a list of allowed actions for
  524. # anonymous users, specified in settings
  525. if action not in settings.ALLOW_ANONYMOUS_ACTIONS:
  526. return Response(
  527. {"detail": "action allowed on logged in users only"},
  528. status=status.HTTP_400_BAD_REQUEST,
  529. )
  530. if action:
  531. user_or_session = get_user_or_session(request)
  532. save_user_action.delay(
  533. user_or_session,
  534. friendly_token=media.friendly_token,
  535. action=action,
  536. extra_info=extra,
  537. )
  538. return Response(
  539. {"detail": "action received"}, status=status.HTTP_201_CREATED
  540. )
  541. else:
  542. return Response(
  543. {"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST
  544. )
  545. def delete(self, request, friendly_token, format=None):
  546. media = self.get_object(friendly_token)
  547. if isinstance(media, Response):
  548. return media
  549. if not request.user.is_superuser:
  550. return Response(
  551. {"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST
  552. )
  553. action = request.data.get("type")
  554. if action:
  555. if action == "report": # delete reported actions
  556. MediaAction.objects.filter(media=media, action="report").delete()
  557. media.reported_times = 0
  558. media.save(update_fields=["reported_times"])
  559. return Response(
  560. {"detail": "reset reported times counter"},
  561. status=status.HTTP_201_CREATED,
  562. )
  563. else:
  564. return Response(
  565. {"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST
  566. )
  567. class MediaSearch(APIView):
  568. """
  569. Retrieve results for searc
  570. Only GET is implemented here
  571. """
  572. parser_classes = (JSONParser,)
  573. def get(self, request, format=None):
  574. params = self.request.query_params
  575. query = params.get("q", "").strip().lower()
  576. category = params.get("c", "").strip()
  577. tag = params.get("t", "").strip()
  578. ordering = params.get("ordering", "").strip()
  579. sort_by = params.get("sort_by", "").strip()
  580. media_type = params.get("media_type", "").strip()
  581. author = params.get("author", "").strip()
  582. upload_date = params.get('upload_date', '').strip()
  583. sort_by_options = ["title", "add_date", "edit_date", "views", "likes"]
  584. if sort_by not in sort_by_options:
  585. sort_by = "add_date"
  586. if ordering == "asc":
  587. ordering = ""
  588. else:
  589. ordering = "-"
  590. if media_type not in ["video", "image", "audio", "pdf"]:
  591. media_type = None
  592. if not (query or category or tag):
  593. ret = {}
  594. return Response(ret, status=status.HTTP_200_OK)
  595. media = Media.objects.filter(state="public", is_reviewed=True)
  596. if query:
  597. # move this processing to a prepare_query function
  598. query = clean_query(query)
  599. q_parts = [
  600. q_part.rstrip("y")
  601. for q_part in query.split()
  602. if q_part not in STOP_WORDS
  603. ]
  604. if q_parts:
  605. query = SearchQuery(q_parts[0] + ":*", search_type="raw")
  606. for part in q_parts[1:]:
  607. query &= SearchQuery(part + ":*", search_type="raw")
  608. else:
  609. query = None
  610. if query:
  611. media = media.filter(search=query)
  612. if tag:
  613. media = media.filter(tags__title=tag)
  614. if category:
  615. media = media.filter(category__title__contains=category)
  616. if media_type:
  617. media = media.filter(media_type=media_type)
  618. if author:
  619. media = media.filter(user__username=author)
  620. if upload_date:
  621. gte = lte = None
  622. if upload_date == 'today':
  623. gte = datetime.now().date()
  624. if upload_date == 'this_week':
  625. gte = datetime.now() - timedelta(days=7)
  626. if upload_date == 'this_month':
  627. year = datetime.now().date().year
  628. month = datetime.now().date().month
  629. gte = datetime(year,month,1)
  630. if upload_date == 'this_year':
  631. year = datetime.now().date().year
  632. gte = datetime(year,1,1)
  633. if lte:
  634. media = media.filter(add_date__lte=lte)
  635. if gte:
  636. media = media.filter(add_date__gte=gte)
  637. media = media.order_by(f"{ordering}{sort_by}")
  638. if self.request.query_params.get("show", "").strip() == "titles":
  639. media = media.values("title")[:40]
  640. return Response(media, status=status.HTTP_200_OK)
  641. else:
  642. media = media.prefetch_related("user")
  643. if category or tag:
  644. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  645. else:
  646. # pagination_class = FastPaginationWithoutCount
  647. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  648. paginator = pagination_class()
  649. page = paginator.paginate_queryset(media, request)
  650. serializer = MediaSearchSerializer(
  651. page, many=True, context={"request": request}
  652. )
  653. return paginator.get_paginated_response(serializer.data)
  654. class PlaylistList(APIView):
  655. """Playlists listings and creation views"""
  656. permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd)
  657. parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
  658. def get(self, request, format=None):
  659. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  660. paginator = pagination_class()
  661. playlists = Playlist.objects.filter().prefetch_related("user")
  662. if "author" in self.request.query_params:
  663. author = self.request.query_params["author"].strip()
  664. playlists = playlists.filter(user__username=author)
  665. page = paginator.paginate_queryset(playlists, request)
  666. serializer = PlaylistSerializer(page, many=True, context={"request": request})
  667. return paginator.get_paginated_response(serializer.data)
  668. def post(self, request, format=None):
  669. serializer = PlaylistSerializer(data=request.data, context={"request": request})
  670. if serializer.is_valid():
  671. serializer.save(user=request.user)
  672. return Response(serializer.data, status=status.HTTP_201_CREATED)
  673. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  674. class PlaylistDetail(APIView):
  675. """Playlist related views"""
  676. permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor)
  677. parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
  678. def get_playlist(self, friendly_token):
  679. try:
  680. playlist = Playlist.objects.get(friendly_token=friendly_token)
  681. self.check_object_permissions(self.request, playlist)
  682. return playlist
  683. except PermissionDenied:
  684. return Response(
  685. {"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST
  686. )
  687. except BaseException:
  688. return Response(
  689. {"detail": "Playlist does not exist"},
  690. status=status.HTTP_400_BAD_REQUEST,
  691. )
  692. def get(self, request, friendly_token, format=None):
  693. playlist = self.get_playlist(friendly_token)
  694. if isinstance(playlist, Response):
  695. return playlist
  696. serializer = PlaylistDetailSerializer(playlist, context={"request": request})
  697. playlist_media = PlaylistMedia.objects.filter(
  698. playlist=playlist
  699. ).prefetch_related("media__user")
  700. playlist_media = [c.media for c in playlist_media]
  701. playlist_media_serializer = MediaSerializer(
  702. playlist_media, many=True, context={"request": request}
  703. )
  704. ret = serializer.data
  705. ret["playlist_media"] = playlist_media_serializer.data
  706. return Response(ret)
  707. def post(self, request, friendly_token, format=None):
  708. playlist = self.get_playlist(friendly_token)
  709. if isinstance(playlist, Response):
  710. return playlist
  711. serializer = PlaylistDetailSerializer(
  712. playlist, data=request.data, context={"request": request}
  713. )
  714. if serializer.is_valid():
  715. serializer.save(user=request.user)
  716. return Response(serializer.data, status=status.HTTP_201_CREATED)
  717. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  718. def put(self, request, friendly_token, format=None):
  719. playlist = self.get_playlist(friendly_token)
  720. if isinstance(playlist, Response):
  721. return playlist
  722. action = request.data.get("type")
  723. media_friendly_token = request.data.get("media_friendly_token")
  724. ordering = 0
  725. if request.data.get("ordering"):
  726. try:
  727. ordering = int(request.data.get("ordering"))
  728. except ValueError:
  729. pass
  730. if action in ["add", "remove", "ordering"]:
  731. media = Media.objects.filter(
  732. friendly_token=media_friendly_token, state="public"
  733. ).first()
  734. if media:
  735. if action == "add":
  736. media_in_playlist = PlaylistMedia.objects.filter(
  737. playlist=playlist
  738. ).count()
  739. if media_in_playlist >= settings.MAX_MEDIA_PER_PLAYLIST:
  740. return Response(
  741. {"detail": "max number of media for a Playlist reached"},
  742. status=status.HTTP_400_BAD_REQUEST,
  743. )
  744. else:
  745. obj, created = PlaylistMedia.objects.get_or_create(
  746. playlist=playlist,
  747. media=media,
  748. ordering=media_in_playlist + 1,
  749. )
  750. obj.save()
  751. return Response(
  752. {"detail": "media added to Playlist"},
  753. status=status.HTTP_201_CREATED,
  754. )
  755. elif action == "remove":
  756. PlaylistMedia.objects.filter(
  757. playlist=playlist, media=media
  758. ).delete()
  759. return Response(
  760. {"detail": "media removed from Playlist"},
  761. status=status.HTTP_201_CREATED,
  762. )
  763. elif action == "ordering":
  764. if ordering:
  765. playlist.set_ordering(media, ordering)
  766. return Response(
  767. {"detail": "new ordering set"},
  768. status=status.HTTP_201_CREATED,
  769. )
  770. else:
  771. return Response(
  772. {"detail": "media is not valid"}, status=status.HTTP_400_BAD_REQUEST
  773. )
  774. return Response(
  775. {"detail": "invalid or not specified action"},
  776. status=status.HTTP_400_BAD_REQUEST,
  777. )
  778. def delete(self, request, friendly_token, format=None):
  779. playlist = self.get_playlist(friendly_token)
  780. if isinstance(playlist, Response):
  781. return playlist
  782. playlist.delete()
  783. return Response(status=status.HTTP_204_NO_CONTENT)
  784. class EncodingDetail(APIView):
  785. """Experimental. This View is used by remote workers
  786. Needs heavy testing and documentation.
  787. """
  788. permission_classes = (permissions.IsAdminUser,)
  789. parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
  790. def post(self, request, encoding_id):
  791. ret = {}
  792. force = request.data.get("force", False)
  793. task_id = request.data.get("task_id", False)
  794. action = request.data.get("action", "")
  795. chunk = request.data.get("chunk", False)
  796. chunk_file_path = request.data.get("chunk_file_path", "")
  797. encoding_status = request.data.get("status", "")
  798. progress = request.data.get("progress", "")
  799. commands = request.data.get("commands", "")
  800. logs = request.data.get("logs", "")
  801. retries = request.data.get("retries", "")
  802. worker = request.data.get("worker", "")
  803. temp_file = request.data.get("temp_file", "")
  804. total_run_time = request.data.get("total_run_time", "")
  805. if action == "start":
  806. try:
  807. encoding = Encoding.objects.get(id=encoding_id)
  808. media = encoding.media
  809. profile = encoding.profile
  810. except BaseException:
  811. Encoding.objects.filter(id=encoding_id).delete()
  812. return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
  813. # TODO: break chunk True/False logic here
  814. if (
  815. Encoding.objects.filter(
  816. media=media,
  817. profile=profile,
  818. chunk=chunk,
  819. chunk_file_path=chunk_file_path,
  820. ).count()
  821. > 1
  822. and force == False
  823. ):
  824. Encoding.objects.filter(id=encoding_id).delete()
  825. return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
  826. else:
  827. Encoding.objects.filter(
  828. media=media,
  829. profile=profile,
  830. chunk=chunk,
  831. chunk_file_path=chunk_file_path,
  832. ).exclude(id=encoding.id).delete()
  833. encoding.status = "running"
  834. if task_id:
  835. encoding.task_id = task_id
  836. encoding.save()
  837. if chunk:
  838. original_media_path = chunk_file_path
  839. original_media_md5sum = encoding.md5sum
  840. original_media_url = (
  841. settings.SSL_FRONTEND_HOST + encoding.media_chunk_url
  842. )
  843. else:
  844. original_media_path = media.media_file.path
  845. original_media_md5sum = media.md5sum
  846. original_media_url = (
  847. settings.SSL_FRONTEND_HOST + media.original_media_url
  848. )
  849. ret["original_media_url"] = original_media_url
  850. ret["original_media_path"] = original_media_path
  851. ret["original_media_md5sum"] = original_media_md5sum
  852. # generating the commands here, and will replace these with temporary
  853. # files created on the remote server
  854. tf = "TEMP_FILE_REPLACE"
  855. tfpass = "TEMP_FPASS_FILE_REPLACE"
  856. ffmpeg_commands = produce_ffmpeg_commands(
  857. original_media_path,
  858. media.media_info,
  859. resolution=profile.resolution,
  860. codec=profile.codec,
  861. output_filename=tf,
  862. pass_file=tfpass,
  863. chunk=chunk,
  864. )
  865. if not ffmpeg_commands:
  866. encoding.delete()
  867. return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
  868. ret["duration"] = media.duration
  869. ret["ffmpeg_commands"] = ffmpeg_commands
  870. ret["profile_extension"] = profile.extension
  871. return Response(ret, status=status.HTTP_201_CREATED)
  872. elif action == "update_fields":
  873. try:
  874. encoding = Encoding.objects.get(id=encoding_id)
  875. except BaseException:
  876. return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
  877. to_update = ["size", "update_date"]
  878. if encoding_status:
  879. encoding.status = encoding_status
  880. to_update.append("status")
  881. if progress:
  882. encoding.progress = progress
  883. to_update.append("progress")
  884. if logs:
  885. encoding.logs = logs
  886. to_update.append("logs")
  887. if commands:
  888. encoding.commands = commands
  889. to_update.append("commands")
  890. if task_id:
  891. encoding.task_id = task_id
  892. to_update.append("task_id")
  893. if total_run_time:
  894. encoding.total_run_time = total_run_time
  895. to_update.append("total_run_time")
  896. if worker:
  897. encoding.worker = worker
  898. to_update.append("worker")
  899. if temp_file:
  900. encoding.temp_file = temp_file
  901. to_update.append("temp_file")
  902. if retries:
  903. encoding.retries = retries
  904. to_update.append("retries")
  905. try:
  906. encoding.save(update_fields=to_update)
  907. except BaseException:
  908. return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
  909. return Response({"status": "success"}, status=status.HTTP_201_CREATED)
  910. def put(self, request, encoding_id, format=None):
  911. encoding_file = request.data["file"]
  912. encoding = Encoding.objects.filter(id=encoding_id).first()
  913. if not encoding:
  914. return Response(
  915. {"detail": "encoding does not exist"},
  916. status=status.HTTP_400_BAD_REQUEST,
  917. )
  918. encoding.media_file = encoding_file
  919. encoding.save()
  920. return Response({"detail": "ok"}, status=status.HTTP_201_CREATED)
  921. class CommentList(APIView):
  922. permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd)
  923. parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
  924. def get(self, request, format=None):
  925. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  926. paginator = pagination_class()
  927. comments = Comment.objects.filter()
  928. comments = comments.prefetch_related("user")
  929. comments = comments.prefetch_related("media")
  930. params = self.request.query_params
  931. if "author" in params:
  932. author_param = params["author"].strip()
  933. user_queryset = User.objects.all()
  934. user = get_object_or_404(user_queryset, username=author_param)
  935. comments = comments.filter(user=user)
  936. page = paginator.paginate_queryset(comments, request)
  937. serializer = CommentSerializer(page, many=True, context={"request": request})
  938. return paginator.get_paginated_response(serializer.data)
  939. class CommentDetail(APIView):
  940. """Comments related views
  941. Listings of comments for a media (GET)
  942. Create comment (POST)
  943. Delete comment (DELETE)
  944. """
  945. permission_classes = (IsAuthorizedToAdd,)
  946. parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
  947. def get_object(self, friendly_token):
  948. try:
  949. media = Media.objects.select_related("user").get(
  950. friendly_token=friendly_token
  951. )
  952. self.check_object_permissions(self.request, media)
  953. if media.state == "private" and self.request.user != media.user:
  954. return Response(
  955. {"detail": "media is private"}, status=status.HTTP_400_BAD_REQUEST
  956. )
  957. return media
  958. except PermissionDenied:
  959. return Response(
  960. {"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST
  961. )
  962. except BaseException:
  963. return Response(
  964. {"detail": "media file does not exist"},
  965. status=status.HTTP_400_BAD_REQUEST,
  966. )
  967. def get(self, request, friendly_token):
  968. # list comments for a media
  969. media = self.get_object(friendly_token)
  970. if isinstance(media, Response):
  971. return media
  972. comments = media.comments.filter().prefetch_related("user")
  973. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  974. paginator = pagination_class()
  975. page = paginator.paginate_queryset(comments, request)
  976. serializer = CommentSerializer(page, many=True, context={"request": request})
  977. return paginator.get_paginated_response(serializer.data)
  978. def delete(self, request, friendly_token, uid=None):
  979. """Delete a comment
  980. Administrators, MediaCMS editors and managers,
  981. media owner, and comment owners, can delete a comment
  982. """
  983. if uid:
  984. try:
  985. comment = Comment.objects.get(uid=uid)
  986. except BaseException:
  987. return Response(
  988. {"detail": "comment does not exist"},
  989. status=status.HTTP_400_BAD_REQUEST,
  990. )
  991. if (
  992. (comment.user == self.request.user)
  993. or comment.media.user == self.request.user
  994. or is_mediacms_editor(self.request.user)
  995. ):
  996. comment.delete()
  997. else:
  998. return Response(
  999. {"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST
  1000. )
  1001. return Response(status=status.HTTP_204_NO_CONTENT)
  1002. def post(self, request, friendly_token):
  1003. """Create a comment"""
  1004. media = self.get_object(friendly_token)
  1005. if isinstance(media, Response):
  1006. return media
  1007. if not media.enable_comments:
  1008. return Response(
  1009. {"detail": "comments not allowed here"},
  1010. status=status.HTTP_400_BAD_REQUEST,
  1011. )
  1012. serializer = CommentSerializer(data=request.data, context={"request": request})
  1013. if serializer.is_valid():
  1014. serializer.save(user=request.user, media=media)
  1015. if request.user != media.user:
  1016. notify_user_on_comment(friendly_token=media.friendly_token)
  1017. return Response(serializer.data, status=status.HTTP_201_CREATED)
  1018. return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  1019. class UserActions(APIView):
  1020. parser_classes = (JSONParser,)
  1021. def get(self, request, action):
  1022. media = []
  1023. if action in VALID_USER_ACTIONS:
  1024. if request.user.is_authenticated:
  1025. media = (
  1026. Media.objects.select_related("user")
  1027. .filter(
  1028. mediaactions__user=request.user, mediaactions__action=action
  1029. )
  1030. .order_by("-mediaactions__action_date")
  1031. )
  1032. elif request.session.session_key:
  1033. media = (
  1034. Media.objects.select_related("user")
  1035. .filter(
  1036. mediaactions__session_key=request.session.session_key,
  1037. mediaactions__action=action,
  1038. )
  1039. .order_by("-mediaactions__action_date")
  1040. )
  1041. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  1042. paginator = pagination_class()
  1043. page = paginator.paginate_queryset(media, request)
  1044. serializer = MediaSerializer(page, many=True, context={"request": request})
  1045. return paginator.get_paginated_response(serializer.data)
  1046. class CategoryList(APIView):
  1047. """List categories"""
  1048. def get(self, request, format=None):
  1049. categories = Category.objects.filter().order_by("title")
  1050. serializer = CategorySerializer(
  1051. categories, many=True, context={"request": request}
  1052. )
  1053. ret = serializer.data
  1054. return Response(ret)
  1055. class TagList(APIView):
  1056. """List tags"""
  1057. def get(self, request, format=None):
  1058. tags = Tag.objects.filter().order_by("-media_count")
  1059. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
  1060. paginator = pagination_class()
  1061. page = paginator.paginate_queryset(tags, request)
  1062. serializer = TagSerializer(page, many=True, context={"request": request})
  1063. return paginator.get_paginated_response(serializer.data)
  1064. class EncodeProfileList(APIView):
  1065. """List encode profiles"""
  1066. def get(self, request, format=None):
  1067. profiles = EncodeProfile.objects.all()
  1068. serializer = EncodeProfileSerializer(
  1069. profiles, many=True, context={"request": request}
  1070. )
  1071. return Response(serializer.data)
  1072. class TasksList(APIView):
  1073. """List tasks"""
  1074. permission_classes = (permissions.IsAdminUser,)
  1075. def get(self, request, format=None):
  1076. ret = list_tasks()
  1077. return Response(ret)
  1078. class TaskDetail(APIView):
  1079. """Cancel a task"""
  1080. permission_classes = (permissions.IsAdminUser,)
  1081. def delete(self, request, uid, format=None):
  1082. revoke(uid, terminate=True)
  1083. return Response(status=status.HTTP_204_NO_CONTENT)