format content (#198)

This commit is contained in:
Markos Gogoulos 2021-05-26 18:35:21 +03:00 committed by GitHub
parent 2d49b1df29
commit 6df942ac4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 420 additions and 962 deletions

View File

@ -1,7 +1,7 @@
# Generated by Django 3.1.4 on 2020-12-01 07:12
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# Generated by Django 3.1.4 on 2020-12-01 07:12
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -35,8 +35,6 @@ class Migration(migrations.Migration):
),
migrations.AddIndex(
model_name="mediaaction",
index=models.Index(
fields=["session_key", "action"], name="actions_med_session_fac55a_idx"
),
index=models.Index(fields=["session_key", "action"], name="actions_med_session_fac55a_idx"),
),
]

View File

@ -1,6 +1,7 @@
from django.db import models
from users.models import User
from files.models import Media
from users.models import User
USER_MEDIA_ACTIONS = (
("like", "Like"),
@ -30,15 +31,11 @@ class MediaAction(models.Model):
help_text="for not logged in users",
)
action = models.CharField(
max_length=20, choices=USER_MEDIA_ACTIONS, default="watch"
)
action = models.CharField(max_length=20, choices=USER_MEDIA_ACTIONS, default="watch")
# keeps extra info, eg on report action, why it is reported
extra_info = models.TextField(blank=True, null=True)
media = models.ForeignKey(
Media, on_delete=models.CASCADE, related_name="mediaactions"
)
media = models.ForeignKey(Media, on_delete=models.CASCADE, related_name="mediaactions")
action_date = models.DateTimeField(auto_now_add=True)
remote_ip = models.CharField(max_length=40, blank=True, null=True)

View File

@ -1,4 +1,5 @@
from __future__ import absolute_import
from .celery import app as celery_app
__all__ = ["celery_app"]

View File

@ -1,5 +1,7 @@
from __future__ import absolute_import
import os
from celery import Celery
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings")

View File

@ -1,8 +1,9 @@
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from collections import OrderedDict # requires Python 2.7 or later
from django.core.paginator import Paginator
from django.utils.functional import cached_property
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class FasterDjangoPaginator(Paginator):

View File

@ -1,5 +1,6 @@
from django.conf import settings
from rest_framework import permissions
from files.methods import is_mediacms_editor, is_mediacms_manager

View File

@ -1,4 +1,5 @@
import os
from celery.schedules import crontab
DEBUG = False
@ -17,7 +18,7 @@ CAN_ADD_MEDIA = "all"
PORTAL_WORKFLOW = "public"
# valid values: 'light', 'dark'.
DEFAULT_THEME = "light"
DEFAULT_THEME = "light"
# These are passed on every request
@ -213,9 +214,7 @@ POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY = ""
CANNOT_ADD_MEDIA_MESSAGE = ""
# mp4hls command, part of Bendo4
MP4HLS_COMMAND = (
"/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls"
)
MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls"
# highly experimental, related with remote workers
ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"

View File

@ -1,7 +1,7 @@
import debug_toolbar
from django.conf.urls import include, url
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
import debug_toolbar
urlpatterns = [
url(r"^__debug__/", include(debug_toolbar.urls)),

View File

@ -29,8 +29,6 @@ CACHES = {
BROKER_URL = REDIS_LOCATION
CELERY_RESULT_BACKEND = BROKER_URL
MP4HLS_COMMAND = (
"/home/mediacms.io/bento4/bin/mp4hls"
)
MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls"
DEBUG = False

View File

@ -1,14 +1,14 @@
from django.contrib import admin
from .models import (
Media,
Encoding,
EncodeProfile,
Category,
Comment,
Tag,
EncodeProfile,
Encoding,
Language,
Media,
Subtitle,
Tag,
)

View File

@ -1,9 +1,9 @@
# ffmpeg only backend
from subprocess import PIPE, Popen
import locale
import re
import logging
import re
from subprocess import PIPE, Popen
logger = logging.getLogger(__name__)

View File

@ -1,4 +1,5 @@
from django.conf import settings
from .methods import is_mediacms_editor, is_mediacms_manager
@ -19,18 +20,12 @@ def stuff(request):
ret["UPLOAD_MAX_SIZE"] = settings.UPLOAD_MAX_SIZE
ret["UPLOAD_MAX_FILES_NUMBER"] = settings.UPLOAD_MAX_FILES_NUMBER
ret["PRE_UPLOAD_MEDIA_MESSAGE"] = settings.PRE_UPLOAD_MEDIA_MESSAGE
ret[
"POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY"
] = settings.POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY
ret["POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY"] = settings.POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY
ret["IS_MEDIACMS_ADMIN"] = request.user.is_superuser
ret["IS_MEDIACMS_EDITOR"] = is_mediacms_editor(request.user)
ret["IS_MEDIACMS_MANAGER"] = is_mediacms_manager(request.user)
ret["ALLOW_RATINGS"] = settings.ALLOW_RATINGS
ret[
"ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY"
] = settings.ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY
ret[
"VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE"
] = settings.VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE
ret["ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY"] = settings.ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY
ret["VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE"] = settings.VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE
ret["RSS_URL"] = "/rss"
return ret

View File

@ -1,12 +1,12 @@
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Rss201rev2Feed
from django.urls import reverse
from django.db.models import Q
from django.conf import settings
from django.contrib.postgres.search import SearchQuery
from django.contrib.syndication.views import Feed
from django.db.models import Q
from django.urls import reverse
from django.utils.feedgenerator import Rss201rev2Feed
from .models import Media, Category
from . import helpers
from .models import Category, Media
from .stop_words import STOP_WORDS
@ -119,11 +119,7 @@ class SearchRSSFeed(Feed):
elif query:
# same as on files.views.MediaSearch: move this processing to a prepare_query function
query = helpers.clean_query(query)
q_parts = [
q_part.rstrip("y")
for q_part in query.split()
if q_part not in STOP_WORDS
]
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:]:

View File

@ -1,6 +1,7 @@
from django import forms
from .methods import get_next_state, is_mediacms_editor
from .models import Media, Subtitle
from .methods import is_mediacms_editor, get_next_state
class MultipleSelect(forms.CheckboxSelectMultiple):
@ -8,9 +9,7 @@ class MultipleSelect(forms.CheckboxSelectMultiple):
class MediaForm(forms.ModelForm):
new_tags = forms.CharField(
label="Tags", help_text="a comma separated list of new tags.", required=False
)
new_tags = forms.CharField(label="Tags", help_text="a comma separated list of new tags.", required=False)
class Meta:
model = Media
@ -27,7 +26,7 @@ class MediaForm(forms.ModelForm):
"thumbnail_time",
"reported_times",
"is_reviewed",
"allow_download"
"allow_download",
)
widgets = {
"tags": MultipleSelect(),
@ -42,9 +41,7 @@ class MediaForm(forms.ModelForm):
self.fields.pop("featured")
self.fields.pop("reported_times")
self.fields.pop("is_reviewed")
self.fields["new_tags"].initial = ", ".join(
[tag.title for tag in self.instance.tags.all()]
)
self.fields["new_tags"].initial = ", ".join([tag.title for tag in self.instance.tags.all()])
def clean_uploaded_poster(self):
image = self.cleaned_data.get("uploaded_poster", False)
@ -57,9 +54,7 @@ class MediaForm(forms.ModelForm):
data = self.cleaned_data
state = data.get("state")
if state != self.initial["state"]:
self.instance.state = get_next_state(
self.user, self.initial["state"], self.instance.state
)
self.instance.state = get_next_state(self.user, self.initial["state"], self.instance.state)
media = super(MediaForm, self).save(*args, **kwargs)
return media

View File

@ -1,19 +1,19 @@
# Kudos to Werner Robitza, AVEQ GmbH, for helping with ffmpeg
# related content
import os
import math
import shutil
import tempfile
import random
import hashlib
import subprocess
import json
import math
import os
import random
import shutil
import subprocess
import tempfile
from fractions import Fraction
import filetype
from django.conf import settings
CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
CRF_ENCODING_NUM_SECONDS = 2 # 0 * 60 # videos with greater duration will get
@ -168,9 +168,7 @@ def rm_dir(directory):
def url_from_path(filename):
# TODO: find a way to preserver http - https ...
return "{0}{1}".format(
settings.MEDIA_URL, filename.replace(settings.MEDIA_ROOT, "")
)
return "{0}{1}".format(settings.MEDIA_URL, filename.replace(settings.MEDIA_ROOT, ""))
def create_temp_file(suffix=None, dir=settings.TEMP_DIRECTORY):
@ -210,9 +208,7 @@ def run_command(cmd, cwd=None):
cmd = cmd.split()
ret = {}
if cwd:
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd
)
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
else:
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
@ -331,9 +327,7 @@ def media_file_info(input_file):
except ValueError:
hms, msec = duration_str.split(",")
total_dur = sum(
int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":")))
)
total_dur = sum(int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":"))))
video_duration = total_dur + float("0." + msec)
else:
# fallback to format, eg for webm
@ -370,7 +364,7 @@ def media_file_info(input_file):
input_file,
]
stdout = run_command(cmd).get("out")
stream_size = sum([int(l) for l in stdout.split("\n") if l != ""])
stream_size = sum([int(line) for line in stdout.split("\n") if line != ""])
video_bitrate = round((stream_size * 8 / 1024.0) / video_duration, 2)
ret = {
@ -396,9 +390,7 @@ def media_file_info(input_file):
hms, msec = duration_str.split(".")
except ValueError:
hms, msec = duration_str.split(",")
total_dur = sum(
int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":")))
)
total_dur = sum(int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":"))))
audio_duration = total_dur + float("0." + msec)
else:
# fallback to format, eg for webm
@ -432,7 +424,7 @@ def media_file_info(input_file):
input_file,
]
stdout = run_command(cmd).get("out")
stream_size = sum([int(l) for l in stdout.split("\n") if l != ""])
stream_size = sum([int(line) for line in stdout.split("\n") if line != ""])
audio_bitrate = round((stream_size * 8 / 1024.0) / audio_duration, 2)
ret.update(
@ -660,9 +652,7 @@ def get_base_ffmpeg_command(
return cmd
def produce_ffmpeg_commands(
media_file, media_info, resolution, codec, output_filename, pass_file, chunk=False
):
def produce_ffmpeg_commands(media_file, media_info, resolution, codec, output_filename, pass_file, chunk=False):
try:
media_info = json.loads(media_info)
except BaseException:
@ -699,9 +689,7 @@ def produce_ffmpeg_commands(
# else:
# adjust the target frame rate if the input is fractional
target_fps = (
src_framerate if isinstance(src_framerate, int) else math.ceil(src_framerate)
)
target_fps = src_framerate if isinstance(src_framerate, int) else math.ceil(src_framerate)
if media_info.get("video_duration") > CRF_ENCODING_NUM_SECONDS:
enc_type = "crf"

View File

@ -1,16 +1,16 @@
from rest_framework.views import APIView
from rest_framework.parsers import JSONParser
from rest_framework.settings import api_settings
from rest_framework.response import Response
from rest_framework import status
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from users.models import User
from users.serializers import UserSerializer
from .permissions import IsMediacmsEditor
from .models import Media, Comment
from .methods import is_mediacms_manager
from .serializers import MediaSerializer, CommentSerializer
from .methods import is_mediacms_manager
from .models import Comment, Media
from .permissions import IsMediacmsEditor
from .serializers import CommentSerializer, MediaSerializer
class MediaList(APIView):
@ -189,9 +189,7 @@ class UserList(APIView):
def delete(self, request, format=None):
if not is_mediacms_manager(request.user):
return Response(
{"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
tokens = request.GET.get("tokens")
if tokens:

View File

@ -1,15 +1,17 @@
# Kudos to Werner Robitza, AVEQ GmbH, for helping with ffmpeg
# related content
import itertools
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 django.db.models import Q
from cms import celery_app
from . import models
from .helpers import mask_ip
@ -48,9 +50,7 @@ def pre_save_action(media, user, session_key, action, remote_ip):
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 = MediaAction.objects.filter(media=media, action=action, session_key=session_key)
query = query.order_by("-action_date")
if query:
@ -71,11 +71,7 @@ def pre_save_action(media, user, session_key, action, remote_ip):
# 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")
)
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)
@ -204,11 +200,8 @@ URL: %s
d["to"] = [media.user.email]
notify_items.append(d)
for item in notify_items:
email = EmailMessage(
item["title"], item["msg"], settings.DEFAULT_FROM_EMAIL, item["to"]
)
email = EmailMessage(item["title"], item["msg"], settings.DEFAULT_FROM_EMAIL, item["to"])
email.send(fail_silently=True)
return True
@ -222,17 +215,9 @@ def show_recommended_media(request, limit=100):
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]
)
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]
)
media = list(models.Media.objects.filter(basic_query).order_by("-views", "-likes").prefetch_related("user")[:limit])
random.shuffle(media)
return media
@ -257,11 +242,7 @@ def show_related_media_content(media, request, limit):
# 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]
)
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
@ -282,20 +263,12 @@ def show_related_media_content(media, request, 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]
)
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]
)
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
@ -313,11 +286,7 @@ 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]
)
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
@ -347,13 +316,7 @@ 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()
)
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
@ -379,9 +342,7 @@ View it on %s
media.title,
media_url,
)
email = EmailMessage(
title, msg, settings.DEFAULT_FROM_EMAIL, [media.user.email]
)
email = EmailMessage(title, msg, settings.DEFAULT_FROM_EMAIL, [media.user.email])
email.send(fail_silently=True)
return True
@ -420,27 +381,17 @@ def list_tasks():
friendly_token = task_args.split()[0]
profile_id = task_args.split()[1]
media = models.Media.objects.filter(
friendly_token=friendly_token
).first()
media = models.Media.objects.filter(friendly_token=friendly_token).first()
if media:
profile = models.EncodeProfile.objects.filter(
id=profile_id
).first()
profile = models.EncodeProfile.objects.filter(id=profile_id).first()
if profile:
media_profile_pairs.append(
(media.friendly_token, profile.id)
)
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()
encoding = models.Encoding.objects.filter(task_id=task.get("id")).first()
if encoding:
task_dict["info"][
"encoding progress"
] = encoding.progress
task_dict["info"]["encoding progress"] = encoding.progress
ret[state]["tasks"].append(task_dict)
ret["task_ids"] = task_ids

View File

@ -1,11 +1,13 @@
# Generated by Django 3.1.4 on 2020-12-01 07:12
import django.contrib.postgres.search
from django.db import migrations, models
import files.models
import imagekit.models.fields
import uuid
import django.contrib.postgres.search
import imagekit.models.fields
from django.db import migrations, models
import files.models
class Migration(migrations.Migration):
@ -32,9 +34,7 @@ class Migration(migrations.Migration):
("description", models.TextField(blank=True)),
(
"is_global",
models.BooleanField(
default=False, help_text="global categories or user specific"
),
models.BooleanField(default=False, help_text="global categories or user specific"),
),
(
"media_count",
@ -42,9 +42,7 @@ class Migration(migrations.Migration):
),
(
"thumbnail",
imagekit.models.fields.ProcessedImageField(
blank=True, upload_to=files.models.category_thumb_path
),
imagekit.models.fields.ProcessedImageField(blank=True, upload_to=files.models.category_thumb_path),
),
(
"listings_thumbnail",
@ -153,9 +151,7 @@ class Migration(migrations.Migration):
("commands", models.TextField(blank=True, help_text="commands run")),
(
"chunk",
models.BooleanField(
db_index=True, default=False, help_text="is chunk?"
),
models.BooleanField(db_index=True, default=False, help_text="is chunk?"),
),
("chunk_file_path", models.CharField(blank=True, max_length=400)),
("chunks_info", models.TextField(blank=True)),
@ -317,9 +313,7 @@ class Migration(migrations.Migration):
("likes", models.IntegerField(db_index=True, default=1)),
(
"listable",
models.BooleanField(
default=False, help_text="Whether it will appear on listings"
),
models.BooleanField(default=False, help_text="Whether it will appear on listings"),
),
(
"md5sum",
@ -341,9 +335,7 @@ class Migration(migrations.Migration):
),
(
"media_info",
models.TextField(
blank=True, help_text="extracted media metadata info"
),
models.TextField(blank=True, help_text="extracted media metadata info"),
),
(
"media_type",
@ -387,9 +379,7 @@ class Migration(migrations.Migration):
),
(
"reported_times",
models.IntegerField(
default=0, help_text="how many time a Medis is reported"
),
models.IntegerField(default=0, help_text="how many time a Medis is reported"),
),
(
"search",
@ -485,9 +475,7 @@ class Migration(migrations.Migration):
),
(
"user_featured",
models.BooleanField(
default=False, help_text="Featured by the user"
),
models.BooleanField(default=False, help_text="Featured by the user"),
),
("video_height", models.IntegerField(default=1)),
("views", models.IntegerField(db_index=True, default=1)),

View File

@ -1,10 +1,10 @@
# Generated by Django 3.1.4 on 2020-12-01 07:12
from django.conf import settings
import django.contrib.postgres.indexes
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -31,9 +31,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="subtitle",
name="language",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="files.language"
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="files.language"),
),
migrations.AddField(
model_name="subtitle",
@ -47,9 +45,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="subtitle",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name="rating",
@ -63,37 +59,27 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="rating",
name="rating_category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="files.ratingcategory"
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="files.ratingcategory"),
),
migrations.AddField(
model_name="rating",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name="playlistmedia",
name="media",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="files.media"
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="files.media"),
),
migrations.AddField(
model_name="playlistmedia",
name="playlist",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="files.playlist"
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="files.playlist"),
),
migrations.AddField(
model_name="playlist",
name="media",
field=models.ManyToManyField(
blank=True, through="files.PlaylistMedia", to="files.Media"
),
field=models.ManyToManyField(blank=True, through="files.PlaylistMedia", to="files.Media"),
),
migrations.AddField(
model_name="playlist",
@ -173,9 +159,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="encoding",
name="profile",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="files.encodeprofile"
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="files.encodeprofile"),
),
migrations.AddField(
model_name="comment",
@ -200,9 +184,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="comment",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name="category",
@ -216,9 +198,7 @@ class Migration(migrations.Migration):
),
migrations.AddIndex(
model_name="rating",
index=models.Index(
fields=["user", "media"], name="files_ratin_user_id_72ca6a_idx"
),
index=models.Index(fields=["user", "media"], name="files_ratin_user_id_72ca6a_idx"),
),
migrations.AlterUniqueTogether(
name="rating",
@ -226,8 +206,6 @@ class Migration(migrations.Migration):
),
migrations.AddIndex(
model_name="media",
index=django.contrib.postgres.indexes.GinIndex(
fields=["search"], name="files_media_search_7194c6_gin"
),
index=django.contrib.postgres.indexes.GinIndex(fields=["search"], name="files_media_search_7194c6_gin"),
),
]

View File

@ -1,28 +1,27 @@
import json
import logging
import uuid
import os
import random
import re
import tempfile
import random
import json
import uuid
import m3u8
from django.utils import timezone
from django.db import connection
from django.db import models
from django.template.defaultfilters import slugify
from django.conf import settings
from django.contrib.postgres.indexes import GinIndex
from django.db.models.signals import pre_delete, post_delete, post_save, m2m_changed
from django.core.files import File
from django.core.exceptions import ValidationError
from django.dispatch import receiver
from django.urls import reverse
from django.utils.html import strip_tags
from django.contrib.postgres.search import SearchVectorField
from mptt.models import MPTTModel, TreeForeignKey
from imagekit.processors import ResizeToFit
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import connection, models
from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete
from django.dispatch import receiver
from django.template.defaultfilters import slugify
from django.urls import reverse
from django.utils import timezone
from django.utils.html import strip_tags
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFit
from mptt.models import MPTTModel, TreeForeignKey
from . import helpers
from .methods import notify_users
@ -88,36 +87,26 @@ ENCODE_RESOLUTIONS_KEYS = [resolution for resolution, name in ENCODE_RESOLUTIONS
def original_media_file_path(instance, filename):
"""Helper function to place original media file"""
file_name = "{0}.{1}".format(instance.uid.hex, helpers.get_file_name(filename))
return settings.MEDIA_UPLOAD_DIR + "user/{0}/{1}".format(
instance.user.username, file_name
)
return settings.MEDIA_UPLOAD_DIR + "user/{0}/{1}".format(instance.user.username, file_name)
def encoding_media_file_path(instance, filename):
"""Helper function to place encoded media file"""
file_name = "{0}.{1}".format(
instance.media.uid.hex, helpers.get_file_name(filename)
)
return settings.MEDIA_ENCODING_DIR + "{0}/{1}/{2}".format(
instance.profile.id, instance.media.user.username, file_name
)
file_name = "{0}.{1}".format(instance.media.uid.hex, helpers.get_file_name(filename))
return settings.MEDIA_ENCODING_DIR + "{0}/{1}/{2}".format(instance.profile.id, instance.media.user.username, file_name)
def original_thumbnail_file_path(instance, filename):
"""Helper function to place original media thumbnail file"""
return settings.THUMBNAIL_UPLOAD_DIR + "user/{0}/{1}".format(
instance.user.username, filename
)
return settings.THUMBNAIL_UPLOAD_DIR + "user/{0}/{1}".format(instance.user.username, filename)
def subtitles_file_path(instance, filename):
"""Helper function to place subtitle file"""
return settings.SUBTITLES_UPLOAD_DIR + "user/{0}/{1}".format(
instance.media.user.username, filename
)
return settings.SUBTITLES_UPLOAD_DIR + "user/{0}/{1}".format(instance.media.user.username, filename)
def category_thumb_path(instance, filename):
@ -130,17 +119,11 @@ def category_thumb_path(instance, filename):
class Media(models.Model):
"""The most important model for MediaCMS"""
add_date = models.DateTimeField(
"Date produced", blank=True, null=True, db_index=True
)
add_date = models.DateTimeField("Date produced", blank=True, null=True, db_index=True)
allow_download = models.BooleanField(
default=True, help_text="Whether option to download media is shown"
)
allow_download = models.BooleanField(default=True, help_text="Whether option to download media is shown")
category = models.ManyToManyField(
"Category", blank=True, help_text="Media can be part of one or more categories"
)
category = models.ManyToManyField("Category", blank=True, help_text="Media can be part of one or more categories")
channel = models.ForeignKey(
"users.Channel",
@ -158,13 +141,9 @@ class Media(models.Model):
edit_date = models.DateTimeField(auto_now=True)
enable_comments = models.BooleanField(
default=True, help_text="Whether comments will be allowed for this media"
)
enable_comments = models.BooleanField(default=True, help_text="Whether comments will be allowed for this media")
encoding_status = models.CharField(
max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending", db_index=True
)
encoding_status = models.CharField(max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending", db_index=True)
featured = models.BooleanField(
default=False,
@ -172,13 +151,9 @@ class Media(models.Model):
help_text="Whether media is globally featured by a MediaCMS editor",
)
friendly_token = models.CharField(
blank=True, max_length=12, db_index=True, help_text="Identifier for the Media"
)
friendly_token = models.CharField(blank=True, max_length=12, db_index=True, help_text="Identifier for the Media")
hls_file = models.CharField(
max_length=1000, blank=True, help_text="Path to HLS file for videos"
)
hls_file = models.CharField(max_length=1000, blank=True, help_text="Path to HLS file for videos")
is_reviewed = models.BooleanField(
default=settings.MEDIA_IS_REVIEWED,
@ -186,19 +161,13 @@ class Media(models.Model):
help_text="Whether media is reviewed, so it can appear on public listings",
)
license = models.ForeignKey(
"License", on_delete=models.CASCADE, db_index=True, blank=True, null=True
)
license = models.ForeignKey("License", on_delete=models.CASCADE, db_index=True, blank=True, null=True)
likes = models.IntegerField(db_index=True, default=1)
listable = models.BooleanField(
default=False, help_text="Whether it will appear on listings"
)
listable = models.BooleanField(default=False, help_text="Whether it will appear on listings")
md5sum = models.CharField(
max_length=50, blank=True, null=True, help_text="Not exposed, used internally"
)
md5sum = models.CharField(max_length=50, blank=True, null=True, help_text="Not exposed, used internally")
media_file = models.FileField(
"media file",
@ -217,9 +186,7 @@ class Media(models.Model):
default="video",
)
password = models.CharField(
max_length=100, blank=True, help_text="password for private media"
)
password = models.CharField(max_length=100, blank=True, help_text="password for private media")
preview_file_path = models.CharField(
max_length=500,
@ -243,9 +210,7 @@ class Media(models.Model):
help_text="Rating category, if media Rating is allowed",
)
reported_times = models.IntegerField(
default=0, help_text="how many time a Medis is reported"
)
reported_times = models.IntegerField(default=0, help_text="how many time a Medis is reported")
search = SearchVectorField(
null=True,
@ -274,13 +239,9 @@ class Media(models.Model):
help_text="state of Media",
)
tags = models.ManyToManyField(
"Tag", blank=True, help_text="select one or more out of the existing tags"
)
tags = models.ManyToManyField("Tag", blank=True, help_text="select one or more out of the existing tags")
title = models.CharField(
max_length=100, help_text="media title", blank=True, db_index=True
)
title = models.CharField(max_length=100, help_text="media title", blank=True, db_index=True)
thumbnail = ProcessedImageField(
upload_to=original_thumbnail_file_path,
@ -292,13 +253,9 @@ class Media(models.Model):
help_text="media extracted small thumbnail, shown on listings",
)
thumbnail_time = models.FloatField(
blank=True, null=True, help_text="Time on video that a thumbnail will be taken"
)
thumbnail_time = models.FloatField(blank=True, null=True, help_text="Time on video that a thumbnail will be taken")
uid = models.UUIDField(
unique=True, default=uuid.uuid4, help_text="A unique identifier for the Media"
)
uid = models.UUIDField(unique=True, default=uuid.uuid4, help_text="A unique identifier for the Media")
uploaded_thumbnail = ProcessedImageField(
upload_to=original_thumbnail_file_path,
@ -321,9 +278,7 @@ class Media(models.Model):
max_length=500,
)
user = models.ForeignKey(
"users.User", on_delete=models.CASCADE, help_text="user that uploads the media"
)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, help_text="user that uploads the media")
user_featured = models.BooleanField(default=False, help_text="Featured by the user")
@ -406,11 +361,7 @@ class Media(models.Model):
self.state = helpers.get_default_state(user=self.user)
# condition to appear on listings
if (
self.state == "public"
and self.encoding_status == "success"
and self.is_reviewed == True
):
if self.state == "public" and self.encoding_status == "success" and self.is_reviewed is True:
self.listable = True
else:
self.listable = False
@ -419,10 +370,7 @@ class Media(models.Model):
# produce a thumbnail out of an uploaded poster
# will run only when a poster is uploaded for the first time
if (
self.uploaded_poster
and self.uploaded_poster != self.__original_uploaded_poster
):
if self.uploaded_poster and self.uploaded_poster != self.__original_uploaded_poster:
with open(self.uploaded_poster.path, "rb") as f:
# set this otherwise gets to infinite loop
@ -458,9 +406,7 @@ class Media(models.Model):
]
items = [item for item in items if item]
text = " ".join(items)
text = " ".join(
[token for token in text.lower().split(" ") if token not in STOP_WORDS]
)
text = " ".join([token for token in text.lower().split(" ") if token not in STOP_WORDS])
sql_code = """
UPDATE {db_table} SET search = to_tsvector(
@ -561,9 +507,7 @@ class Media(models.Model):
if self.media_type == "image":
with open(self.media_file.path, "rb") as f:
myfile = File(f)
thumbnail_name = (
helpers.get_file_name(self.media_file.path) + ".jpg"
)
thumbnail_name = helpers.get_file_name(self.media_file.path) + ".jpg"
self.thumbnail.save(content=myfile, name=thumbnail_name)
self.poster.save(content=myfile, name=thumbnail_name)
return True
@ -585,9 +529,7 @@ class Media(models.Model):
command = [
settings.FFMPEG_COMMAND,
"-ss",
str(
thumbnail_time
), # -ss need to be firt here otherwise time taken is huge
str(thumbnail_time), # -ss need to be firt here otherwise time taken is huge
"-i",
self.media_file.path,
"-vframes",
@ -650,10 +592,7 @@ class Media(models.Model):
for profile in profiles:
if profile.extension != "gif":
if self.video_height and self.video_height < profile.resolution:
if (
profile.resolution
not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE
):
if profile.resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE:
continue
encoding = Encoding(media=self, profile=profile)
encoding.save()
@ -688,12 +627,7 @@ class Media(models.Model):
self.save(update_fields=["encoding_status", "listable"])
if (
encoding
and encoding.status == "success"
and encoding.profile.codec == "h264"
and action == "add"
):
if encoding and encoding.status == "success" and encoding.profile.codec == "h264" and action == "add":
from . import tasks
tasks.create_hls(self.friendly_token)
@ -704,10 +638,7 @@ class Media(models.Model):
"""Set encoding_status for videos
Set success if at least one mp4 exists
"""
mp4_statuses = set(
encoding.status
for encoding in self.encodings.filter(profile__extension="mp4", chunk=False)
)
mp4_statuses = set(encoding.status for encoding in self.encodings.filter(profile__extension="mp4", chunk=False))
if not mp4_statuses:
encoding_status = "pending"
@ -752,12 +683,8 @@ class Media(models.Model):
extra.append(encoding.profile.codec)
for codec in extra:
ret[resolution][codec] = {}
v = self.encodings.filter(chunk=True, profile__codec=codec).values(
"progress"
)
ret[resolution][codec]["progress"] = (
sum([p["progress"] for p in v]) / v.count()
)
v = self.encodings.filter(chunk=True, profile__codec=codec).values("progress")
ret[resolution][codec]["progress"] = sum([p["progress"] for p in v]) / v.count()
# TODO; status/logs/errors
return ret
@ -897,19 +824,13 @@ class Media(models.Model):
for iframe_playlist in m3u8_obj.iframe_playlists:
uri = os.path.join(p, iframe_playlist.uri)
if os.path.exists(uri):
resolution = iframe_playlist.iframe_stream_info.resolution[
1
]
res["{}_iframe".format(resolution)] = helpers.url_from_path(
uri
)
resolution = iframe_playlist.iframe_stream_info.resolution[1]
res["{}_iframe".format(resolution)] = helpers.url_from_path(uri)
for playlist in m3u8_obj.playlists:
uri = os.path.join(p, playlist.uri)
if os.path.exists(uri):
resolution = playlist.stream_info.resolution[1]
res[
"{}_playlist".format(resolution)
] = helpers.url_from_path(uri)
res["{}_playlist".format(resolution)] = helpers.url_from_path(uri)
return res
@property
@ -930,9 +851,7 @@ class Media(models.Model):
if edit:
return reverse("edit_media") + "?m={0}".format(self.friendly_token)
if api:
return reverse(
"api_get_media", kwargs={"friendly_token": self.friendly_token}
)
return reverse("api_get_media", kwargs={"friendly_token": self.friendly_token})
else:
return reverse("get_media") + "?m={0}".format(self.friendly_token)
@ -988,13 +907,9 @@ class Category(models.Model):
description = models.TextField(blank=True)
user = models.ForeignKey(
"users.User", on_delete=models.CASCADE, blank=True, null=True
)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, blank=True, null=True)
is_global = models.BooleanField(
default=False, help_text="global categories or user specific"
)
is_global = models.BooleanField(default=False, help_text="global categories or user specific")
media_count = models.IntegerField(default=0, help_text="number of media")
@ -1006,9 +921,7 @@ class Category(models.Model):
blank=True,
)
listings_thumbnail = models.CharField(
max_length=400, blank=True, null=True, help_text="Thumbnail to show on listings"
)
listings_thumbnail = models.CharField(max_length=400, blank=True, null=True, help_text="Thumbnail to show on listings")
def __str__(self):
return self.title
@ -1039,11 +952,7 @@ class Category(models.Model):
if self.thumbnail:
return helpers.url_from_path(self.thumbnail.path)
media = (
Media.objects.filter(category=self, state="public")
.order_by("-views")
.first()
)
media = Media.objects.filter(category=self, state="public").order_by("-views").first()
if media:
return media.thumbnail_url
@ -1061,9 +970,7 @@ class Tag(models.Model):
title = models.CharField(max_length=100, unique=True, db_index=True)
user = models.ForeignKey(
"users.User", on_delete=models.CASCADE, blank=True, null=True
)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, blank=True, null=True)
media_count = models.IntegerField(default=0, help_text="number of media")
@ -1085,9 +992,7 @@ class Tag(models.Model):
return reverse("search") + "?t={0}".format(self.title)
def update_tag_media(self):
self.media_count = Media.objects.filter(
state="public", is_reviewed=True, tags=self
).count()
self.media_count = Media.objects.filter(state="public", is_reviewed=True, tags=self).count()
self.save(update_fields=["media_count"])
return True
@ -1102,9 +1007,7 @@ class Tag(models.Model):
def thumbnail_url(self):
if self.listings_thumbnail:
return self.listings_thumbnail
media = (
Media.objects.filter(tags=self, state="public").order_by("-views").first()
)
media = Media.objects.filter(tags=self, state="public").order_by("-views").first()
if media:
return media.thumbnail_url
@ -1154,9 +1057,7 @@ class Encoding(models.Model):
media = models.ForeignKey(Media, on_delete=models.CASCADE, related_name="encodings")
media_file = models.FileField(
"encoding file", upload_to=encoding_media_file_path, blank=True, max_length=500
)
media_file = models.FileField("encoding file", upload_to=encoding_media_file_path, blank=True, max_length=500)
profile = models.ForeignKey(EncodeProfile, on_delete=models.CASCADE)
@ -1168,9 +1069,7 @@ class Encoding(models.Model):
size = models.CharField(max_length=20, blank=True)
status = models.CharField(
max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending"
)
status = models.CharField(max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending")
temp_file = models.CharField(max_length=400, blank=True)
@ -1305,9 +1204,7 @@ class Rating(models.Model):
unique_together = ("user", "media", "rating_category")
def __str__(self):
return "{0}, rate for {1} for category {2}".format(
self.user.username, self.media.title, self.rating_category.title
)
return "{0}, rate for {1} for category {2}".format(self.user.username, self.media.title, self.rating_category.title)
class Playlist(models.Model):
@ -1325,9 +1222,7 @@ class Playlist(models.Model):
uid = models.UUIDField(unique=True, default=uuid.uuid4)
user = models.ForeignKey(
"users.User", on_delete=models.CASCADE, db_index=True, related_name="playlists"
)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, db_index=True, related_name="playlists")
def __str__(self):
return self.title
@ -1338,13 +1233,9 @@ class Playlist(models.Model):
def get_absolute_url(self, api=False):
if api:
return reverse(
"api_get_playlist", kwargs={"friendly_token": self.friendly_token}
)
return reverse("api_get_playlist", kwargs={"friendly_token": self.friendly_token})
else:
return reverse(
"get_playlist", kwargs={"friendly_token": self.friendly_token}
)
return reverse("get_playlist", kwargs={"friendly_token": self.friendly_token})
@property
def url(self):
@ -1411,13 +1302,9 @@ class Comment(MPTTModel):
add_date = models.DateTimeField(auto_now_add=True)
media = models.ForeignKey(
Media, on_delete=models.CASCADE, db_index=True, related_name="comments"
)
media = models.ForeignKey(Media, on_delete=models.CASCADE, db_index=True, related_name="comments")
parent = TreeForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
)
parent = TreeForeignKey("self", on_delete=models.CASCADE, null=True, blank=True, related_name="children")
text = models.TextField(help_text="text")
@ -1566,13 +1453,9 @@ def encoding_file_save(sender, instance, created, **kwargs):
# concatenate chunks and create final encoding file
chunks_paths = [f.media_file.path for f in chunks]
with tempfile.TemporaryDirectory(
dir=settings.TEMP_DIRECTORY
) as temp_dir:
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
seg_file = helpers.create_temp_file(suffix=".txt", dir=temp_dir)
tf = helpers.create_temp_file(
suffix=".{0}".format(instance.profile.extension), dir=temp_dir
)
tf = helpers.create_temp_file(suffix=".{0}".format(instance.profile.extension), dir=temp_dir)
with open(seg_file, "w") as ff:
for f in chunks_paths:
ff.write("file {}\n".format(f))
@ -1602,9 +1485,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
progress=100,
)
all_logs = "\n".join([st.logs for st in chunks])
encoding.logs = "{0}\n{1}\n{2}".format(
chunks_paths, stdout, all_logs
)
encoding.logs = "{0}\n{1}\n{2}".format(chunks_paths, stdout, all_logs)
workers = list(set([st.worker for st in chunks]))
encoding.worker = json.dumps({"workers": workers})
@ -1635,9 +1516,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
):
# if two chunks are finished at the same time, this
# will be changed
who = Encoding.objects.filter(
media=encoding.media, profile=encoding.profile
).exclude(id=encoding.id)
who = Encoding.objects.filter(media=encoding.media, profile=encoding.profile).exclude(id=encoding.id)
who.delete()
else:
encoding.delete()
@ -1652,13 +1531,9 @@ def encoding_file_save(sender, instance, created, **kwargs):
instance.media.post_encode_actions(encoding=instance, action="add")
elif instance.chunk and instance.status == "fail":
encoding = Encoding(
media=instance.media, profile=instance.profile, status="fail", progress=100
)
encoding = Encoding(media=instance.media, profile=instance.profile, status="fail", progress=100)
chunks = Encoding.objects.filter(
media=instance.media, chunks_info=instance.chunks_info, chunk=True
).order_by("add_date")
chunks = Encoding.objects.filter(media=instance.media, chunks_info=instance.chunks_info, chunk=True).order_by("add_date")
chunks_paths = [f.media_file.path for f in chunks]
@ -1671,9 +1546,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
encoding.total_run_time = (end_date - start_date).seconds
encoding.save()
who = Encoding.objects.filter(
media=encoding.media, profile=encoding.profile
).exclude(id=encoding.id)
who = Encoding.objects.filter(media=encoding.media, profile=encoding.profile).exclude(id=encoding.id)
who.delete()
pass # TODO: merge with above if, do not repeat code
@ -1681,22 +1554,10 @@ def encoding_file_save(sender, instance, created, **kwargs):
if instance.status in ["fail", "success"]:
instance.media.post_encode_actions(encoding=instance, action="add")
encodings = set(
[
encoding.status
for encoding in Encoding.objects.filter(media=instance.media)
]
)
encodings = set([encoding.status for encoding in Encoding.objects.filter(media=instance.media)])
if ("running" in encodings) or ("pending" in encodings):
return
workers = list(
set(
[
encoding.worker
for encoding in Encoding.objects.filter(media=instance.media)
]
)
)
workers = list(set([encoding.worker for encoding in Encoding.objects.filter(media=instance.media)]))
@receiver(post_delete, sender=Encoding)
@ -1712,4 +1573,3 @@ def encoding_file_delete(sender, instance, **kwargs):
instance.media.post_encode_actions(encoding=instance, action="delete")
# delete local chunks, and remote chunks + media file. Only when the
# last encoding of a media is complete

View File

@ -1,4 +1,5 @@
from rest_framework import permissions
from .methods import is_mediacms_editor

View File

@ -1,6 +1,6 @@
from rest_framework import serializers
from .models import Media, EncodeProfile, Playlist, Comment, Category, Tag
from .models import Category, Comment, EncodeProfile, Media, Playlist, Tag
# TODO: put them in a more DRY way
@ -18,9 +18,7 @@ class MediaSerializer(serializers.ModelSerializer):
return self.context["request"].build_absolute_uri(obj.get_absolute_url())
def get_api_url(self, obj):
return self.context["request"].build_absolute_uri(
obj.get_absolute_url(api=True)
)
return self.context["request"].build_absolute_uri(obj.get_absolute_url(api=True))
def get_thumbnail_url(self, obj):
if obj.thumbnail_url:
@ -210,16 +208,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
read_only_fields = ("add_date", "user")
fields = (
"add_date",
"title",
"description",
"user",
"media_count",
"url",
"api_url",
"thumbnail_url"
)
fields = ("add_date", "title", "description", "user", "media_count", "url", "api_url", "thumbnail_url")
class PlaylistDetailSerializer(serializers.ModelSerializer):
@ -228,16 +217,7 @@ class PlaylistDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
read_only_fields = ("add_date", "user")
fields = (
"title",
"add_date",
"user_thumbnail_url",
"description",
"user",
"media_count",
"url",
"thumbnail_url"
)
fields = ("title", "add_date", "user_thumbnail_url", "description", "user", "media_count", "url", "thumbnail_url")
class CommentSerializer(serializers.ModelSerializer):

View File

@ -1,41 +1,40 @@
import re
import os
import json
import subprocess
from datetime import datetime, timedelta
import tempfile
import os
import re
import shutil
from django.core.cache import cache
from django.conf import settings
from django.core.files import File
from django.db.models import Q
import subprocess
import tempfile
from datetime import datetime, timedelta
from celery import Task
from celery.decorators import task
from celery.utils.log import get_task_logger
from celery.exceptions import SoftTimeLimitExceeded
from celery.task.control import revoke
from celery.signals import task_revoked
from celery.task.control import revoke
from celery.utils.log import get_task_logger
from django.conf import settings
from django.core.cache import cache
from django.core.files import File
from django.db.models import Q
from actions.models import USER_MEDIA_ACTIONS, MediaAction
from users.models import User
from .backends import FFmpegBackend
from .exceptions import VideoEncodingError
from .helpers import (
calculate_seconds,
rm_file,
create_temp_file,
get_file_name,
get_file_type,
media_file_info,
run_command,
produce_ffmpeg_commands,
produce_friendly_token,
rm_file,
run_command,
)
from actions.models import MediaAction, USER_MEDIA_ACTIONS
from users.models import User
from .models import Encoding, EncodeProfile, Media, Category, Rating, Tag
from .methods import list_tasks, pre_save_action, notify_users
from .methods import list_tasks, notify_users, pre_save_action
from .models import Category, EncodeProfile, Encoding, Media, Rating, Tag
logger = get_task_logger(__name__)
@ -83,10 +82,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
chunks.append(ch[0])
if not chunks:
# command completely failed to segment file.putting to normal encode
logger.info(
"Failed to break file {0} in chunks."
" Putting to normal encode queue".format(friendly_token)
)
logger.info("Failed to break file {0} in chunks." " Putting to normal encode queue".format(friendly_token))
for profile in profiles:
if media.video_height and media.video_height < profile.resolution:
if profile.resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE:
@ -94,9 +90,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
encoding = Encoding(media=media, profile=profile)
encoding.save()
enc_url = settings.SSL_FRONTEND_HOST + encoding.get_absolute_url()
encode_media.delay(
friendly_token, profile.id, encoding.id, enc_url, force=force
)
encode_media.delay(friendly_token, profile.id, encoding.id, enc_url, force=force)
return False
chunks = [os.path.join(cwd, ch) for ch in chunks]
@ -137,11 +131,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
priority=priority,
)
logger.info(
"got {0} chunks and will encode to {1} profiles".format(
len(chunks), to_profiles
)
)
logger.info("got {0} chunks and will encode to {1} profiles".format(len(chunks), to_profiles))
return True
@ -180,11 +170,7 @@ def encode_media(
):
"""Encode a media to given profile, using ffmpeg, storing progress"""
logger.info(
"Encode Media started, friendly token {0}, profile id {1}, force {2}".format(
friendly_token, profile_id, force
)
)
logger.info("Encode Media started, friendly token {0}, profile id {1}, force {2}".format(friendly_token, profile_id, force))
if self.request.id:
task_id = self.request.id
@ -202,13 +188,7 @@ def encode_media(
# TODO: in case a video is chunkized and this enters here many times
# it will always run since chunk_file_path is always different
# thus find a better way for this check
if (
Encoding.objects.filter(
media=media, profile=profile, chunk_file_path=chunk_file_path
).count()
> 1
and force == False
):
if Encoding.objects.filter(media=media, profile=profile, chunk_file_path=chunk_file_path).count() > 1 and force is False:
Encoding.objects.filter(id=encoding_id).delete()
return False
else:
@ -230,19 +210,14 @@ def encode_media(
chunk_file_path=chunk_file_path,
)
else:
if (
Encoding.objects.filter(media=media, profile=profile).count() > 1
and force is False
):
if Encoding.objects.filter(media=media, profile=profile).count() > 1 and force is False:
Encoding.objects.filter(id=encoding_id).delete()
return False
else:
try:
encoding = Encoding.objects.get(id=encoding_id)
encoding.status = "running"
Encoding.objects.filter(media=media, profile=profile).exclude(
id=encoding_id
).delete()
Encoding.objects.filter(media=media, profile=profile).exclude(id=encoding_id).delete()
except BaseException:
encoding = Encoding(media=media, profile=profile, status="running")
@ -287,7 +262,7 @@ def encode_media(
else:
original_media_path = media.media_file.path
#if not media.duration:
# if not media.duration:
# encoding.status = "fail"
# encoding.save(update_fields=["status"])
# return False
@ -337,9 +312,7 @@ def encode_media(
if n_times % 60 == 0:
encoding.progress = percent
try:
encoding.save(
update_fields=["progress", "update_date"]
)
encoding.save(update_fields=["progress", "update_date"])
logger.info("Saved {0}".format(round(percent, 2)))
except BaseException:
pass
@ -383,18 +356,12 @@ def encode_media(
with open(tf, "rb") as f:
myfile = File(f)
output_name = "{0}.{1}".format(
get_file_name(original_media_path), profile.extension
)
output_name = "{0}.{1}".format(get_file_name(original_media_path), profile.extension)
encoding.media_file.save(content=myfile, name=output_name)
encoding.total_run_time = (
encoding.update_date - encoding.add_date
).seconds
encoding.total_run_time = (encoding.update_date - encoding.add_date).seconds
try:
encoding.save(
update_fields=["status", "logs", "progress", "total_run_time"]
)
encoding.save(update_fields=["status", "logs", "progress", "total_run_time"])
# this will raise a django.db.utils.DatabaseError error when task is revoked,
# since we delete the encoding at that stage
except BaseException:
@ -457,18 +424,14 @@ def create_hls(friendly_token):
p = media.uid.hex
output_dir = os.path.join(settings.HLS_DIR, p)
encodings = media.encodings.filter(
profile__extension="mp4", status="success", chunk=False, profile__codec="h264"
)
encodings = media.encodings.filter(profile__extension="mp4", status="success", chunk=False, profile__codec="h264")
if encodings:
existing_output_dir = None
if os.path.exists(output_dir):
existing_output_dir = output_dir
output_dir = os.path.join(settings.HLS_DIR, p + produce_friendly_token())
files = " ".join([f.media_file.path for f in encodings if f.media_file])
cmd = "{0} --segment-duration=4 --output-dir={1} {2}".format(
settings.MP4HLS_COMMAND, output_dir, files
)
cmd = "{0} --segment-duration=4 --output-dir={1} {2}".format(settings.MP4HLS_COMMAND, output_dir, files)
ret = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
if existing_output_dir:
# override content with -T !
@ -515,11 +478,7 @@ def check_running_states():
def check_media_states():
# Experimental - unused
# check encoding status of not success media
media = Media.objects.filter(
Q(encoding_status="running")
| Q(encoding_status="fail")
| Q(encoding_status="pending")
)
media = Media.objects.filter(Q(encoding_status="running") | Q(encoding_status="fail") | Q(encoding_status="pending"))
logger.info("got {0} media that are not in state success".format(media.count()))
@ -564,11 +523,7 @@ def check_pending_states():
media.encode(profiles=[profile], force=False)
changed += 1
if changed:
logger.info(
"set to the encode queue {0} encodings that were on pending state".format(
changed
)
)
logger.info("set to the encode queue {0} encodings that were on pending state".format(changed))
return True
@ -602,6 +557,7 @@ def clear_sessions():
try:
from importlib import import_module
from django.conf import settings
engine = import_module(settings.SESSION_ENGINE)
@ -612,9 +568,7 @@ def clear_sessions():
@task(name="save_user_action", queue="short_tasks")
def save_user_action(
user_or_session, friendly_token=None, action="watch", extra_info=None
):
def save_user_action(user_or_session, friendly_token=None, action="watch", extra_info=None):
"""Short task that saves a user action"""
if action not in VALID_USER_ACTIONS:
@ -652,9 +606,7 @@ def save_user_action(
if user:
MediaAction.objects.filter(user=user, media=media, action="watch").delete()
else:
MediaAction.objects.filter(
session_key=session_key, media=media, action="watch"
).delete()
MediaAction.objects.filter(session_key=session_key, media=media, action="watch").delete()
if action == "rate":
try:
score = extra_info.get("score")
@ -663,9 +615,7 @@ def save_user_action(
# TODO: better error handling?
return False
try:
rating = Rating.objects.filter(
user=user, media=media, rating_category_id=rating_category
).first()
rating = Rating.objects.filter(user=user, media=media, rating_category_id=rating_category).first()
if rating:
rating.score = score
rating.save(update_fields=["score"])
@ -735,14 +685,10 @@ def get_list_of_popular_media():
for media in media_x:
ft = media["friendly_token"]
num = MediaAction.objects.filter(
action_date__gte=period_x, action="watch", media__friendly_token=ft
).count()
num = MediaAction.objects.filter(action_date__gte=period_x, action="watch", media__friendly_token=ft).count()
if num:
valid_media_x[ft] = num
num = MediaAction.objects.filter(
action_date__gte=period_y, action="like", media__friendly_token=ft
).count()
num = MediaAction.objects.filter(action_date__gte=period_y, action="like", media__friendly_token=ft).count()
if num:
valid_media_y[ft] = num
@ -767,12 +713,7 @@ def update_listings_thumbnails():
saved = 0
qs = Category.objects.filter().order_by("-media_count")
for object in qs:
media = (
Media.objects.exclude(friendly_token__in=used_media)
.filter(category=object, state="public", is_reviewed=True)
.order_by("-views")
.first()
)
media = Media.objects.exclude(friendly_token__in=used_media).filter(category=object, state="public", is_reviewed=True).order_by("-views").first()
if media:
object.listings_thumbnail = media.thumbnail_url
object.save(update_fields=["listings_thumbnail"])
@ -785,12 +726,7 @@ def update_listings_thumbnails():
saved = 0
qs = Tag.objects.filter().order_by("-media_count")
for object in qs:
media = (
Media.objects.exclude(friendly_token__in=used_media)
.filter(tags=object, state="public", is_reviewed=True)
.order_by("-views")
.first()
)
media = Media.objects.exclude(friendly_token__in=used_media).filter(tags=object, state="public", is_reviewed=True).order_by("-views").first()
if media:
object.listings_thumbnail = media.thumbnail_url
object.save(update_fields=["listings_thumbnail"])

View File

@ -1,10 +1,9 @@
from django.conf.urls.static import static
from django.conf import settings
from django.conf.urls import url, include
from django.conf.urls import include, url
from django.conf.urls.static import static
from django.urls import path
from . import views
from . import management_views
from . import management_views, views
from .feeds import IndexRSSFeed, SearchRSSFeed
urlpatterns = [

View File

@ -1,69 +1,67 @@
from datetime import datetime, timedelta
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.db.models import Q
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.template.defaultfilters import slugify
from django.core.mail import EmailMessage
from django.contrib.postgres.search import SearchQuery
from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.exceptions import PermissionDenied
from rest_framework import status
from rest_framework.parsers import (
JSONParser,
MultiPartParser,
FileUploadParser,
FormParser,
)
from celery.task.control import revoke
from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor
from cms.permissions import user_allowed_to_upload
from cms.custom_pagination import FastPaginationWithoutCount
from actions.models import MediaAction, USER_MEDIA_ACTIONS
from users.models import User
from .helpers import produce_ffmpeg_commands, clean_query
from .models import (
Media,
EncodeProfile,
Encoding,
Playlist,
PlaylistMedia,
Comment,
Category,
Tag,
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.postgres.search import SearchQuery
from django.core.mail import EmailMessage
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.template.defaultfilters import slugify
from rest_framework import permissions, status
from rest_framework.exceptions import PermissionDenied
from rest_framework.parsers import (
FileUploadParser,
FormParser,
JSONParser,
MultiPartParser,
)
from .forms import MediaForm, ContactForm, SubtitleForm
from .tasks import save_user_action
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from actions.models import USER_MEDIA_ACTIONS, MediaAction
from cms.custom_pagination import FastPaginationWithoutCount
from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor, user_allowed_to_upload
from users.models import User
from .forms import ContactForm, MediaForm, SubtitleForm
from .helpers import clean_query, produce_ffmpeg_commands
from .methods import (
list_tasks,
get_user_or_session,
show_recommended_media,
show_related_media,
is_mediacms_editor,
is_mediacms_manager,
update_user_ratings,
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 (
MediaSerializer,
CategorySerializer,
TagSerializer,
SingleMediaSerializer,
CommentSerializer,
EncodeProfileSerializer,
MediaSearchSerializer,
PlaylistSerializer,
MediaSerializer,
PlaylistDetailSerializer,
CommentSerializer,
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]
@ -86,11 +84,7 @@ def add_subtitle(request):
if not media:
return HttpResponseRedirect("/")
if not (
request.user == media.user
or is_mediacms_editor(request.user)
or is_mediacms_manager(request.user)
):
if not (request.user == media.user or is_mediacms_editor(request.user) or is_mediacms_manager(request.user)):
return HttpResponseRedirect("/")
if request.method == "POST":
@ -175,11 +169,7 @@ def edit_media(request):
if not media:
return HttpResponseRedirect("/")
if not (
request.user == media.user
or is_mediacms_editor(request.user)
or is_mediacms_manager(request.user)
):
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)
@ -342,9 +332,7 @@ def view_media(request):
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"
)
save_user_action.delay(user_or_session, friendly_token=friendly_token, action="watch")
context = {}
context["media"] = friendly_token
context["media_object"] = media
@ -354,11 +342,7 @@ def view_media(request):
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)
):
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
@ -443,33 +427,21 @@ class MediaDetail(APIView):
def get_object(self, friendly_token, password=None):
try:
media = (
Media.objects.select_related("user")
.prefetch_related("encodings__profile")
.get(friendly_token=friendly_token)
)
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)
):
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
)
return Response({"detail": "bad permissions"}, status=status.HTTP_401_UNAUTHORIZED)
except BaseException:
return Response(
{"detail": "media file does not exist"},
@ -488,23 +460,15 @@ class MediaDetail(APIView):
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_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")
)
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)
@ -521,9 +485,7 @@ class MediaDetail(APIView):
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
)
return Response({"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST)
action = request.data.get("type")
profiles_list = request.data.get("encoding_profiles")
@ -544,24 +506,18 @@ class MediaDetail(APIView):
valid_profiles.append(p)
except ValueError:
return Response(
{
"detail": "encoding_profiles must be int or list of ints of valid encode profiles"
},
{"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
)
return Response({"detail": "media will be encoded"}, status=status.HTTP_201_CREATED)
elif action == "review":
if result:
media.is_reviewed = True
elif result == False:
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": "media reviewed set"}, status=status.HTTP_201_CREATED)
return Response(
{"detail": "not valid action or no action specified"},
status=status.HTTP_400_BAD_REQUEST,
@ -573,9 +529,7 @@ class MediaDetail(APIView):
if isinstance(media, Response):
return media
serializer = MediaSerializer(
media, data=request.data, context={"request": request}
)
serializer = MediaSerializer(media, 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)
@ -601,20 +555,12 @@ class MediaActions(APIView):
def get_object(self, friendly_token):
try:
media = (
Media.objects.select_related("user")
.prefetch_related("encodings__profile")
.get(friendly_token=friendly_token)
)
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 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
)
return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
except BaseException:
return Response(
{"detail": "media file does not exist"},
@ -661,13 +607,9 @@ class MediaActions(APIView):
extra_info=extra,
)
return Response(
{"detail": "action received"}, status=status.HTTP_201_CREATED
)
return Response({"detail": "action received"}, status=status.HTTP_201_CREATED)
else:
return Response(
{"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, friendly_token, format=None):
media = self.get_object(friendly_token)
@ -675,9 +617,7 @@ class MediaActions(APIView):
return media
if not request.user.is_superuser:
return Response(
{"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST)
action = request.data.get("type")
if action:
@ -690,9 +630,7 @@ class MediaActions(APIView):
status=status.HTTP_201_CREATED,
)
else:
return Response(
{"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST)
class MediaSearch(APIView):
@ -736,11 +674,7 @@ class MediaSearch(APIView):
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
]
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:]:
@ -771,10 +705,10 @@ class MediaSearch(APIView):
if upload_date == 'this_month':
year = datetime.now().date().year
month = datetime.now().date().month
gte = datetime(year,month,1)
gte = datetime(year, month, 1)
if upload_date == 'this_year':
year = datetime.now().date().year
gte = datetime(year,1,1)
gte = datetime(year, 1, 1)
if lte:
media = media.filter(add_date__lte=lte)
if gte:
@ -794,9 +728,7 @@ class MediaSearch(APIView):
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator = pagination_class()
page = paginator.paginate_queryset(media, request)
serializer = MediaSearchSerializer(
page, many=True, context={"request": request}
)
serializer = MediaSearchSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)
@ -840,9 +772,7 @@ class PlaylistDetail(APIView):
self.check_object_permissions(self.request, playlist)
return playlist
except PermissionDenied:
return Response(
{"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST)
except BaseException:
return Response(
{"detail": "Playlist does not exist"},
@ -856,14 +786,10 @@ class PlaylistDetail(APIView):
serializer = PlaylistDetailSerializer(playlist, context={"request": request})
playlist_media = PlaylistMedia.objects.filter(
playlist=playlist
).prefetch_related("media__user")
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}
)
playlist_media_serializer = MediaSerializer(playlist_media, many=True, context={"request": request})
ret = serializer.data
ret["playlist_media"] = playlist_media_serializer.data
@ -873,9 +799,7 @@ class PlaylistDetail(APIView):
playlist = self.get_playlist(friendly_token)
if isinstance(playlist, Response):
return playlist
serializer = PlaylistDetailSerializer(
playlist, data=request.data, context={"request": request}
)
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)
@ -895,14 +819,10 @@ class PlaylistDetail(APIView):
pass
if action in ["add", "remove", "ordering"]:
media = Media.objects.filter(
friendly_token=media_friendly_token
).first()
media = Media.objects.filter(friendly_token=media_friendly_token).first()
if media:
if action == "add":
media_in_playlist = PlaylistMedia.objects.filter(
playlist=playlist
).count()
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"},
@ -920,9 +840,7 @@ class PlaylistDetail(APIView):
status=status.HTTP_201_CREATED,
)
elif action == "remove":
PlaylistMedia.objects.filter(
playlist=playlist, media=media
).delete()
PlaylistMedia.objects.filter(playlist=playlist, media=media).delete()
return Response(
{"detail": "media removed from Playlist"},
status=status.HTTP_201_CREATED,
@ -935,9 +853,7 @@ class PlaylistDetail(APIView):
status=status.HTTP_201_CREATED,
)
else:
return Response(
{"detail": "media is not valid"}, status=status.HTTP_400_BAD_REQUEST
)
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,
@ -993,7 +909,7 @@ class EncodingDetail(APIView):
chunk_file_path=chunk_file_path,
).count()
> 1
and force == False
and force is False
):
Encoding.objects.filter(id=encoding_id).delete()
return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
@ -1013,15 +929,11 @@ class EncodingDetail(APIView):
if chunk:
original_media_path = chunk_file_path
original_media_md5sum = encoding.md5sum
original_media_url = (
settings.SSL_FRONTEND_HOST + encoding.media_chunk_url
)
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
)
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
@ -1137,19 +1049,13 @@ class CommentDetail(APIView):
def get_object(self, friendly_token):
try:
media = Media.objects.select_related("user").get(
friendly_token=friendly_token
)
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 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
)
return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
except BaseException:
return Response(
{"detail": "media file does not exist"},
@ -1181,16 +1087,10 @@ class CommentDetail(APIView):
{"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)
):
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({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_204_NO_CONTENT)
def post(self, request, friendly_token):
@ -1221,13 +1121,7 @@ class UserActions(APIView):
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")
)
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")
@ -1250,9 +1144,7 @@ class CategoryList(APIView):
def get(self, request, format=None):
categories = Category.objects.filter().order_by("title")
serializer = CategorySerializer(
categories, many=True, context={"request": request}
)
serializer = CategorySerializer(categories, many=True, context={"request": request})
ret = serializer.data
return Response(ret)
@ -1274,9 +1166,7 @@ class EncodeProfileList(APIView):
def get(self, request, format=None):
profiles = EncodeProfile.objects.all()
serializer = EncodeProfileSerializer(
profiles, many=True, context={"request": request}
)
serializer = EncodeProfileSerializer(profiles, many=True, context={"request": request})
return Response(serializer.data)

View File

@ -7,9 +7,5 @@ if __name__ == "__main__":
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
raise ImportError("Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?") from exc
execute_from_command_line(sys.argv)

View File

@ -1,4 +1,4 @@
[flake8]
exclude = .git,*migrations*
max-line-length = 119
ignore=F401,E711,E302,E201,E501,E303,E231,F841,E722
ignore=F401,F403,W503,E711,E302,E201,E501,E303,E231,F841,E722

View File

@ -1,6 +1,7 @@
from os.path import join
from io import StringIO
import shutil
from io import StringIO
from os.path import join
from django.conf import settings
from . import utils

View File

@ -1,6 +1,7 @@
from django.core.exceptions import ImproperlyConfigured
from importlib import import_module
from django.core.exceptions import ImproperlyConfigured
def import_class(path):
path_bits = path.split(".")
@ -14,9 +15,7 @@ def import_class(path):
module_itself = import_module(module_path)
if not hasattr(module_itself, class_name):
message = "The Python module '{}' has no '{}' class.".format(
module_path, class_name
)
message = "The Python module '{}' has no '{}' class.".format(module_path, class_name)
raise ImportError(message)
return getattr(module_itself, class_name)

View File

@ -2,17 +2,18 @@
import os
import shutil
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.core.files import File
from django.http import JsonResponse
from django.views import generic
from django.conf import settings
from django.core.files import File
from django.core.exceptions import PermissionDenied
from cms.permissions import user_allowed_to_upload
from files.models import Media
from files.helpers import rm_file
from .forms import FineUploaderUploadForm, FineUploaderUploadSuccessForm
from files.models import Media
from .fineuploader import ChunkedFineUploader
from .forms import FineUploaderUploadForm, FineUploaderUploadSuccessForm
class FineUploaderView(generic.FormView):
@ -67,9 +68,7 @@ class FineUploaderView(generic.FormView):
new = Media.objects.create(media_file=myfile, user=self.request.user)
rm_file(media_file)
shutil.rmtree(os.path.join(settings.MEDIA_ROOT, self.upload.file_path))
return self.make_response(
{"success": True, "media_url": new.get_absolute_url()}
)
return self.make_response({"success": True, "media_url": new.get_absolute_url()})
def form_invalid(self, form):
data = {"success": False, "error": "%s" % repr(form.errors)}

View File

@ -1,7 +1,7 @@
from django.urls import reverse
from django.conf import settings
from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings
from django.core.exceptions import ValidationError
from django.urls import reverse
class MyAccountAdapter(DefaultAccountAdapter):

View File

@ -1,5 +1,6 @@
from django import forms
from .models import User, Channel
from .models import Channel, User
class SignupForm(forms.Form):
@ -23,7 +24,7 @@ class UserForm(forms.ModelForm):
"advancedUser",
"is_manager",
"is_editor",
#"allow_contact",
# "allow_contact",
)
def clean_logo(self):

View File

@ -1,12 +1,12 @@
# Generated by Django 3.1.4 on 2020-12-01 07:12
from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import imagekit.models.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
@ -33,9 +33,7 @@ class Migration(migrations.Migration):
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
models.DateTimeField(blank=True, null=True, verbose_name="last login"),
),
(
"is_superuser",
@ -48,35 +46,25 @@ class Migration(migrations.Migration):
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
models.CharField(blank=True, max_length=150, verbose_name="first name"),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
models.CharField(blank=True, max_length=150, verbose_name="last name"),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
models.EmailField(blank=True, max_length=254, verbose_name="email address"),
),
(
"is_staff",
@ -96,9 +84,7 @@ class Migration(migrations.Migration):
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"),
),
(
"logo",
@ -111,9 +97,7 @@ class Migration(migrations.Migration):
("description", models.TextField(blank=True, verbose_name="About me")),
(
"name",
models.CharField(
db_index=True, max_length=250, verbose_name="full name"
),
models.CharField(db_index=True, max_length=250, verbose_name="full name"),
),
(
"date_added",
@ -125,9 +109,7 @@ class Migration(migrations.Migration):
),
(
"is_featured",
models.BooleanField(
db_index=True, default=False, verbose_name="Is featured"
),
models.BooleanField(db_index=True, default=False, verbose_name="Is featured"),
),
(
"title",
@ -135,9 +117,7 @@ class Migration(migrations.Migration):
),
(
"advancedUser",
models.BooleanField(
db_index=True, default=False, verbose_name="advanced user"
),
models.BooleanField(db_index=True, default=False, verbose_name="advanced user"),
),
("media_count", models.IntegerField(default=0)),
(
@ -156,21 +136,15 @@ class Migration(migrations.Migration):
),
(
"location",
models.CharField(
blank=True, max_length=250, verbose_name="Location"
),
models.CharField(blank=True, max_length=250, verbose_name="Location"),
),
(
"is_editor",
models.BooleanField(
db_index=True, default=False, verbose_name="MediaCMS Editor"
),
models.BooleanField(db_index=True, default=False, verbose_name="MediaCMS Editor"),
),
(
"is_manager",
models.BooleanField(
db_index=True, default=False, verbose_name="MediaCMS Manager"
),
models.BooleanField(db_index=True, default=False, verbose_name="MediaCMS Manager"),
),
(
"groups",
@ -218,9 +192,7 @@ class Migration(migrations.Migration):
("notify", models.BooleanField(default=False)),
(
"method",
models.CharField(
choices=[("email", "Email")], default="email", max_length=20
),
models.CharField(choices=[("email", "Email")], default="email", max_length=20),
),
(
"user",
@ -276,8 +248,6 @@ class Migration(migrations.Migration):
),
migrations.AddIndex(
model_name="user",
index=models.Index(
fields=["-date_added", "name"], name="users_user_date_ad_4eb0b8_idx"
),
index=models.Index(fields=["-date_added", "name"], name="users_user_date_ad_4eb0b8_idx"),
),
]

View File

@ -1,18 +1,17 @@
from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.utils import timezone
from django.urls import reverse
from django.dispatch import receiver
from django.db.models.signals import post_save, post_delete
from django.utils.html import strip_tags
from django.core.mail import EmailMessage
from imagekit.processors import ResizeToFill
from django.db import models
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
from django.utils.html import strip_tags
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
import files.helpers as helpers
from files.models import Media, Tag, Category
from files.models import Category, Media, Tag
class User(AbstractUser):
@ -40,9 +39,7 @@ class User(AbstractUser):
location = models.CharField("Location", max_length=250, blank=True)
is_editor = models.BooleanField("MediaCMS Editor", default=False, db_index=True)
is_manager = models.BooleanField("MediaCMS Manager", default=False, db_index=True)
allow_contact = models.BooleanField(
"Whether allow contact will be shown on profile page", default=False
)
allow_contact = models.BooleanField("Whether allow contact will be shown on profile page", default=False)
class Meta:
ordering = ["-date_added", "name"]
@ -117,9 +114,7 @@ class User(AbstractUser):
class Channel(models.Model):
title = models.CharField(max_length=90, db_index=True)
description = models.TextField(blank=True, help_text="description")
user = models.ForeignKey(
User, on_delete=models.CASCADE, db_index=True, related_name="channels"
)
user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True, related_name="channels")
add_date = models.DateTimeField(auto_now_add=True, db_index=True)
subscribers = models.ManyToManyField(User, related_name="subscriptions", blank=True)
friendly_token = models.CharField(blank=True, max_length=12)
@ -150,13 +145,9 @@ class Channel(models.Model):
def get_absolute_url(self, edit=False):
if edit:
return reverse(
"edit_channel", kwargs={"friendly_token": self.friendly_token}
)
return reverse("edit_channel", kwargs={"friendly_token": self.friendly_token})
else:
return reverse(
"view_channel", kwargs={"friendly_token": self.friendly_token}
)
return reverse("view_channel", kwargs={"friendly_token": self.friendly_token})
@property
def edit_url(self):
@ -178,9 +169,7 @@ Visit user profile page at %s
instance.email,
settings.SSL_FRONTEND_HOST + instance.get_absolute_url(),
)
email = EmailMessage(
title, msg, settings.DEFAULT_FROM_EMAIL, settings.ADMIN_EMAIL_LIST
)
email = EmailMessage(title, msg, settings.DEFAULT_FROM_EMAIL, settings.ADMIN_EMAIL_LIST)
email.send(fail_silently=True)
@ -193,14 +182,10 @@ class Notification(models.Model):
Needs work
"""
user = models.ForeignKey(
User, on_delete=models.CASCADE, db_index=True, related_name="notifications"
)
user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True, related_name="notifications")
action = models.CharField(max_length=30, blank=True)
notify = models.BooleanField(default=False)
method = models.CharField(
max_length=20, choices=NOTIFICATION_METHODS, default="email"
)
method = models.CharField(max_length=20, choices=NOTIFICATION_METHODS, default="email")
def save(self, *args, **kwargs):
super(Notification, self).save(*args, **kwargs)

View File

@ -1,4 +1,5 @@
from rest_framework import serializers
from .models import User
@ -11,9 +12,7 @@ class UserSerializer(serializers.ModelSerializer):
return self.context["request"].build_absolute_uri(obj.get_absolute_url())
def get_api_url(self, obj):
return self.context["request"].build_absolute_uri(
obj.get_absolute_url(api=True)
)
return self.context["request"].build_absolute_uri(obj.get_absolute_url(api=True))
def get_thumbnail_url(self, obj):
return self.context["request"].build_absolute_uri(obj.thumbnail_url())
@ -55,9 +54,7 @@ class UserDetailSerializer(serializers.ModelSerializer):
return self.context["request"].build_absolute_uri(obj.get_absolute_url())
def get_api_url(self, obj):
return self.context["request"].build_absolute_uri(
obj.get_absolute_url(api=True)
)
return self.context["request"].build_absolute_uri(obj.get_absolute_url(api=True))
def get_thumbnail_url(self, obj):
return self.context["request"].build_absolute_uri(obj.thumbnail_url())

View File

@ -1,4 +1,5 @@
from django.conf.urls import url
from . import views
urlpatterns = [
@ -20,9 +21,7 @@ urlpatterns = [
name="get_user_about",
),
url(r"^user/(?P<username>[\w@.]*)/edit$", views.edit_user, name="edit_user"),
url(
r"^channel/(?P<friendly_token>[\w]*)$", views.view_channel, name="view_channel"
),
url(r"^channel/(?P<friendly_token>[\w]*)$", views.view_channel, name="view_channel"),
url(
r"^channel/(?P<friendly_token>[\w]*)/edit$",
views.edit_channel,

View File

@ -8,10 +8,7 @@ from django.utils.translation import gettext_lazy as _
@deconstructible
class ASCIIUsernameValidator(validators.RegexValidator):
regex = r"^[\w]+$"
message = _(
"Enter a valid username. This value may contain only "
"English letters and numbers"
)
message = _("Enter a valid username. This value may contain only " "English letters and numbers")
flags = re.ASCII

View File

@ -1,27 +1,27 @@
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.mail import EmailMessage
from django.conf import settings
from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.settings import api_settings
from django.http import HttpResponseRedirect
from django.shortcuts import render
from rest_framework import permissions, status
from rest_framework.decorators import api_view
from rest_framework.exceptions import PermissionDenied
from rest_framework import status
from rest_framework.parsers import (
JSONParser,
MultiPartParser,
FileUploadParser,
FormParser,
JSONParser,
MultiPartParser,
)
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from cms.permissions import IsUserOrManager
from files.methods import is_mediacms_manager, is_mediacms_editor
from .models import User, Channel
from .forms import UserForm, ChannelForm
from .serializers import UserSerializer, UserDetailSerializer
from files.methods import is_mediacms_editor, is_mediacms_manager
from .forms import ChannelForm, UserForm
from .models import Channel, User
from .serializers import UserDetailSerializer, UserSerializer
def get_user(username):
@ -38,15 +38,9 @@ def view_user(request, username):
if not user:
return HttpResponseRedirect("/members")
context["user"] = user
context["CAN_EDIT"] = (
True
if ((user and user == request.user) or is_mediacms_manager(request.user))
else False
)
context["CAN_EDIT"] = True if ((user and user == request.user) or is_mediacms_manager(request.user)) else False
context["CAN_DELETE"] = True if is_mediacms_manager(request.user) else False
context["SHOW_CONTACT_FORM"] = (
True if (user.allow_contact or is_mediacms_editor(request.user)) else False
)
context["SHOW_CONTACT_FORM"] = True if (user.allow_contact or is_mediacms_editor(request.user)) else False
return render(request, "cms/user.html", context)
@ -57,15 +51,9 @@ def view_user_media(request, username):
return HttpResponseRedirect("/members")
context["user"] = user
context["CAN_EDIT"] = (
True
if ((user and user == request.user) or is_mediacms_manager(request.user))
else False
)
context["CAN_EDIT"] = True if ((user and user == request.user) or is_mediacms_manager(request.user)) else False
context["CAN_DELETE"] = True if is_mediacms_manager(request.user) else False
context["SHOW_CONTACT_FORM"] = (
True if (user.allow_contact or is_mediacms_editor(request.user)) else False
)
context["SHOW_CONTACT_FORM"] = True if (user.allow_contact or is_mediacms_editor(request.user)) else False
return render(request, "cms/user_media.html", context)
@ -76,15 +64,9 @@ def view_user_playlists(request, username):
return HttpResponseRedirect("/members")
context["user"] = user
context["CAN_EDIT"] = (
True
if ((user and user == request.user) or is_mediacms_manager(request.user))
else False
)
context["CAN_EDIT"] = True if ((user and user == request.user) or is_mediacms_manager(request.user)) else False
context["CAN_DELETE"] = True if is_mediacms_manager(request.user) else False
context["SHOW_CONTACT_FORM"] = (
True if (user.allow_contact or is_mediacms_editor(request.user)) else False
)
context["SHOW_CONTACT_FORM"] = True if (user.allow_contact or is_mediacms_editor(request.user)) else False
return render(request, "cms/user_playlists.html", context)
@ -96,15 +78,9 @@ def view_user_about(request, username):
return HttpResponseRedirect("/members")
context["user"] = user
context["CAN_EDIT"] = (
True
if ((user and user == request.user) or is_mediacms_manager(request.user))
else False
)
context["CAN_EDIT"] = True if ((user and user == request.user) or is_mediacms_manager(request.user)) else False
context["CAN_DELETE"] = True if is_mediacms_manager(request.user) else False
context["SHOW_CONTACT_FORM"] = (
True if (user.allow_contact or is_mediacms_editor(request.user)) else False
)
context["SHOW_CONTACT_FORM"] = True if (user.allow_contact or is_mediacms_editor(request.user)) else False
return render(request, "cms/user_about.html", context)
@ -134,20 +110,14 @@ def view_channel(request, friendly_token):
else:
user = channel.user
context["user"] = user
context["CAN_EDIT"] = (
True
if ((user and user == request.user) or is_mediacms_manager(request.user))
else False
)
context["CAN_EDIT"] = True if ((user and user == request.user) or is_mediacms_manager(request.user)) else False
return render(request, "cms/channel.html", context)
@login_required
def edit_channel(request, friendly_token):
channel = Channel.objects.filter(friendly_token=friendly_token).first()
if not (
channel and request.user.is_authenticated and (request.user == channel.user)
):
if not (channel and request.user.is_authenticated and (request.user == channel.user)):
return HttpResponseRedirect("/")
if request.method == "POST":
@ -228,13 +198,9 @@ class UserDetail(APIView):
self.check_object_permissions(self.request, user)
return user
except PermissionDenied:
return Response(
{"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST)
except User.DoesNotExist:
return Response(
{"detail": "user does not exist"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "user does not exist"}, status=status.HTTP_400_BAD_REQUEST)
def get(self, request, username, format=None):
# Get user details
@ -251,9 +217,7 @@ class UserDetail(APIView):
if isinstance(user, Response):
return user
serializer = UserDetailSerializer(
user, data=request.data, context={"request": request}
)
serializer = UserDetailSerializer(user, data=request.data, context={"request": request})
if serializer.is_valid():
logo = request.data.get("logo")
if logo:
@ -271,9 +235,7 @@ class UserDetail(APIView):
return user
if not request.user.is_superuser:
return Response(
{"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST
)
return Response({"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST)
action = request.data.get("action")
if action == "feature":