mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-22 06:17:58 -05:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1fda05fdc | ||
|
|
a02e0a8a66 | ||
|
|
21f76dbb6e | ||
|
|
50e9f3103f | ||
|
|
0b9a203123 | ||
|
|
5cbd815496 | ||
|
|
3a8cacc847 |
@@ -24,7 +24,7 @@ RUN mkdir -p /home/mediacms.io/bento4 && \
|
|||||||
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
||||||
|
|
||||||
############ RUNTIME IMAGE ############
|
############ RUNTIME IMAGE ############
|
||||||
FROM python:3.13-bookworm as runtime-image
|
FROM python:3.13-bookworm AS runtime_image
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ INSTALLED_APPS = [
|
|||||||
"crispy_bootstrap5",
|
"crispy_bootstrap5",
|
||||||
'uploader.apps.UploaderConfig',
|
'uploader.apps.UploaderConfig',
|
||||||
'djcelery_email',
|
'djcelery_email',
|
||||||
'ckeditor',
|
|
||||||
'drf_yasg',
|
'drf_yasg',
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ CANNOT_ADD_MEDIA_MESSAGE = ""
|
|||||||
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
|
# highly experimental, related with remote workers
|
||||||
ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
|
ADMIN_TOKEN = ""
|
||||||
# this is used by remote workers to push
|
# this is used by remote workers to push
|
||||||
# encodings once they are done
|
# encodings once they are done
|
||||||
# USE_BASIC_HTTP = True
|
# USE_BASIC_HTTP = True
|
||||||
@@ -247,35 +247,6 @@ ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
|
|||||||
# uncomment the two lines related to htpasswd
|
# uncomment the two lines related to htpasswd
|
||||||
|
|
||||||
|
|
||||||
CKEDITOR_CONFIGS = {
|
|
||||||
"default": {
|
|
||||||
"toolbar": "Custom",
|
|
||||||
"width": "100%",
|
|
||||||
"toolbar_Custom": [
|
|
||||||
["Styles"],
|
|
||||||
["Format"],
|
|
||||||
["Bold", "Italic", "Underline"],
|
|
||||||
["HorizontalRule"],
|
|
||||||
[
|
|
||||||
"NumberedList",
|
|
||||||
"BulletedList",
|
|
||||||
"-",
|
|
||||||
"Outdent",
|
|
||||||
"Indent",
|
|
||||||
"-",
|
|
||||||
"JustifyLeft",
|
|
||||||
"JustifyCenter",
|
|
||||||
"JustifyRight",
|
|
||||||
"JustifyBlock",
|
|
||||||
],
|
|
||||||
["Link", "Unlink"],
|
|
||||||
["Image"],
|
|
||||||
["RemoveFormat", "Source"],
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AUTH_USER_MODEL = "users.User"
|
AUTH_USER_MODEL = "users.User"
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
||||||
@@ -307,7 +278,6 @@ INSTALLED_APPS = [
|
|||||||
"crispy_bootstrap5",
|
"crispy_bootstrap5",
|
||||||
"uploader.apps.UploaderConfig",
|
"uploader.apps.UploaderConfig",
|
||||||
"djcelery_email",
|
"djcelery_email",
|
||||||
"ckeditor",
|
|
||||||
"drf_yasg",
|
"drf_yasg",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -349,11 +319,15 @@ WSGI_APPLICATION = "cms.wsgi.application"
|
|||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
"OPTIONS": {
|
||||||
|
"user_attributes": ("username", "email", "first_name", "last_name"),
|
||||||
|
"max_similarity": 0.7,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"min_length": 5,
|
"min_length": 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -547,3 +521,12 @@ CALCULATE_MD5SUM = False
|
|||||||
|
|
||||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||||
|
|
||||||
|
# allow option to override the default admin url
|
||||||
|
# keep the trailing slash
|
||||||
|
DJANGO_ADMIN_URL = "admin/"
|
||||||
|
|
||||||
|
# CSRF_COOKIE_SECURE = True
|
||||||
|
# SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
PYSUBS_COMMAND = "pysubs2"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
|
from django.conf import settings
|
||||||
from django.conf.urls import include
|
from django.conf.urls import include
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
@@ -25,7 +26,7 @@ urlpatterns = [
|
|||||||
re_path(r"^", include("users.urls")),
|
re_path(r"^", include("users.urls")),
|
||||||
re_path(r"^accounts/", include("allauth.urls")),
|
re_path(r"^accounts/", include("allauth.urls")),
|
||||||
re_path(r"^api-auth/", include("rest_framework.urls")),
|
re_path(r"^api-auth/", include("rest_framework.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path(settings.DJANGO_ADMIN_URL, admin.site.urls),
|
||||||
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||||
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||||
path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
|
|||||||
@@ -34,5 +34,7 @@ def stuff(request):
|
|||||||
ret["RSS_URL"] = "/rss"
|
ret["RSS_URL"] = "/rss"
|
||||||
ret["TRANSLATION"] = get_translation(request.LANGUAGE_CODE)
|
ret["TRANSLATION"] = get_translation(request.LANGUAGE_CODE)
|
||||||
ret["REPLACEMENTS"] = get_translation_strings(request.LANGUAGE_CODE)
|
ret["REPLACEMENTS"] = get_translation_strings(request.LANGUAGE_CODE)
|
||||||
|
if request.user.is_superuser:
|
||||||
|
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ class SubtitleForm(forms.ModelForm):
|
|||||||
def __init__(self, media_item, *args, **kwargs):
|
def __init__(self, media_item, *args, **kwargs):
|
||||||
super(SubtitleForm, self).__init__(*args, **kwargs)
|
super(SubtitleForm, self).__init__(*args, **kwargs)
|
||||||
self.instance.media = media_item
|
self.instance.media = media_item
|
||||||
|
self.fields["subtitle_file"].help_text = "SubRip (.srt) and WebVTT (.vtt) are supported file formats."
|
||||||
|
self.fields["subtitle_file"].label = "Subtitle or Closed Caption File"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.instance.user = self.instance.media.user
|
self.instance.user = self.instance.media.user
|
||||||
@@ -75,6 +77,14 @@ class SubtitleForm(forms.ModelForm):
|
|||||||
return media
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
class EditSubtitleForm(forms.Form):
|
||||||
|
subtitle = forms.CharField(widget=forms.Textarea, required=True)
|
||||||
|
|
||||||
|
def __init__(self, subtitle, *args, **kwargs):
|
||||||
|
super(EditSubtitleForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields["subtitle"].initial = subtitle.subtitle_file.read().decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
class ContactForm(forms.Form):
|
class ContactForm(forms.Form):
|
||||||
from_email = forms.EmailField(required=True)
|
from_email = forms.EmailField(required=True)
|
||||||
name = forms.CharField(required=False)
|
name = forms.CharField(required=False)
|
||||||
|
|||||||
@@ -119,12 +119,16 @@ def get_next_state(user, current_state, next_state):
|
|||||||
|
|
||||||
if next_state not in ["public", "private", "unlisted"]:
|
if next_state not in ["public", "private", "unlisted"]:
|
||||||
next_state = settings.PORTAL_WORKFLOW # get default state
|
next_state = settings.PORTAL_WORKFLOW # get default state
|
||||||
|
|
||||||
if is_mediacms_editor(user):
|
if is_mediacms_editor(user):
|
||||||
# allow any transition
|
# allow any transition
|
||||||
return next_state
|
return next_state
|
||||||
|
|
||||||
if settings.PORTAL_WORKFLOW == "private":
|
if settings.PORTAL_WORKFLOW == "private":
|
||||||
next_state = "private"
|
if next_state in ["private", "unlisted"]:
|
||||||
|
next_state = next_state
|
||||||
|
else:
|
||||||
|
next_state = current_state
|
||||||
|
|
||||||
if settings.PORTAL_WORKFLOW == "unlisted":
|
if settings.PORTAL_WORKFLOW == "unlisted":
|
||||||
# don't allow to make media public in this case
|
# don't allow to make media public in this case
|
||||||
|
|||||||
@@ -1210,9 +1210,36 @@ class Subtitle(models.Model):
|
|||||||
|
|
||||||
user = models.ForeignKey("users.User", on_delete=models.CASCADE)
|
user = models.ForeignKey("users.User", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["language__title"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{0}-{1}".format(self.media.title, self.language.title)
|
return "{0}-{1}".format(self.media.title, self.language.title)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return f"{reverse('edit_subtitle')}?id={self.id}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self.get_absolute_url()
|
||||||
|
|
||||||
|
def convert_to_srt(self):
|
||||||
|
input_path = self.subtitle_file.path
|
||||||
|
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname:
|
||||||
|
pysub = settings.PYSUBS_COMMAND
|
||||||
|
|
||||||
|
cmd = [pysub, input_path, "--to", "vtt", "-o", tmpdirname]
|
||||||
|
stdout = helpers.run_command(cmd)
|
||||||
|
|
||||||
|
list_of_files = os.listdir(tmpdirname)
|
||||||
|
if list_of_files:
|
||||||
|
subtitles_file = os.path.join(tmpdirname, list_of_files[0])
|
||||||
|
cmd = ["cp", subtitles_file, input_path]
|
||||||
|
stdout = helpers.run_command(cmd) # noqa
|
||||||
|
else:
|
||||||
|
raise Exception("Could not convert to srt")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class RatingCategory(models.Model):
|
class RatingCategory(models.Model):
|
||||||
"""Rating Category
|
"""Rating Category
|
||||||
@@ -1285,7 +1312,7 @@ class Playlist(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def media_count(self):
|
def media_count(self):
|
||||||
return self.media.count()
|
return self.media.filter(listable=True).count()
|
||||||
|
|
||||||
def get_absolute_url(self, api=False):
|
def get_absolute_url(self, api=False):
|
||||||
if api:
|
if api:
|
||||||
@@ -1332,7 +1359,7 @@ class Playlist(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def thumbnail_url(self):
|
def thumbnail_url(self):
|
||||||
pm = self.playlistmedia_set.first()
|
pm = self.playlistmedia_set.filter(media__listable=True).first()
|
||||||
if pm and pm.media.thumbnail:
|
if pm and pm.media.thumbnail:
|
||||||
return helpers.url_from_path(pm.media.thumbnail.path)
|
return helpers.url_from_path(pm.media.thumbnail.path)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ urlpatterns = [
|
|||||||
re_path(r"^about", views.about, name="about"),
|
re_path(r"^about", views.about, name="about"),
|
||||||
re_path(r"^setlanguage", views.setlanguage, name="setlanguage"),
|
re_path(r"^setlanguage", views.setlanguage, name="setlanguage"),
|
||||||
re_path(r"^add_subtitle", views.add_subtitle, name="add_subtitle"),
|
re_path(r"^add_subtitle", views.add_subtitle, name="add_subtitle"),
|
||||||
|
re_path(r"^edit_subtitle", views.edit_subtitle, name="edit_subtitle"),
|
||||||
re_path(r"^categories$", views.categories, name="categories"),
|
re_path(r"^categories$", views.categories, name="categories"),
|
||||||
re_path(r"^contact$", views.contact, name="contact"),
|
re_path(r"^contact$", views.contact, name="contact"),
|
||||||
re_path(r"^edit", views.edit_media, name="edit_media"),
|
re_path(r"^edit", views.edit_media, name="edit_media"),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.contrib.postgres.search import SearchQuery
|
from django.contrib.postgres.search import SearchQuery
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from drf_yasg import openapi as openapi
|
from drf_yasg import openapi as openapi
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
@@ -32,7 +32,7 @@ from cms.permissions import (
|
|||||||
)
|
)
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
|
||||||
from .forms import ContactForm, MediaForm, SubtitleForm
|
from .forms import ContactForm, EditSubtitleForm, MediaForm, SubtitleForm
|
||||||
from .frontend_translations import translate_string
|
from .frontend_translations import translate_string
|
||||||
from .helpers import clean_query, get_alphanumeric_only, produce_ffmpeg_commands
|
from .helpers import clean_query, get_alphanumeric_only, produce_ffmpeg_commands
|
||||||
from .methods import (
|
from .methods import (
|
||||||
@@ -54,6 +54,7 @@ from .models import (
|
|||||||
Media,
|
Media,
|
||||||
Playlist,
|
Playlist,
|
||||||
PlaylistMedia,
|
PlaylistMedia,
|
||||||
|
Subtitle,
|
||||||
Tag,
|
Tag,
|
||||||
)
|
)
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
@@ -105,12 +106,68 @@ def add_subtitle(request):
|
|||||||
form = SubtitleForm(media, request.POST, request.FILES)
|
form = SubtitleForm(media, request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
subtitle = form.save()
|
subtitle = form.save()
|
||||||
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Subtitle was added"))
|
new_subtitle = Subtitle.objects.filter(id=subtitle.id).first()
|
||||||
|
try:
|
||||||
|
new_subtitle.convert_to_srt()
|
||||||
|
messages.add_message(request, messages.INFO, "Subtitle was added!")
|
||||||
|
return HttpResponseRedirect(subtitle.media.get_absolute_url())
|
||||||
|
except: # noqa: E722
|
||||||
|
new_subtitle.delete()
|
||||||
|
error_msg = "Invalid subtitle format. Use SubRip (.srt) or WebVTT (.vtt) files."
|
||||||
|
form.add_error("subtitle_file", error_msg)
|
||||||
|
|
||||||
return HttpResponseRedirect(subtitle.media.get_absolute_url())
|
|
||||||
else:
|
else:
|
||||||
form = SubtitleForm(media_item=media)
|
form = SubtitleForm(media_item=media)
|
||||||
return render(request, "cms/add_subtitle.html", {"form": form})
|
subtitles = media.subtitles.all()
|
||||||
|
context = {"media": media, "form": form, "subtitles": subtitles}
|
||||||
|
return render(request, "cms/add_subtitle.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_subtitle(request):
|
||||||
|
subtitle_id = request.GET.get("id", "").strip()
|
||||||
|
action = request.GET.get("action", "").strip()
|
||||||
|
if not subtitle_id:
|
||||||
|
return HttpResponseRedirect("/")
|
||||||
|
subtitle = Subtitle.objects.filter(id=subtitle_id).first()
|
||||||
|
|
||||||
|
if not subtitle:
|
||||||
|
return HttpResponseRedirect("/")
|
||||||
|
|
||||||
|
if not (request.user == subtitle.user or is_mediacms_editor(request.user) or is_mediacms_manager(request.user)):
|
||||||
|
return HttpResponseRedirect("/")
|
||||||
|
|
||||||
|
context = {"subtitle": subtitle, "action": action}
|
||||||
|
|
||||||
|
if action == "download":
|
||||||
|
response = HttpResponse(subtitle.subtitle_file.read(), content_type="text/vtt")
|
||||||
|
filename = subtitle.subtitle_file.name.split("/")[-1]
|
||||||
|
|
||||||
|
if not filename.endswith(".vtt"):
|
||||||
|
filename = f"{filename}.vtt"
|
||||||
|
|
||||||
|
response["Content-Disposition"] = f"attachment; filename={filename}" # noqa
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
form = EditSubtitleForm(subtitle)
|
||||||
|
context["form"] = form
|
||||||
|
elif request.method == "POST":
|
||||||
|
confirm = request.GET.get("confirm", "").strip()
|
||||||
|
if confirm == "true":
|
||||||
|
messages.add_message(request, messages.INFO, "Subtitle was deleted")
|
||||||
|
redirect_url = subtitle.media.get_absolute_url()
|
||||||
|
subtitle.delete()
|
||||||
|
return HttpResponseRedirect(redirect_url)
|
||||||
|
form = EditSubtitleForm(subtitle, request.POST)
|
||||||
|
subtitle_text = form.data["subtitle"]
|
||||||
|
with open(subtitle.subtitle_file.path, "w") as ff:
|
||||||
|
ff.write(subtitle_text)
|
||||||
|
|
||||||
|
messages.add_message(request, messages.INFO, "Subtitle was edited")
|
||||||
|
return HttpResponseRedirect(subtitle.media.get_absolute_url())
|
||||||
|
return render(request, "cms/edit_subtitle.html", context)
|
||||||
|
|
||||||
|
|
||||||
def categories(request):
|
def categories(request):
|
||||||
@@ -675,6 +732,9 @@ class MediaActions(APIView):
|
|||||||
def get(self, request, friendly_token, format=None):
|
def get(self, request, friendly_token, format=None):
|
||||||
# show date and reason for each time media was reported
|
# show date and reason for each time media was reported
|
||||||
media = self.get_object(friendly_token)
|
media = self.get_object(friendly_token)
|
||||||
|
if not (request.user == media.user or is_mediacms_editor(request.user) or is_mediacms_manager(request.user)):
|
||||||
|
return Response({"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
if isinstance(media, Response):
|
if isinstance(media, Response):
|
||||||
return media
|
return media
|
||||||
|
|
||||||
@@ -928,9 +988,10 @@ class PlaylistDetail(APIView):
|
|||||||
|
|
||||||
serializer = PlaylistDetailSerializer(playlist, context={"request": request})
|
serializer = PlaylistDetailSerializer(playlist, context={"request": request})
|
||||||
|
|
||||||
playlist_media = PlaylistMedia.objects.filter(playlist=playlist).prefetch_related("media__user")
|
playlist_media = PlaylistMedia.objects.filter(playlist=playlist, media__state="public").prefetch_related("media__user")
|
||||||
|
|
||||||
playlist_media = [c.media for c in playlist_media]
|
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 = serializer.data
|
||||||
ret["playlist_media"] = playlist_media_serializer.data
|
ret["playlist_media"] = playlist_media_serializer.data
|
||||||
@@ -1195,7 +1256,7 @@ class CommentList(APIView):
|
|||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
|
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
|
||||||
paginator = pagination_class()
|
paginator = pagination_class()
|
||||||
comments = Comment.objects.filter()
|
comments = Comment.objects.filter(media__state="public").order_by("-add_date")
|
||||||
comments = comments.prefetch_related("user")
|
comments = comments.prefetch_related("user")
|
||||||
comments = comments.prefetch_related("media")
|
comments = comments.prefetch_related("media")
|
||||||
params = self.request.query_params
|
params = self.request.query_params
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ if (window.MediaCMS.site.devEnv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PlayAllLink(props) {
|
function PlayAllLink(props) {
|
||||||
|
|
||||||
|
if (!props.media || !props.media.length) {
|
||||||
|
return <span>{props.children}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
let playAllUrl = props.media[0].url;
|
let playAllUrl = props.media[0].url;
|
||||||
|
|
||||||
if (window.MediaCMS.site.devEnv && -1 < playAllUrl.indexOf('view?')) {
|
if (window.MediaCMS.site.devEnv && -1 < playAllUrl.indexOf('view?')) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ crispy-bootstrap5==2024.10
|
|||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
django-celery-email==3.0.0
|
django-celery-email==3.0.0
|
||||||
m3u8==6.0.0
|
m3u8==6.0.0
|
||||||
django-ckeditor==6.7.2
|
|
||||||
django-debug-toolbar==5.0.1
|
django-debug-toolbar==5.0.1
|
||||||
django-login-required-middleware==0.9.0
|
django-login-required-middleware==0.9.0
|
||||||
pre-commit==4.1.0
|
pre-commit==4.1.0
|
||||||
|
pysubs2==1.8.0
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -7,11 +7,23 @@
|
|||||||
<div class="user-action-form-wrap">
|
<div class="user-action-form-wrap">
|
||||||
<div class="user-action-form-inner">
|
<div class="user-action-form-inner">
|
||||||
<h1>Add subtitle</h1>
|
<h1>Add subtitle</h1>
|
||||||
|
Media: <a href="{{media.get_absolute_url}}">{{media.title}}</a>
|
||||||
|
|
||||||
<form enctype="multipart/form-data" action="" method="post" class="post-form">
|
<form enctype="multipart/form-data" action="" method="post" class="post-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button class="primaryAction" type="submit">Add</button>
|
<button class="primaryAction" type="submit">Add</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{% if subtitles %}
|
||||||
|
<h3>View/Edit Existing Subtitles</h3>
|
||||||
|
<ul>
|
||||||
|
{% for subtitle in subtitles %}
|
||||||
|
<li><a href="{{subtitle.url}}">{{subtitle.language.title}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock innercontent %}
|
{% endblock innercontent %}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
{% block headermeta %}{% endblock headermeta %}
|
{% block headermeta %}{% endblock headermeta %}
|
||||||
|
|
||||||
{% block innercontent %}
|
{% block innercontent %}
|
||||||
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
|
|
||||||
|
|
||||||
<div class="user-action-form-wrap">
|
<div class="user-action-form-wrap">
|
||||||
<div class="user-action-form-inner">
|
<div class="user-action-form-inner">
|
||||||
|
|||||||
49
templates/cms/edit_subtitle.html
Normal file
49
templates/cms/edit_subtitle.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block headtitle %}Edit subtitle - {{PORTAL_NAME}}{% endblock headtitle %}
|
||||||
|
|
||||||
|
{% block headermeta %}{% endblock headermeta %}
|
||||||
|
|
||||||
|
{% block innercontent %}
|
||||||
|
|
||||||
|
{% if action == 'delete' %}
|
||||||
|
|
||||||
|
<div class="user-action-form-wrap">
|
||||||
|
<h1>Confirm deletion</h1>
|
||||||
|
|
||||||
|
<div class="user-action-form-inner">
|
||||||
|
are you sure you want to delete the subtitle?
|
||||||
|
|
||||||
|
<form action="{{subtitle.url}}&action=delete&confirm=true" method="post" class="post-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button class="secondaryAction" type="submit">DELETE SUBTITLE</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<div class="user-action-form-wrap">
|
||||||
|
<h1>Edit {{subtitle.language.title}} subtitle</h1>
|
||||||
|
<div class="user-action-form-inner">
|
||||||
|
Media: <a href="{{subtitle.media.get_absolute_url}}">{{subtitle.media.title}}</a>
|
||||||
|
<form action="" method="post" class="post-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<button class="primaryAction" style="margin-right: 10px" type="submit">SAVE</button>
|
||||||
|
<button class="primaryAction" style="margin-right: 10px" type="button" onclick="window.location.href='{{subtitle.url}}&action=download';">DOWNLOAD</button>
|
||||||
|
<button class="primaryAction" type="button" onclick="window.location.href='{{subtitle.url}}&action=delete';">DELETE</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endblock innercontent %}
|
||||||
|
|
||||||
|
|
||||||
@@ -20,100 +20,105 @@
|
|||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if media_object.media_type == "video" %}
|
{% if media_object.state != "private" %}
|
||||||
|
|
||||||
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
|
{% if media_object.media_type == "video" %}
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
|
||||||
|
|
||||||
<script type="application/ld+json">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
{
|
|
||||||
"@context": "http://schema.org",
|
<script type="application/ld+json">
|
||||||
"@type": "VideoObject",
|
{
|
||||||
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
"@context": "http://schema.org",
|
||||||
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
"@type": "VideoObject",
|
||||||
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
||||||
"thumbnailUrl": [
|
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
||||||
"{{FRONTEND_HOST}}{{media_object.poster_url}}"
|
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
||||||
],
|
"thumbnailUrl": [
|
||||||
"uploadDate": "{{media_object.add_date}}",
|
"{{FRONTEND_HOST}}{{media_object.poster_url}}"
|
||||||
"dateModified": "{{media_object.edit_date}}",
|
],
|
||||||
"embedUrl": "{{FRONTEND_HOST}}/embed?m={{media}}",
|
"uploadDate": "{{media_object.add_date}}",
|
||||||
"duration": "T{{media_object.duration}}S",
|
"dateModified": "{{media_object.edit_date}}",
|
||||||
"potentialAction": {
|
"embedUrl": "{{FRONTEND_HOST}}/embed?m={{media}}",
|
||||||
"@type": "ViewAction",
|
"duration": "T{{media_object.duration}}S",
|
||||||
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
"potentialAction": {
|
||||||
|
"@type": "ViewAction",
|
||||||
|
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
</script>
|
||||||
</script>
|
|
||||||
|
|
||||||
{% elif media_object.media_type == "audio" %}
|
{% elif media_object.media_type == "audio" %}
|
||||||
|
|
||||||
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
|
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.poster_url}}">
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@context": "http://schema.org",
|
"@context": "http://schema.org",
|
||||||
"@type": "AudioObject",
|
"@type": "AudioObject",
|
||||||
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
||||||
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
||||||
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
||||||
"uploadDate": "{{media_object.add_date}}",
|
"uploadDate": "{{media_object.add_date}}",
|
||||||
"dateModified": "{{media_object.edit_date}}",
|
"dateModified": "{{media_object.edit_date}}",
|
||||||
"duration": "T{{media_object.duration}}S",
|
"duration": "T{{media_object.duration}}S",
|
||||||
"potentialAction": {
|
"potentialAction": {
|
||||||
"@type": "ViewAction",
|
"@type": "ViewAction",
|
||||||
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
</script>
|
||||||
</script>
|
|
||||||
|
|
||||||
{% elif media_object.media_type == "image" %}
|
{% elif media_object.media_type == "image" %}
|
||||||
|
|
||||||
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.original_media_url}}">
|
<meta property="og:image" content="{{FRONTEND_HOST}}{{media_object.original_media_url}}">
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
"@context": "http://schema.org",
|
"@context": "http://schema.org",
|
||||||
"@type": "ImageObject",
|
"@type": "ImageObject",
|
||||||
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
||||||
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
||||||
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
||||||
"uploadDate": "{{media_object.add_date}}",
|
"uploadDate": "{{media_object.add_date}}",
|
||||||
"dateModified": "{{media_object.edit_date}}",
|
"dateModified": "{{media_object.edit_date}}",
|
||||||
"potentialAction": {
|
"potentialAction": {
|
||||||
"@type": "ViewAction",
|
"@type": "ViewAction",
|
||||||
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
</script>
|
||||||
</script>
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "http://schema.org",
|
||||||
|
"@type": "MediaObject",
|
||||||
|
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
||||||
|
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
||||||
|
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
||||||
|
"uploadDate": "{{media_object.add_date}}",
|
||||||
|
"dateModified": "{{media_object.edit_date}}",
|
||||||
|
"potentialAction": {
|
||||||
|
"@type": "ViewAction",
|
||||||
|
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary">
|
|
||||||
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "http://schema.org",
|
|
||||||
"@type": "MediaObject",
|
|
||||||
"name": "{{media_object.title}} - {{PORTAL_NAME}}",
|
|
||||||
"url": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}",
|
|
||||||
"description": "{% if media_object.summary %}{{media_object.summary}}{% else %}{{media_object.description}}{% endif %}",
|
|
||||||
"uploadDate": "{{media_object.add_date}}",
|
|
||||||
"dateModified": "{{media_object.edit_date}}",
|
|
||||||
"potentialAction": {
|
|
||||||
"@type": "ViewAction",
|
|
||||||
"target": "{{FRONTEND_HOST}}{{media_object.get_absolute_url}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endblock headermeta %}
|
{% endblock headermeta %}
|
||||||
|
|
||||||
{% block topimports %}
|
{% block topimports %}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
{% block headtitle %}Edit profile - {% endblock headtitle %}
|
{% block headtitle %}Edit profile - {% endblock headtitle %}
|
||||||
|
|
||||||
{% block innercontent %}
|
{% block innercontent %}
|
||||||
<script type="text/javascript" src="{% static "ckeditor/ckeditor-init.js" %}"></script>
|
|
||||||
<script type="text/javascript" src="{% static "ckeditor/ckeditor/ckeditor.js" %}"></script>
|
|
||||||
|
|
||||||
<div class="user-action-form-wrap">
|
<div class="user-action-form-wrap">
|
||||||
<div class="user-action-form-inner">
|
<div class="user-action-form-inner">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ MediaCMS.url = {
|
|||||||
editChannel: "{{user.default_channel_edit_url}}",
|
editChannel: "{{user.default_channel_edit_url}}",
|
||||||
changePassword: "/accounts/password/change/",
|
changePassword: "/accounts/password/change/",
|
||||||
/* Administration pages */
|
/* Administration pages */
|
||||||
{% if IS_MEDIACMS_ADMIN %}admin: '/admin',{% endif %}
|
{% if IS_MEDIACMS_ADMIN %}admin: '/{{DJANGO_ADMIN_URL}}',{% endif %}
|
||||||
/* Management pages */
|
/* Management pages */
|
||||||
{% if IS_MEDIACMS_EDITOR %}manageMedia: "/manage/media",{% endif %}
|
{% if IS_MEDIACMS_EDITOR %}manageMedia: "/manage/media",{% endif %}
|
||||||
{% if IS_MEDIACMS_MANAGER %}manageUsers: "/manage/users",{% endif %}
|
{% if IS_MEDIACMS_MANAGER %}manageUsers: "/manage/users",{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user