From 1261719996d4f29fa0c36d72a8c97ffee223240d Mon Sep 17 00:00:00 2001 From: Markos Gogoulos Date: Mon, 20 Apr 2026 09:30:27 +0300 Subject: [PATCH] state --- cms/version.py | 2 +- files/forms.py | 156 +++++++++++++------------ files/models/media.py | 6 + templates/cms/crispy_custom_field.html | 1 + templates/cms/publish_media.html | 113 +++++++++++------- 5 files changed, 159 insertions(+), 119 deletions(-) diff --git a/cms/version.py b/cms/version.py index 688d2efe..9a0c4325 100644 --- a/cms/version.py +++ b/cms/version.py @@ -1 +1 @@ -VERSION = "8.991" +VERSION = "8.995" diff --git a/files/forms.py b/files/forms.py index e615f54a..f7f5d3ba 100644 --- a/files/forms.py +++ b/files/forms.py @@ -5,7 +5,7 @@ from django import forms from django.conf import settings from .methods import get_next_state, is_mediacms_editor -from .models import MEDIA_STATES, Category, Media, Subtitle, Tag +from .models import MEDIA_STATES, Category, Media, MediaPermission, Subtitle, Tag from .widgets import CategoryModalWidget @@ -116,6 +116,7 @@ class MediaMetadataForm(forms.ModelForm): class MediaPublishForm(forms.ModelForm): confirm_state = forms.BooleanField(required=False, initial=False, label="Acknowledge sharing status", help_text="") + shared = forms.BooleanField(required=False, initial=False, label="Shared") class Meta: model = Media @@ -131,10 +132,7 @@ class MediaPublishForm(forms.ModelForm): self.request = kwargs.pop('request', None) 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 + self.was_shared = self.instance.is_shared if self.instance.pk else False if not is_mediacms_editor(user): for field in ["featured", "reported_times", "is_reviewed"]: @@ -148,12 +146,8 @@ 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" + self.fields["shared"].initial = self.was_shared + self.initial["shared"] = self.was_shared if getattr(settings, 'USE_RBAC', False) and 'category' in self.fields: if is_mediacms_editor(user): @@ -189,7 +183,59 @@ class MediaPublishForm(forms.ModelForm): self.helper.form_show_errors = False self.helper.layout = Layout( CustomField('category'), - CustomField('state'), + HTML( + """ +
+
+ +
+
+
+ {% for val, lbl in form.fields.state.choices %}{% if val == 'private' %} + + {% endif %}{% endfor %} + {% for val, lbl in form.fields.state.choices %}{% if val == 'unlisted' %} + + {% endif %}{% endfor %} + + {% for val, lbl in form.fields.state.choices %}{% if val == 'public' %} + + {% endif %}{% endfor %} +
+ {% if form.state.errors %} +
+ {% for error in form.state.errors %}

{{ error }}

{% endfor %} +
+ {% endif %} +
+ {% if form.confirm_state.errors %} +
+ +
+ {% endif %} +
+ """ + ), CustomField('featured'), CustomField('reported_times'), CustomField('is_reviewed'), @@ -217,80 +263,42 @@ class MediaPublishForm(forms.ModelForm): def clean(self): cleaned_data = super().clean() state = cleaned_data.get("state") - categories = cleaned_data.get("category") + shared = cleaned_data.get("shared") - 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: - 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 rbac_categories: - error_message = f"I understand that although media state is {state}, the media is also shared with users that have access to categories: {', '.join(rbac_categories)}" - self.add_error('confirm_state', error_message) - if custom_permissions: - 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 + if self.was_shared and not shared and not cleaned_data.get('confirm_state'): + if state == 'private': + error_parts = [] + rbac_cat_titles = list(self.instance.category.filter(is_rbac_category=True).values_list('title', flat=True)) + if rbac_cat_titles: + error_parts.append(f"shared with users that have access to categories: {', '.join(rbac_cat_titles)}") + if self.instance.permissions.exists(): + error_parts.append("shared by me with other users (visible in 'Shared by me' page)") + detail = f" Currently this media is {' and '.join(error_parts)}." if error_parts else "" + self.add_error('confirm_state', f"I understand that changing to Private will remove all sharing.{detail}") + else: + self.add_error('confirm_state', "I understand that unchecking Shared will affect existing sharing settings.") return cleaned_data def save(self, *args, **kwargs): data = self.cleaned_data state = data.get("state") + shared = data.get("shared") - # 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 + if shared: + if self.request and self.request.user.is_authenticated: + MediaPermission.objects.get_or_create( + media=self.instance, + user=self.request.user, + defaults={'owner_user': self.request.user, 'permission': 'owner'}, + ) + elif state == 'private': 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) + if state != self.initial.get("state"): + self.instance.state = get_next_state(self.user, self.initial.get("state"), self.instance.state) media = super(MediaPublishForm, self).save(*args, **kwargs) diff --git a/files/models/media.py b/files/models/media.py index 9f582cca..d37895f9 100644 --- a/files/models/media.py +++ b/files/models/media.py @@ -965,6 +965,12 @@ class Media(models.Model): return chapter_data.chapter_data return data + @property + def is_shared(self): + if not self.pk: + return False + return self.permissions.exists() or self.category.filter(is_rbac_category=True).exists() + class MediaPermission(models.Model): """Model to store user permissions for media""" diff --git a/templates/cms/crispy_custom_field.html b/templates/cms/crispy_custom_field.html index dc58918d..cc9e716a 100644 --- a/templates/cms/crispy_custom_field.html +++ b/templates/cms/crispy_custom_field.html @@ -78,6 +78,7 @@ box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25); } +
diff --git a/templates/cms/publish_media.html b/templates/cms/publish_media.html index cee5745a..f0fba23f 100644 --- a/templates/cms/publish_media.html +++ b/templates/cms/publish_media.html @@ -1,44 +1,69 @@ -{% extends "base.html" %} -{% load crispy_forms_tags %} -{% load static %} - -{% block headtitle %}Publish media - {{PORTAL_NAME}}{% endblock headtitle %} - -{% block headermeta %}{% endblock headermeta %} - -{% block extra_head %} - - -{% endblock extra_head %} - -{% block innercontent %} -
- {% include "cms/media_nav.html" with active_tab="publish" %} -
- {% csrf_token %} - {% crispy form %} -
-
-{% endblock innercontent %} \ No newline at end of file +{% extends "base.html" %} +{% load crispy_forms_tags %} +{% load static %} + +{% block headtitle %}Publish media - {{PORTAL_NAME}}{% endblock headtitle %} + +{% block headermeta %}{% endblock headermeta %} + +{% block extra_head %} + + +{% endblock extra_head %} + +{% block innercontent %} +
+ {% include "cms/media_nav.html" with active_tab="publish" %} +
+ {% crispy form %} +
+
+{% endblock innercontent %}