123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- # Kudos to Werner Robitza, AVEQ GmbH, for helping with ffmpeg
- # related content
- import logging
- import random
- import itertools
- from datetime import datetime
- from cms import celery_app
- from django.conf import settings
- from django.core.cache import cache
- from django.db.models import Q
- from django.core.mail import EmailMessage
- from . import models
- from .helpers import mask_ip
- logger = logging.getLogger(__name__)
- def get_user_or_session(request):
- """Return a dictionary with user info
- whether user is authenticated or not
- this is used in action calculations, example for
- increasing the watch counter of a media
- """
- ret = {}
- if request.user.is_authenticated:
- ret["user_id"] = request.user.id
- else:
- if not request.session.session_key:
- request.session.save()
- ret["user_session"] = request.session.session_key
- if settings.MASK_IPS_FOR_ACTIONS:
- ret["remote_ip_addr"] = mask_ip(request.META.get("REMOTE_ADDR"))
- else:
- ret["remote_ip_addr"] = request.META.get("REMOTE_ADDR")
- return ret
- def pre_save_action(media, user, session_key, action, remote_ip):
- """This will perform some checkes
- example threshold checks, before performing an action
- """
- from actions.models import MediaAction
- if user:
- query = MediaAction.objects.filter(media=media, action=action, user=user)
- else:
- query = MediaAction.objects.filter(
- media=media, action=action, session_key=session_key
- )
- query = query.order_by("-action_date")
- if query:
- query = query.first()
- if action in ["like", "dislike", "report"]:
- return False # has alread done action once
- elif action == "watch" and user:
- # increase the number of times a media is viewed
- if media.duration:
- now = datetime.now(query.action_date.tzinfo)
- if (now - query.action_date).seconds > media.duration:
- return True
- else:
- if user: # first time action
- return True
- if not user:
- # perform some checking for requests where no session
- # id is specified (and user is anonymous) to avoid spam
- # eg allow for the same remote_ip for a specific number of actions
- query = (
- MediaAction.objects.filter(media=media, action=action, remote_ip=remote_ip)
- .filter(user=None)
- .order_by("-action_date")
- )
- if query:
- query = query.first()
- now = datetime.now(query.action_date.tzinfo)
- if action == "watch":
- if not (now - query.action_date).seconds > media.duration:
- return False
- if (now - query.action_date).seconds > settings.TIME_TO_ACTION_ANONYMOUS:
- return True
- else:
- return True
- return False
- def is_mediacms_editor(user):
- """Whether user is MediaCMS editor"""
- editor = False
- try:
- if user.is_superuser or user.is_manager or user.is_editor:
- editor = True
- except BaseException:
- pass
- return editor
- def is_mediacms_manager(user):
- """Whether user is MediaCMS manager"""
- manager = False
- try:
- if user.is_superuser or user.is_manager:
- manager = True
- except BaseException:
- pass
- return manager
- def get_next_state(user, current_state, next_state):
- """Return valid state, given a current and next state
- and the user object.
- Users may themselves perform only allowed transitions
- """
- if next_state not in ["public", "private", "unlisted"]:
- next_state = settings.PORTAL_WORKFLOW # get default state
- if is_mediacms_editor(user):
- # allow any transition
- return next_state
- if settings.PORTAL_WORKFLOW == "private":
- next_state = "private"
- if settings.PORTAL_WORKFLOW == "unlisted":
- # don't allow to make media public in this case
- if next_state == "public":
- next_state = current_state
- return next_state
- def notify_users(friendly_token=None, action=None, extra=None):
- """Notify users through email, for a set of actions"""
- notify_items = []
- media = None
- if friendly_token:
- media = models.Media.objects.filter(friendly_token=friendly_token).first()
- if not media:
- return False
- media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
- if action == "media_reported" and media:
- msg = """
- Media %s was reported.
- Reason: %s\n
- Total times this media has been reported: %s\n
- Media becomes private if it gets reported %s times %s\n
- """ % (
- media_url,
- extra,
- media.reported_times,
- settings.REPORTED_TIMES_THRESHOLD,
- )
- if settings.ADMINS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
- title = "[{}] - Media was reported".format(settings.PORTAL_NAME)
- d = {}
- d["title"] = title
- d["msg"] = msg
- d["to"] = settings.ADMIN_EMAIL_LIST
- notify_items.append(d)
- if settings.USERS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
- title = "[{}] - Media was reported".format(settings.PORTAL_NAME)
- d = {}
- d["title"] = title
- d["msg"] = msg
- d["to"] = [media.user.email]
- notify_items.append(d)
- if action == "media_added" and media:
- if settings.ADMINS_NOTIFICATIONS.get("MEDIA_ADDED", False):
- title = "[{}] - Media was added".format(settings.PORTAL_NAME)
- msg = """
- Media %s was added by user %s.
- """ % (
- media_url,
- media.user,
- )
- d = {}
- d["title"] = title
- d["msg"] = msg
- d["to"] = settings.ADMIN_EMAIL_LIST
- notify_items.append(d)
- if settings.USERS_NOTIFICATIONS.get("MEDIA_ADDED", False):
- title = "[{}] - Your media was added".format(settings.PORTAL_NAME)
- msg = """
- Your media has been added! It will be encoded and will be available soon.
- URL: %s
- """ % (
- media_url
- )
- d = {}
- d["title"] = title
- d["msg"] = msg
- d["to"] = [media.user.email]
- notify_items.append(d)
- if action == "comment_added" and media:
- if settings.USERS_NOTIFICATIONS.get("COMMENT_ADDED", False):
- d = {}
- title = f"[{settings.PORTAL_NAME}] - Comment was added"
- msg = f"A comment was added on media {media_url}\n"
- d["title"] = title
- d["msg"] = msg
- d["to"] = [media.user.username]
- notify_items.append(d)
- for item in notify_items:
- email = EmailMessage(
- item["title"], item["msg"], settings.DEFAULT_FROM_EMAIL, item["to"]
- )
- email.send(fail_silently=True)
- return True
- def show_recommended_media(request, limit=100):
- """Return a list of recommended media
- used on the index page
- """
- basic_query = Q(listable=True)
- pmi = cache.get("popular_media_ids")
- # produced by task get_list_of_popular_media and cached
- if pmi:
- media = list(
- models.Media.objects.filter(friendly_token__in=pmi)
- .filter(basic_query)
- .prefetch_related("user")[:limit]
- )
- else:
- media = list(
- models.Media.objects.filter(basic_query)
- .order_by("-views", "-likes")
- .prefetch_related("user")[:limit]
- )
- random.shuffle(media)
- return media
- def show_related_media(media, request=None, limit=100):
- """Return a list of related media"""
- if settings.RELATED_MEDIA_STRATEGY == "calculated":
- return show_related_media_calculated(media, request, limit)
- elif settings.RELATED_MEDIA_STRATEGY == "author":
- return show_related_media_author(media, request, limit)
- return show_related_media_content(media, request, limit)
- def show_related_media_content(media, request, limit):
- """Return a list of related media based on simple calculations"""
- # Create list with author items
- # then items on same category, then some random(latest)
- # Aim is to always show enough (limit) videos
- # and include author videos in any case
- q_author = Q(listable=True, user=media.user)
- m = list(
- models.Media.objects.filter(q_author)
- .order_by()
- .prefetch_related("user")[:limit]
- )
- # order by random criteria so that it doesn't bring the same results
- # attention: only fields that are indexed make sense here! also need
- # find a way for indexes with more than 1 field
- order_criteria = [
- "-views",
- "views",
- "add_date",
- "-add_date",
- "featured",
- "-featured",
- "user_featured",
- "-user_featured",
- ]
- # TODO: MAke this mess more readable, and add TAGS support - aka related
- # tags rather than random media
- if len(m) < limit:
- category = media.category.first()
- if category:
- q_category = Q(listable=True, category=category)
- 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]
- )
- m = list(itertools.chain(m, q_res))
- if len(m) < limit:
- q_generic = Q(listable=True)
- 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]
- )
- m = list(itertools.chain(m, q_res))
- m = list(set(m[:limit])) # remove duplicates
- try:
- m.remove(media) # remove media from results
- except ValueError:
- pass
- random.shuffle(m)
- return m
- def show_related_media_author(media, request, limit):
- """Return a list of related media form the same author"""
- q_author = Q(listable=True, user=media.user)
- m = list(
- models.Media.objects.filter(q_author)
- .order_by()
- .prefetch_related("user")[:limit]
- )
- # order by random criteria so that it doesn't bring the same results
- # attention: only fields that are indexed make sense here! also need
- # find a way for indexes with more than 1 field
- m = list(set(m[:limit])) # remove duplicates
- try:
- m.remove(media) # remove media from results
- except ValueError:
- pass
- random.shuffle(m)
- return m
- def show_related_media_calculated(media, request, limit):
- """Return a list of related media based on ML recommendations
- A big todo!
- """
- return []
- def update_user_ratings(user, media, user_ratings):
- """Populate user ratings for a media"""
- for rating in user_ratings:
- user_rating = (
- models.Rating.objects.filter(
- user=user, media_id=media, rating_category_id=rating.get("category_id")
- )
- .only("score")
- .first()
- )
- if user_rating:
- rating["score"] = user_rating.score
- return user_ratings
- def notify_user_on_comment(friendly_token):
- """Notify users through email, for a set of actions"""
- media = None
- media = models.Media.objects.filter(friendly_token=friendly_token).first()
- if not media:
- return False
- user = media.user
- media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
- if user.notification_on_comments:
- title = "[{}] - A comment was added".format(settings.PORTAL_NAME)
- msg = """
- A comment has been added to your media %s .
- View it on %s
- """ % (
- media.title,
- media_url,
- )
- email = EmailMessage(
- title, msg, settings.DEFAULT_FROM_EMAIL, [media.user.email]
- )
- email.send(fail_silently=True)
- return True
- def list_tasks():
- """Lists celery tasks
- To be used in an admin dashboard
- """
- i = celery_app.control.inspect([])
- ret = {}
- temp = {}
- task_ids = []
- media_profile_pairs = []
- temp["active"] = i.active()
- temp["reserved"] = i.reserved()
- temp["scheduled"] = i.scheduled()
- for state, state_dict in temp.items():
- ret[state] = {}
- ret[state]["tasks"] = []
- for worker, worker_dict in state_dict.items():
- for task in worker_dict:
- task_dict = {}
- task_dict["worker"] = worker
- task_dict["task_id"] = task.get("id")
- task_ids.append(task.get("id"))
- task_dict["args"] = task.get("args")
- task_dict["name"] = task.get("name")
- task_dict["time_start"] = task.get("time_start")
- if task.get("name") == "encode_media":
- task_args = task.get("args")
- for bad in "(),'":
- task_args = task_args.replace(bad, "")
- friendly_token = task_args.split()[0]
- profile_id = task_args.split()[1]
- media = models.Media.objects.filter(
- friendly_token=friendly_token
- ).first()
- if media:
- profile = models.EncodeProfile.objects.filter(
- id=profile_id
- ).first()
- if profile:
- media_profile_pairs.append(
- (media.friendly_token, profile.id)
- )
- task_dict["info"] = {}
- task_dict["info"]["profile name"] = profile.name
- task_dict["info"]["media title"] = media.title
- encoding = models.Encoding.objects.filter(
- task_id=task.get("id")
- ).first()
- if encoding:
- task_dict["info"][
- "encoding progress"
- ] = encoding.progress
- ret[state]["tasks"].append(task_dict)
- ret["task_ids"] = task_ids
- ret["media_profile_pairs"] = media_profile_pairs
- return ret
|