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(
+ """
+
+ """
+ ),
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);
}
+