mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-04-23 15:58:08 -04:00
state
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
VERSION = "8.991"
|
||||
VERSION = "8.995"
|
||||
|
||||
+82
-74
@@ -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(
|
||||
"""
|
||||
<div class="form-group{% if form.state.errors or form.confirm_state.errors %} has-error{% endif %}">
|
||||
<div class="control-label-container">
|
||||
<label class="control-label">State</label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="state-options">
|
||||
{% for val, lbl in form.fields.state.choices %}{% if val == 'private' %}
|
||||
<label class="state-option">
|
||||
<input type="radio" name="state" value="private"
|
||||
{% if form.state.value == 'private' %}checked{% endif %}>
|
||||
Private
|
||||
</label>
|
||||
{% endif %}{% endfor %}
|
||||
{% for val, lbl in form.fields.state.choices %}{% if val == 'unlisted' %}
|
||||
<label class="state-option">
|
||||
<input type="radio" name="state" value="unlisted"
|
||||
{% if form.state.value == 'unlisted' %}checked{% endif %}>
|
||||
Unlisted
|
||||
</label>
|
||||
{% endif %}{% endfor %}
|
||||
<label class="state-option shared-option">
|
||||
<input type="checkbox" name="shared" id="id_shared"
|
||||
{% if form.shared.value %}checked{% endif %}>
|
||||
Shared
|
||||
</label>
|
||||
{% for val, lbl in form.fields.state.choices %}{% if val == 'public' %}
|
||||
<label class="state-option">
|
||||
<input type="radio" name="state" value="public"
|
||||
{% if form.state.value == 'public' %}checked{% endif %}>
|
||||
Public
|
||||
</label>
|
||||
{% endif %}{% endfor %}
|
||||
</div>
|
||||
{% if form.state.errors %}
|
||||
<div class="error-container" style="margin-top:0.5rem;">
|
||||
{% for error in form.state.errors %}<p class="invalid-feedback">{{ error }}</p>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if form.confirm_state.errors %}
|
||||
<div class="confirm-state-section">
|
||||
<label for="id_confirm_state">
|
||||
<input type="checkbox" name="confirm_state" id="id_confirm_state"
|
||||
{% if form.confirm_state.value %}checked{% endif %}>
|
||||
<span>{{ form.confirm_state.errors.0 }}</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
"""
|
||||
),
|
||||
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)
|
||||
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div class="form-group{% if field.errors %} has-error{% endif %}">
|
||||
|
||||
@@ -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 %}
|
||||
<script>
|
||||
(function() {
|
||||
var url = new URL(window.location.href);
|
||||
if (sessionStorage.getItem('lms_embed_mode') === 'true' && url.searchParams.get('mode') !== 'lms_embed_mode') {
|
||||
url.searchParams.set('mode', 'lms_embed_mode');
|
||||
window.location.replace(url.toString());
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
#div_id_state ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#div_id_state ul li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
||||
{% endblock extra_head %}
|
||||
|
||||
{% block innercontent %}
|
||||
<div class="user-action-form-wrap">
|
||||
{% include "cms/media_nav.html" with active_tab="publish" %}
|
||||
<div style="max-width: 900px; margin: 0 auto; padding: 20px; border-radius: 8px; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);">
|
||||
{% csrf_token %}
|
||||
{% crispy form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock innercontent %}
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block headtitle %}Publish media - {{PORTAL_NAME}}{% endblock headtitle %}
|
||||
|
||||
{% block headermeta %}{% endblock headermeta %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script>
|
||||
(function() {
|
||||
var url = new URL(window.location.href);
|
||||
if (sessionStorage.getItem('lms_embed_mode') === 'true' && url.searchParams.get('mode') !== 'lms_embed_mode') {
|
||||
url.searchParams.set('mode', 'lms_embed_mode');
|
||||
window.location.replace(url.toString());
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
.state-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.state-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.state-option input { cursor: pointer; }
|
||||
.shared-option {
|
||||
border-left: 1px solid #dee2e6;
|
||||
padding-left: 1.5rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
.confirm-state-section {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.confirm-state-section label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.confirm-state-section input[type="checkbox"] {
|
||||
margin-top: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock extra_head %}
|
||||
|
||||
{% block innercontent %}
|
||||
<div class="user-action-form-wrap">
|
||||
{% include "cms/media_nav.html" with active_tab="publish" %}
|
||||
<div style="max-width: 900px; margin: 0 auto; padding: 20px; border-radius: 8px; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);">
|
||||
{% crispy form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock innercontent %}
|
||||
|
||||
Reference in New Issue
Block a user