mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-12-25 12:52:30 -05:00
replace media, shared state, better category options
This commit is contained in:
@@ -58,6 +58,7 @@ def stuff(request):
|
||||
ret["USE_RBAC"] = settings.USE_RBAC
|
||||
ret["USE_ROUNDED_CORNERS"] = settings.USE_ROUNDED_CORNERS
|
||||
ret["INCLUDE_LISTING_NUMBERS"] = settings.INCLUDE_LISTING_NUMBERS
|
||||
ret["ALLOW_MEDIA_REPLACEMENT"] = getattr(settings, 'ALLOW_MEDIA_REPLACEMENT', False)
|
||||
ret["VERSION"] = VERSION
|
||||
|
||||
if request.user.is_superuser:
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.conf import settings
|
||||
|
||||
from .methods import get_next_state, is_mediacms_editor
|
||||
from .models import MEDIA_STATES, Category, Media, Subtitle
|
||||
from .widgets import CategoryModalWidget
|
||||
|
||||
|
||||
class CustomField(Field):
|
||||
@@ -121,13 +122,18 @@ class MediaPublishForm(forms.ModelForm):
|
||||
fields = ("category", "state", "featured", "reported_times", "is_reviewed", "allow_download")
|
||||
|
||||
widgets = {
|
||||
"category": MultipleSelect(),
|
||||
"category": CategoryModalWidget(),
|
||||
}
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
self.user = user
|
||||
super(MediaPublishForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.has_custom_permissions = self.instance.permissions.exists() if self.instance.pk else False
|
||||
self.has_rbac_categories = self.instance.category.filter(is_rbac_category=True).exists() if self.instance.pk else False
|
||||
self.is_shared = self.has_custom_permissions or self.has_rbac_categories
|
||||
self.actual_state = self.instance.state if self.instance.pk else None
|
||||
|
||||
if not is_mediacms_editor(user):
|
||||
for field in ["featured", "reported_times", "is_reviewed"]:
|
||||
self.fields[field].disabled = True
|
||||
@@ -140,6 +146,13 @@ class MediaPublishForm(forms.ModelForm):
|
||||
valid_states.append(self.instance.state)
|
||||
self.fields["state"].choices = [(state, dict(MEDIA_STATES).get(state, state)) for state in valid_states]
|
||||
|
||||
if self.is_shared:
|
||||
current_choices = list(self.fields["state"].choices)
|
||||
current_choices.insert(0, ("shared", "Shared"))
|
||||
self.fields["state"].choices = current_choices
|
||||
self.fields["state"].initial = "shared"
|
||||
self.initial["state"] = "shared"
|
||||
|
||||
if getattr(settings, 'USE_RBAC', False) and 'category' in self.fields:
|
||||
if is_mediacms_editor(user):
|
||||
pass
|
||||
@@ -178,7 +191,35 @@ class MediaPublishForm(forms.ModelForm):
|
||||
state = cleaned_data.get("state")
|
||||
categories = cleaned_data.get("category")
|
||||
|
||||
if state in ['private', 'unlisted']:
|
||||
if self.is_shared and state != "shared":
|
||||
self.fields['confirm_state'].widget = forms.CheckboxInput()
|
||||
state_index = None
|
||||
for i, layout_item in enumerate(self.helper.layout):
|
||||
if isinstance(layout_item, CustomField) and layout_item.fields[0] == 'state':
|
||||
state_index = i
|
||||
break
|
||||
|
||||
if state_index is not None:
|
||||
layout_items = list(self.helper.layout)
|
||||
layout_items.insert(state_index + 1, CustomField('confirm_state'))
|
||||
self.helper.layout = Layout(*layout_items)
|
||||
|
||||
if not cleaned_data.get('confirm_state'):
|
||||
if state == 'private':
|
||||
error_parts = []
|
||||
if self.has_rbac_categories:
|
||||
rbac_cat_titles = self.instance.category.filter(is_rbac_category=True).values_list('title', flat=True)
|
||||
error_parts.append(f"shared with users that have access to categories: {', '.join(rbac_cat_titles)}")
|
||||
if self.has_custom_permissions:
|
||||
error_parts.append("shared by me with other users (visible in 'Shared by me' page)")
|
||||
|
||||
error_message = f"I understand that changing to Private will remove all sharing. Currently this media is {' and '.join(error_parts)}. All this sharing will be removed."
|
||||
self.add_error('confirm_state', error_message)
|
||||
else:
|
||||
error_message = f"I understand that changing to {state.title()} will maintain existing sharing settings."
|
||||
self.add_error('confirm_state', error_message)
|
||||
|
||||
elif state in ['private', 'unlisted']:
|
||||
custom_permissions = self.instance.permissions.exists()
|
||||
rbac_categories = categories.filter(is_rbac_category=True).values_list('title', flat=True)
|
||||
if rbac_categories or custom_permissions:
|
||||
@@ -189,7 +230,7 @@ class MediaPublishForm(forms.ModelForm):
|
||||
state_index = i
|
||||
break
|
||||
|
||||
if state_index:
|
||||
if state_index is not None:
|
||||
layout_items = list(self.helper.layout)
|
||||
layout_items.insert(state_index + 1, CustomField('confirm_state'))
|
||||
self.helper.layout = Layout(*layout_items)
|
||||
@@ -202,11 +243,24 @@ class MediaPublishForm(forms.ModelForm):
|
||||
error_message = f"I understand that although media state is {state}, the media is also shared by me with other users, that I can see in the 'Shared by me' page"
|
||||
self.add_error('confirm_state', error_message)
|
||||
|
||||
# Convert "shared" state to actual underlying state for saving. we dont keep shared state in DB
|
||||
if state == "shared":
|
||||
cleaned_data["state"] = self.actual_state
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
data = self.cleaned_data
|
||||
state = data.get("state")
|
||||
|
||||
# If transitioning from shared to private, remove all sharing
|
||||
if self.is_shared and state == 'private' and data.get('confirm_state'):
|
||||
# Remove all custom permissions
|
||||
self.instance.permissions.all().delete()
|
||||
# Remove RBAC categories
|
||||
rbac_cats = self.instance.category.filter(is_rbac_category=True)
|
||||
self.instance.category.remove(*rbac_cats)
|
||||
|
||||
if state != self.initial["state"]:
|
||||
self.instance.state = get_next_state(self.user, self.initial["state"], self.instance.state)
|
||||
|
||||
@@ -333,3 +387,35 @@ class ContactForm(forms.Form):
|
||||
if user.is_authenticated:
|
||||
self.fields.pop("name")
|
||||
self.fields.pop("from_email")
|
||||
|
||||
|
||||
class ReplaceMediaForm(forms.Form):
|
||||
new_media_file = forms.FileField(
|
||||
required=True,
|
||||
label="New Media File",
|
||||
help_text="Select a new file to replace the current media",
|
||||
)
|
||||
|
||||
def __init__(self, media_instance, *args, **kwargs):
|
||||
self.media_instance = media_instance
|
||||
super(ReplaceMediaForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = True
|
||||
self.helper.form_class = 'post-form'
|
||||
self.helper.form_method = 'post'
|
||||
self.helper.form_enctype = "multipart/form-data"
|
||||
self.helper.form_show_errors = False
|
||||
self.helper.layout = Layout(
|
||||
CustomField('new_media_file'),
|
||||
)
|
||||
|
||||
self.helper.layout.append(FormActions(Submit('submit', 'Replace Media', css_class='primaryAction')))
|
||||
|
||||
def clean_new_media_file(self):
|
||||
file = self.cleaned_data.get("new_media_file", False)
|
||||
if file:
|
||||
if file.size > settings.UPLOAD_MAX_SIZE:
|
||||
max_size_mb = settings.UPLOAD_MAX_SIZE / (1024 * 1024)
|
||||
raise forms.ValidationError(f"File too large. Maximum size: {max_size_mb:.0f}MB")
|
||||
return file
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "إزالة من القائمة",
|
||||
"Remove tag": "إزالة العلامة",
|
||||
"Remove user": "إزالة المستخدم",
|
||||
"Replace": "",
|
||||
"SAVE": "حفظ",
|
||||
"SEARCH": "بحث",
|
||||
"SHARE": "مشاركة",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "সংরক্ষণ করুন",
|
||||
"SEARCH": "অনুসন্ধান",
|
||||
"SHARE": "শেয়ার করুন",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Fjern fra liste",
|
||||
"Remove tag": "Fjern tag",
|
||||
"Remove user": "Fjern bruger",
|
||||
"Replace": "",
|
||||
"SAVE": "GEM",
|
||||
"SEARCH": "SØG",
|
||||
"SHARE": "DEL",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Aus Liste entfernen",
|
||||
"Remove tag": "Tag entfernen",
|
||||
"Remove user": "Benutzer entfernen",
|
||||
"Replace": "",
|
||||
"SAVE": "SPEICHERN",
|
||||
"SEARCH": "SUCHE",
|
||||
"SHARE": "TEILEN",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Αφαίρεση από λίστα",
|
||||
"Remove tag": "Αφαίρεση ετικέτας",
|
||||
"Remove user": "Αφαίρεση χρήστη",
|
||||
"Replace": "",
|
||||
"SAVE": "ΑΠΟΘΗΚΕΥΣΗ",
|
||||
"SEARCH": "ΑΝΑΖΗΤΗΣΗ",
|
||||
"SHARE": "ΚΟΙΝΟΠΟΙΗΣΗ",
|
||||
|
||||
@@ -165,6 +165,7 @@ translation_strings = {
|
||||
"Recommended": "",
|
||||
"Record Screen": "",
|
||||
"Register": "",
|
||||
"Replace": "",
|
||||
"Remove category": "",
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Eliminar de la lista",
|
||||
"Remove tag": "Eliminar etiqueta",
|
||||
"Remove user": "Eliminar usuario",
|
||||
"Replace": "",
|
||||
"SAVE": "GUARDAR",
|
||||
"SEARCH": "BUSCAR",
|
||||
"SHARE": "COMPARTIR",
|
||||
|
||||
@@ -163,6 +163,7 @@ translation_strings = {
|
||||
"Remove from list": "Supprimer de la liste",
|
||||
"Remove tag": "Supprimer le tag",
|
||||
"Remove user": "Supprimer l'utilisateur",
|
||||
"Replace": "",
|
||||
"SAVE": "ENREGISTRER",
|
||||
"SEARCH": "RECHERCHER",
|
||||
"SHARE": "PARTAGER",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "שמור",
|
||||
"SEARCH": "חפש",
|
||||
"SHARE": "שתף",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "सूची से हटाएं",
|
||||
"Remove tag": "टैग हटाएं",
|
||||
"Remove user": "उपयोगकर्ता हटाएं",
|
||||
"Replace": "",
|
||||
"SAVE": "सहेजें",
|
||||
"SEARCH": "खोजें",
|
||||
"SHARE": "साझा करें",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Hapus dari daftar",
|
||||
"Remove tag": "Hapus tag",
|
||||
"Remove user": "Hapus pengguna",
|
||||
"Replace": "",
|
||||
"SAVE": "SIMPAN",
|
||||
"SEARCH": "CARI",
|
||||
"SHARE": "BAGIKAN",
|
||||
|
||||
@@ -163,6 +163,7 @@ translation_strings = {
|
||||
"Remove from list": "Rimuovi dalla lista",
|
||||
"Remove tag": "Rimuovi tag",
|
||||
"Remove user": "Rimuovi utente",
|
||||
"Replace": "",
|
||||
"SAVE": "SALVA",
|
||||
"SEARCH": "CERCA",
|
||||
"SHARE": "CONDIVIDI",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "リストから削除",
|
||||
"Remove tag": "タグを削除",
|
||||
"Remove user": "ユーザーを削除",
|
||||
"Replace": "",
|
||||
"SAVE": "保存",
|
||||
"SEARCH": "検索",
|
||||
"SHARE": "共有",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "목록에서 제거",
|
||||
"Remove tag": "태그 제거",
|
||||
"Remove user": "사용자 제거",
|
||||
"Replace": "",
|
||||
"SAVE": "저장",
|
||||
"SEARCH": "검색",
|
||||
"SHARE": "공유",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Verwijderen uit lijst",
|
||||
"Remove tag": "Tag verwijderen",
|
||||
"Remove user": "Gebruiker verwijderen",
|
||||
"Replace": "",
|
||||
"SAVE": "OPSLAAN",
|
||||
"SEARCH": "ZOEKEN",
|
||||
"SHARE": "DELEN",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Remover da lista",
|
||||
"Remove tag": "Remover tag",
|
||||
"Remove user": "Remover usuário",
|
||||
"Replace": "",
|
||||
"SAVE": "SALVAR",
|
||||
"SEARCH": "PESQUISAR",
|
||||
"SHARE": "COMPARTILHAR",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Удалить из списка",
|
||||
"Remove tag": "Удалить тег",
|
||||
"Remove user": "Удалить пользователя",
|
||||
"Replace": "",
|
||||
"SAVE": "СОХРАНИТЬ",
|
||||
"SEARCH": "ПОИСК",
|
||||
"SHARE": "ПОДЕЛИТЬСЯ",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Odstrani s seznama",
|
||||
"Remove tag": "Odstrani oznako",
|
||||
"Remove user": "Odstrani uporabnika",
|
||||
"Replace": "",
|
||||
"SAVE": "SHRANI",
|
||||
"SEARCH": "ISKANJE",
|
||||
"SHARE": "DELI",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "Listeden kaldır",
|
||||
"Remove tag": "Etiketi kaldır",
|
||||
"Remove user": "Kullanıcıyı kaldır",
|
||||
"Replace": "",
|
||||
"SAVE": "KAYDET",
|
||||
"SEARCH": "ARA",
|
||||
"SHARE": "PAYLAŞ",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "فہرست سے ہٹائیں",
|
||||
"Remove tag": "ٹیگ ہٹائیں",
|
||||
"Remove user": "صارف ہٹائیں",
|
||||
"Replace": "",
|
||||
"SAVE": "محفوظ کریں",
|
||||
"SEARCH": "تلاش کریں",
|
||||
"SHARE": "شیئر کریں",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "保存",
|
||||
"SEARCH": "搜索",
|
||||
"SHARE": "分享",
|
||||
|
||||
@@ -162,6 +162,7 @@ translation_strings = {
|
||||
"Remove from list": "",
|
||||
"Remove tag": "",
|
||||
"Remove user": "",
|
||||
"Replace": "",
|
||||
"SAVE": "儲存",
|
||||
"SEARCH": "搜尋",
|
||||
"SHARE": "分享",
|
||||
|
||||
@@ -270,7 +270,9 @@ class Media(models.Model):
|
||||
if self.media_file != self.__original_media_file:
|
||||
# set this otherwise gets to infinite loop
|
||||
self.__original_media_file = self.media_file
|
||||
self.media_init()
|
||||
from .. import tasks
|
||||
|
||||
tasks.media_init.apply_async(args=[self.friendly_token], countdown=5)
|
||||
|
||||
# for video files, if user specified a different time
|
||||
# to automatically grub thumbnail
|
||||
@@ -417,6 +419,11 @@ class Media(models.Model):
|
||||
self.media_type = "image"
|
||||
elif kind == "pdf":
|
||||
self.media_type = "pdf"
|
||||
elif kind == "audio":
|
||||
self.media_type = "audio"
|
||||
elif kind == "video":
|
||||
self.media_type = "video"
|
||||
|
||||
if self.media_type in ["image", "pdf"]:
|
||||
self.encoding_status = "success"
|
||||
else:
|
||||
|
||||
@@ -101,10 +101,17 @@ class MediaSerializer(serializers.ModelSerializer):
|
||||
class SingleMediaSerializer(serializers.ModelSerializer):
|
||||
user = serializers.ReadOnlyField(source="user.username")
|
||||
url = serializers.SerializerMethodField()
|
||||
is_shared = serializers.SerializerMethodField()
|
||||
|
||||
def get_url(self, obj):
|
||||
return self.context["request"].build_absolute_uri(obj.get_absolute_url())
|
||||
|
||||
def get_is_shared(self, obj):
|
||||
"""Check if media has custom permissions or RBAC categories"""
|
||||
custom_permissions = obj.permissions.exists()
|
||||
rbac_categories = obj.category.filter(is_rbac_category=True).exists()
|
||||
return custom_permissions or rbac_categories
|
||||
|
||||
class Meta:
|
||||
model = Media
|
||||
read_only_fields = (
|
||||
@@ -133,6 +140,7 @@ class SingleMediaSerializer(serializers.ModelSerializer):
|
||||
"edit_date",
|
||||
"media_type",
|
||||
"state",
|
||||
"is_shared",
|
||||
"duration",
|
||||
"thumbnail_url",
|
||||
"poster_url",
|
||||
|
||||
@@ -625,6 +625,18 @@ def create_hls(friendly_token):
|
||||
return True
|
||||
|
||||
|
||||
@task(name="media_init", queue="short_tasks")
|
||||
def media_init(friendly_token):
|
||||
try:
|
||||
media = Media.objects.get(friendly_token=friendly_token)
|
||||
except: # noqa
|
||||
logger.info("failed to get media with friendly_token %s" % friendly_token)
|
||||
return False
|
||||
media.media_init()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@task(name="check_running_states", queue="short_tasks")
|
||||
def check_running_states():
|
||||
# Experimental - unused
|
||||
|
||||
@@ -20,6 +20,7 @@ urlpatterns = [
|
||||
re_path(r"^contact$", views.contact, name="contact"),
|
||||
re_path(r"^publish", views.publish_media, name="publish_media"),
|
||||
re_path(r"^edit_chapters", views.edit_chapters, name="edit_chapters"),
|
||||
re_path(r"^replace_media", views.replace_media, name="replace_media"),
|
||||
re_path(r"^edit_video", views.edit_video, name="edit_video"),
|
||||
re_path(r"^edit", views.edit_media, name="edit_media"),
|
||||
re_path(r"^embed", views.embed_media, name="get_embed"),
|
||||
|
||||
@@ -32,6 +32,7 @@ from .pages import members # noqa: F401
|
||||
from .pages import publish_media # noqa: F401
|
||||
from .pages import recommended_media # noqa: F401
|
||||
from .pages import record_screen # noqa: F401
|
||||
from .pages import replace_media # noqa: F401
|
||||
from .pages import search # noqa: F401
|
||||
from .pages import setlanguage # noqa: F401
|
||||
from .pages import sitemap # noqa: F401
|
||||
|
||||
@@ -226,8 +226,13 @@ class MediaList(APIView):
|
||||
elif duration == '60-120':
|
||||
media = media.filter(duration__gte=3600)
|
||||
|
||||
if publish_state and publish_state in ['private', 'public', 'unlisted']:
|
||||
media = media.filter(state=publish_state)
|
||||
if publish_state:
|
||||
if publish_state == 'shared':
|
||||
# Filter media that have custom permissions OR RBAC categories
|
||||
shared_conditions = Q(permissions__isnull=False) | Q(category__is_rbac_category=True)
|
||||
media = media.filter(shared_conditions).distinct()
|
||||
elif publish_state in ['private', 'public', 'unlisted']:
|
||||
media = media.filter(state=publish_state)
|
||||
|
||||
if not already_sorted:
|
||||
media = media.order_by(f"{ordering}{sort_by}")
|
||||
@@ -799,13 +804,14 @@ class MediaDetail(APIView):
|
||||
|
||||
serializer = MediaSerializer(media, data=request.data, context={"request": request})
|
||||
if serializer.is_valid():
|
||||
serializer.save(user=request.user)
|
||||
# no need to update the media file itself, only the metadata
|
||||
# if request.data.get('media_file'):
|
||||
# media_file = request.data["media_file"]
|
||||
# serializer.save(user=request.user, media_file=media_file)
|
||||
# media_file = request.data["media_file"]
|
||||
# media.state = helpers.get_default_state(request.user)
|
||||
# media.listable = False
|
||||
# serializer.save(user=request.user, media_file=media_file)
|
||||
# else:
|
||||
# serializer.save(user=request.user)
|
||||
# serializer.save(user=request.user)
|
||||
serializer.save(user=request.user)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
@@ -18,6 +19,7 @@ from ..forms import (
|
||||
EditSubtitleForm,
|
||||
MediaMetadataForm,
|
||||
MediaPublishForm,
|
||||
ReplaceMediaForm,
|
||||
SubtitleForm,
|
||||
WhisperSubtitlesForm,
|
||||
)
|
||||
@@ -363,6 +365,76 @@ def publish_media(request):
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def replace_media(request):
|
||||
"""Replace media file"""
|
||||
|
||||
if not getattr(settings, 'ALLOW_MEDIA_REPLACEMENT', False):
|
||||
return HttpResponseRedirect("/")
|
||||
|
||||
friendly_token = request.GET.get("m", "").strip()
|
||||
if not friendly_token:
|
||||
return HttpResponseRedirect("/")
|
||||
media = Media.objects.filter(friendly_token=friendly_token).first()
|
||||
|
||||
if not media:
|
||||
return HttpResponseRedirect("/")
|
||||
|
||||
if not (request.user.has_contributor_access_to_media(media) or is_mediacms_editor(request.user)):
|
||||
return HttpResponseRedirect("/")
|
||||
|
||||
if not is_media_allowed_type(media):
|
||||
return HttpResponseRedirect(media.get_absolute_url())
|
||||
|
||||
if request.method == "POST":
|
||||
form = ReplaceMediaForm(media, request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
new_media_file = form.cleaned_data.get("new_media_file")
|
||||
|
||||
media.encodings.all().delete()
|
||||
|
||||
if media.thumbnail:
|
||||
helpers.rm_file(media.thumbnail.path)
|
||||
media.thumbnail = None
|
||||
if media.poster:
|
||||
helpers.rm_file(media.poster.path)
|
||||
media.poster = None
|
||||
if media.uploaded_thumbnail:
|
||||
helpers.rm_file(media.uploaded_thumbnail.path)
|
||||
media.uploaded_thumbnail = None
|
||||
if media.uploaded_poster:
|
||||
helpers.rm_file(media.uploaded_poster.path)
|
||||
media.uploaded_poster = None
|
||||
if media.sprites:
|
||||
helpers.rm_file(media.sprites.path)
|
||||
media.sprites = None
|
||||
if media.preview_file_path:
|
||||
helpers.rm_file(media.preview_file_path)
|
||||
media.preview_file_path = ""
|
||||
|
||||
if media.hls_file:
|
||||
hls_dir = os.path.dirname(media.hls_file)
|
||||
helpers.rm_dir(hls_dir)
|
||||
media.hls_file = ""
|
||||
|
||||
media.media_file = new_media_file
|
||||
|
||||
media.listable = False
|
||||
media.state = helpers.get_default_state(request.user)
|
||||
media.save()
|
||||
|
||||
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Media file was replaced successfully"))
|
||||
return HttpResponseRedirect(media.get_absolute_url())
|
||||
else:
|
||||
form = ReplaceMediaForm(media)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"cms/replace_media.html",
|
||||
{"form": form, "media_object": media, "add_subtitle_url": media.add_subtitle_url},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_chapters(request):
|
||||
"""Edit chapters"""
|
||||
|
||||
39
files/widgets.py
Normal file
39
files/widgets.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import json
|
||||
|
||||
from django import forms
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
||||
class CategoryModalWidget(forms.SelectMultiple):
|
||||
"""Two-panel category selector with modal"""
|
||||
|
||||
class Media:
|
||||
css = {'all': ('css/category_modal.css',)}
|
||||
js = ('js/category_modal.js',)
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
# Get all categories as JSON
|
||||
categories = []
|
||||
for opt_value, opt_label in self.choices:
|
||||
if opt_value: # Skip empty choice
|
||||
categories.append({'id': str(opt_value), 'title': str(opt_label)})
|
||||
|
||||
all_categories_json = json.dumps(categories)
|
||||
selected_ids_json = json.dumps([str(v) for v in (value or [])])
|
||||
|
||||
html = f'''<div class="category-widget" data-name="{name}">
|
||||
<div class="category-content">
|
||||
<div class="category-panel">
|
||||
<input type="text" class="category-search" placeholder="Search categories...">
|
||||
<div class="category-list scrollable" data-panel="left"></div>
|
||||
</div>
|
||||
<div class="category-panel">
|
||||
<h3>Selected Categories</h3>
|
||||
<div class="category-list scrollable" data-panel="right"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden-inputs"></div>
|
||||
<script type="application/json" class="category-data">{{"all":{all_categories_json},"selected":{selected_ids_json}}}</script>
|
||||
</div>'''
|
||||
|
||||
return mark_safe(html)
|
||||
Reference in New Issue
Block a user