methods.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. # Kudos to Werner Robitza, AVEQ GmbH, for helping with ffmpeg
  2. # related content
  3. import itertools
  4. import logging
  5. import random
  6. import re
  7. from datetime import datetime
  8. from django.conf import settings
  9. from django.core.cache import cache
  10. from django.core.mail import EmailMessage
  11. from django.db.models import Q
  12. from cms import celery_app
  13. from . import models
  14. from .helpers import mask_ip
  15. logger = logging.getLogger(__name__)
  16. def get_user_or_session(request):
  17. """Return a dictionary with user info
  18. whether user is authenticated or not
  19. this is used in action calculations, example for
  20. increasing the watch counter of a media
  21. """
  22. ret = {}
  23. if request.user.is_authenticated:
  24. ret["user_id"] = request.user.id
  25. else:
  26. if not request.session.session_key:
  27. request.session.save()
  28. ret["user_session"] = request.session.session_key
  29. if settings.MASK_IPS_FOR_ACTIONS:
  30. ret["remote_ip_addr"] = mask_ip(request.META.get("REMOTE_ADDR"))
  31. else:
  32. ret["remote_ip_addr"] = request.META.get("REMOTE_ADDR")
  33. return ret
  34. def pre_save_action(media, user, session_key, action, remote_ip):
  35. """This will perform some checkes
  36. example threshold checks, before performing an action
  37. """
  38. from actions.models import MediaAction
  39. if user:
  40. query = MediaAction.objects.filter(media=media, action=action, user=user)
  41. else:
  42. query = MediaAction.objects.filter(media=media, action=action, session_key=session_key)
  43. query = query.order_by("-action_date")
  44. if query:
  45. query = query.first()
  46. if action in ["like", "dislike", "report"]:
  47. return False # has alread done action once
  48. elif action == "watch" and user:
  49. # increase the number of times a media is viewed
  50. if media.duration:
  51. now = datetime.now(query.action_date.tzinfo)
  52. if (now - query.action_date).seconds > media.duration:
  53. return True
  54. else:
  55. if user: # first time action
  56. return True
  57. if not user:
  58. # perform some checking for requests where no session
  59. # id is specified (and user is anonymous) to avoid spam
  60. # eg allow for the same remote_ip for a specific number of actions
  61. query = MediaAction.objects.filter(media=media, action=action, remote_ip=remote_ip).filter(user=None).order_by("-action_date")
  62. if query:
  63. query = query.first()
  64. now = datetime.now(query.action_date.tzinfo)
  65. if action == "watch":
  66. if not (now - query.action_date).seconds > media.duration:
  67. return False
  68. if (now - query.action_date).seconds > settings.TIME_TO_ACTION_ANONYMOUS:
  69. return True
  70. else:
  71. return True
  72. return False
  73. def is_mediacms_editor(user):
  74. """Whether user is MediaCMS editor"""
  75. editor = False
  76. try:
  77. if user.is_superuser or user.is_manager or user.is_editor:
  78. editor = True
  79. except BaseException:
  80. pass
  81. return editor
  82. def is_mediacms_manager(user):
  83. """Whether user is MediaCMS manager"""
  84. manager = False
  85. try:
  86. if user.is_superuser or user.is_manager:
  87. manager = True
  88. except BaseException:
  89. pass
  90. return manager
  91. def get_next_state(user, current_state, next_state):
  92. """Return valid state, given a current and next state
  93. and the user object.
  94. Users may themselves perform only allowed transitions
  95. """
  96. if next_state not in ["public", "private", "unlisted"]:
  97. next_state = settings.PORTAL_WORKFLOW # get default state
  98. if is_mediacms_editor(user):
  99. # allow any transition
  100. return next_state
  101. if settings.PORTAL_WORKFLOW == "private":
  102. next_state = "private"
  103. if settings.PORTAL_WORKFLOW == "unlisted":
  104. # don't allow to make media public in this case
  105. if next_state == "public":
  106. next_state = current_state
  107. return next_state
  108. def notify_users(friendly_token=None, action=None, extra=None):
  109. """Notify users through email, for a set of actions"""
  110. notify_items = []
  111. media = None
  112. if friendly_token:
  113. media = models.Media.objects.filter(friendly_token=friendly_token).first()
  114. if not media:
  115. return False
  116. media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
  117. if action == "media_reported" and media:
  118. msg = """
  119. Media %s was reported.
  120. Reason: %s\n
  121. Total times this media has been reported: %s\n
  122. Media becomes private if it gets reported %s times\n
  123. """ % (
  124. media_url,
  125. extra,
  126. media.reported_times,
  127. settings.REPORTED_TIMES_THRESHOLD,
  128. )
  129. if settings.ADMINS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
  130. title = "[{}] - Media was reported".format(settings.PORTAL_NAME)
  131. d = {}
  132. d["title"] = title
  133. d["msg"] = msg
  134. d["to"] = settings.ADMIN_EMAIL_LIST
  135. notify_items.append(d)
  136. if settings.USERS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
  137. title = "[{}] - Media was reported".format(settings.PORTAL_NAME)
  138. d = {}
  139. d["title"] = title
  140. d["msg"] = msg
  141. d["to"] = [media.user.email]
  142. notify_items.append(d)
  143. if action == "media_added" and media:
  144. if settings.ADMINS_NOTIFICATIONS.get("MEDIA_ADDED", False):
  145. title = "[{}] - Media was added".format(settings.PORTAL_NAME)
  146. msg = """
  147. Media %s was added by user %s.
  148. """ % (
  149. media_url,
  150. media.user,
  151. )
  152. d = {}
  153. d["title"] = title
  154. d["msg"] = msg
  155. d["to"] = settings.ADMIN_EMAIL_LIST
  156. notify_items.append(d)
  157. if settings.USERS_NOTIFICATIONS.get("MEDIA_ADDED", False):
  158. title = "[{}] - Your media was added".format(settings.PORTAL_NAME)
  159. msg = """
  160. Your media has been added! It will be encoded and will be available soon.
  161. URL: %s
  162. """ % (
  163. media_url
  164. )
  165. d = {}
  166. d["title"] = title
  167. d["msg"] = msg
  168. d["to"] = [media.user.email]
  169. notify_items.append(d)
  170. for item in notify_items:
  171. email = EmailMessage(item["title"], item["msg"], settings.DEFAULT_FROM_EMAIL, item["to"])
  172. email.send(fail_silently=True)
  173. return True
  174. def show_recommended_media(request, limit=100):
  175. """Return a list of recommended media
  176. used on the index page
  177. """
  178. basic_query = Q(listable=True)
  179. pmi = cache.get("popular_media_ids")
  180. # produced by task get_list_of_popular_media and cached
  181. if pmi:
  182. media = list(models.Media.objects.filter(friendly_token__in=pmi).filter(basic_query).prefetch_related("user")[:limit])
  183. else:
  184. media = list(models.Media.objects.filter(basic_query).order_by("-views", "-likes").prefetch_related("user")[:limit])
  185. random.shuffle(media)
  186. return media
  187. def show_related_media(media, request=None, limit=100):
  188. """Return a list of related media"""
  189. if settings.RELATED_MEDIA_STRATEGY == "calculated":
  190. return show_related_media_calculated(media, request, limit)
  191. elif settings.RELATED_MEDIA_STRATEGY == "author":
  192. return show_related_media_author(media, request, limit)
  193. return show_related_media_content(media, request, limit)
  194. def show_related_media_content(media, request, limit):
  195. """Return a list of related media based on simple calculations"""
  196. # Create list with author items
  197. # then items on same category, then some random(latest)
  198. # Aim is to always show enough (limit) videos
  199. # and include author videos in any case
  200. q_author = Q(listable=True, user=media.user)
  201. m = list(models.Media.objects.filter(q_author).order_by().prefetch_related("user")[:limit])
  202. # order by random criteria so that it doesn't bring the same results
  203. # attention: only fields that are indexed make sense here! also need
  204. # find a way for indexes with more than 1 field
  205. order_criteria = [
  206. "-views",
  207. "views",
  208. "add_date",
  209. "-add_date",
  210. "featured",
  211. "-featured",
  212. "user_featured",
  213. "-user_featured",
  214. ]
  215. # TODO: MAke this mess more readable, and add TAGS support - aka related
  216. # tags rather than random media
  217. if len(m) < limit:
  218. category = media.category.first()
  219. if category:
  220. q_category = Q(listable=True, category=category)
  221. q_res = models.Media.objects.filter(q_category).order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]).prefetch_related("user")[: limit - media.user.media_count]
  222. m = list(itertools.chain(m, q_res))
  223. if len(m) < limit:
  224. q_generic = Q(listable=True)
  225. q_res = models.Media.objects.filter(q_generic).order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]).prefetch_related("user")[: limit - media.user.media_count]
  226. m = list(itertools.chain(m, q_res))
  227. m = list(set(m[:limit])) # remove duplicates
  228. try:
  229. m.remove(media) # remove media from results
  230. except ValueError:
  231. pass
  232. random.shuffle(m)
  233. return m
  234. def show_related_media_author(media, request, limit):
  235. """Return a list of related media form the same author"""
  236. q_author = Q(listable=True, user=media.user)
  237. m = list(models.Media.objects.filter(q_author).order_by().prefetch_related("user")[:limit])
  238. # order by random criteria so that it doesn't bring the same results
  239. # attention: only fields that are indexed make sense here! also need
  240. # find a way for indexes with more than 1 field
  241. m = list(set(m[:limit])) # remove duplicates
  242. try:
  243. m.remove(media) # remove media from results
  244. except ValueError:
  245. pass
  246. random.shuffle(m)
  247. return m
  248. def show_related_media_calculated(media, request, limit):
  249. """Return a list of related media based on ML recommendations
  250. A big todo!
  251. """
  252. return []
  253. def update_user_ratings(user, media, user_ratings):
  254. """Populate user ratings for a media"""
  255. for rating in user_ratings:
  256. user_rating = models.Rating.objects.filter(user=user, media_id=media, rating_category_id=rating.get("category_id")).only("score").first()
  257. if user_rating:
  258. rating["score"] = user_rating.score
  259. return user_ratings
  260. def notify_user_on_comment(friendly_token):
  261. """Notify users through email, for a set of actions"""
  262. media = models.Media.objects.filter(friendly_token=friendly_token).first()
  263. if not media:
  264. return False
  265. user = media.user
  266. media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
  267. if user.notification_on_comments:
  268. title = "[{}] - A comment was added".format(settings.PORTAL_NAME)
  269. msg = """
  270. A comment has been added to your media %s .
  271. View it on %s
  272. """ % (
  273. media.title,
  274. media_url,
  275. )
  276. email = EmailMessage(title, msg, settings.DEFAULT_FROM_EMAIL, [media.user.email])
  277. email.send(fail_silently=True)
  278. return True
  279. def notify_user_on_mention(friendly_token, user_mentioned, cleaned_comment):
  280. from users.models import User
  281. media = models.Media.objects.filter(friendly_token=friendly_token).first()
  282. if not media:
  283. return False
  284. user = User.objects.filter(username=user_mentioned).first()
  285. media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
  286. if user.notification_on_comments:
  287. title = "[{}] - You were mentioned in a comment".format(settings.PORTAL_NAME)
  288. msg = """
  289. You were mentioned in a comment on %s .
  290. View it on %s
  291. Comment : %s
  292. """ % (
  293. media.title,
  294. media_url,
  295. cleaned_comment,
  296. )
  297. email = EmailMessage(title, msg, settings.DEFAULT_FROM_EMAIL, [user.email])
  298. email.send(fail_silently=True)
  299. return True
  300. def check_comment_for_mention(friendly_token, comment_text):
  301. """Check the comment for any mentions, and notify each mentioned users"""
  302. cleaned_comment = ''
  303. matches = re.findall('@\\(_(.+?)_\\)', comment_text)
  304. if matches:
  305. cleaned_comment = clean_comment(comment_text)
  306. for match in list(dict.fromkeys(matches)):
  307. notify_user_on_mention(friendly_token, match, cleaned_comment)
  308. def clean_comment(raw_comment):
  309. """Clean the comment fromn ID and username Mentions for preview purposes"""
  310. cleaned_comment = re.sub('@\\(_(.+?)_\\)', '', raw_comment)
  311. cleaned_comment = cleaned_comment.replace("[_", '')
  312. cleaned_comment = cleaned_comment.replace("_]", '')
  313. return cleaned_comment
  314. def list_tasks():
  315. """Lists celery tasks
  316. To be used in an admin dashboard
  317. """
  318. i = celery_app.control.inspect([])
  319. ret = {}
  320. temp = {}
  321. task_ids = []
  322. media_profile_pairs = []
  323. temp["active"] = i.active()
  324. temp["reserved"] = i.reserved()
  325. temp["scheduled"] = i.scheduled()
  326. for state, state_dict in temp.items():
  327. ret[state] = {}
  328. ret[state]["tasks"] = []
  329. for worker, worker_dict in state_dict.items():
  330. for task in worker_dict:
  331. task_dict = {}
  332. task_dict["worker"] = worker
  333. task_dict["task_id"] = task.get("id")
  334. task_ids.append(task.get("id"))
  335. task_dict["args"] = task.get("args")
  336. task_dict["name"] = task.get("name")
  337. task_dict["time_start"] = task.get("time_start")
  338. if task.get("name") == "encode_media":
  339. task_args = task.get("args")
  340. for bad in "(),'":
  341. task_args = task_args.replace(bad, "")
  342. friendly_token = task_args.split()[0]
  343. profile_id = task_args.split()[1]
  344. media = models.Media.objects.filter(friendly_token=friendly_token).first()
  345. if media:
  346. profile = models.EncodeProfile.objects.filter(id=profile_id).first()
  347. if profile:
  348. media_profile_pairs.append((media.friendly_token, profile.id))
  349. task_dict["info"] = {}
  350. task_dict["info"]["profile name"] = profile.name
  351. task_dict["info"]["media title"] = media.title
  352. encoding = models.Encoding.objects.filter(task_id=task.get("id")).first()
  353. if encoding:
  354. task_dict["info"]["encoding progress"] = encoding.progress
  355. ret[state]["tasks"].append(task_dict)
  356. ret["task_ids"] = task_ids
  357. ret["media_profile_pairs"] = media_profile_pairs
  358. return ret