2025-06-11 14:48:30 +03:00
from crispy_forms . bootstrap import FormActions
from crispy_forms . helper import FormHelper
2025-09-01 20:06:49 +03:00
from crispy_forms . layout import HTML , Field , Layout , Submit
2020-12-15 23:33:43 +02:00
from django import forms
2025-04-05 12:44:21 +03:00
from django . conf import settings
2021-05-26 18:35:21 +03:00
from . methods import get_next_state , is_mediacms_editor
2025-06-11 14:48:30 +03:00
from . models import MEDIA_STATES , Category , Media , Subtitle
class CustomField ( Field ) :
template = ' cms/crispy_custom_field.html '
2020-12-15 23:33:43 +02:00
class MultipleSelect ( forms . CheckboxSelectMultiple ) :
input_type = " checkbox "
2025-06-11 14:48:30 +03:00
class MediaMetadataForm ( forms . ModelForm ) :
new_tags = forms . CharField ( label = " Tags " , help_text = " a comma separated list of tags. " , required = False )
2020-12-15 23:33:43 +02:00
class Meta :
model = Media
fields = (
2025-06-24 11:13:33 +03:00
" friendly_token " ,
2020-12-15 23:33:43 +02:00
" title " ,
" new_tags " ,
" add_date " ,
" uploaded_poster " ,
" description " ,
" enable_comments " ,
" thumbnail_time " ,
2025-06-11 14:48:30 +03:00
)
widgets = {
" new_tags " : MultipleSelect ( ) ,
" description " : forms . Textarea ( attrs = { ' rows ' : 4 } ) ,
2025-08-17 19:18:47 +03:00
" add_date " : forms . DateTimeInput ( attrs = { ' type ' : ' datetime-local ' , ' step ' : ' 1 ' } , format = ' % Y- % m- %d T % H: % M: % S ' ) ,
2025-06-11 14:48:30 +03:00
" thumbnail_time " : forms . NumberInput ( attrs = { ' min ' : 0 , ' step ' : 0.1 } ) ,
}
labels = {
2025-06-24 11:13:33 +03:00
" friendly_token " : " Slug " ,
2025-06-11 14:48:30 +03:00
" uploaded_poster " : " Poster Image " ,
" thumbnail_time " : " Thumbnail Time (seconds) " ,
}
help_texts = {
" title " : " " ,
2025-06-24 11:13:33 +03:00
" friendly_token " : " Media URL slug " ,
2025-06-11 14:48:30 +03:00
" thumbnail_time " : " Select the time in seconds for the video thumbnail " ,
" uploaded_poster " : " Maximum file size: 5MB " ,
}
def __init__ ( self , user , * args , * * kwargs ) :
self . user = user
super ( MediaMetadataForm , self ) . __init__ ( * args , * * kwargs )
2025-06-24 11:13:33 +03:00
if not getattr ( settings , ' ALLOW_CUSTOM_MEDIA_URLS ' , False ) :
self . fields . pop ( " friendly_token " )
2025-06-11 14:48:30 +03:00
if self . instance . media_type != " video " :
self . fields . pop ( " thumbnail_time " )
if self . instance . media_type == " image " :
self . fields . pop ( " uploaded_poster " )
self . fields [ " new_tags " ] . initial = " , " . join ( [ tag . title for tag in self . instance . tags . all ( ) ] )
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
2025-08-07 13:21:12 +03:00
layout_fields = [
2025-06-11 14:48:30 +03:00
CustomField ( ' title ' ) ,
CustomField ( ' new_tags ' ) ,
CustomField ( ' add_date ' ) ,
CustomField ( ' description ' ) ,
CustomField ( ' enable_comments ' ) ,
2025-08-07 13:21:12 +03:00
]
if self . instance . media_type != " image " :
layout_fields . append ( CustomField ( ' uploaded_poster ' ) )
self . helper . layout = Layout ( * layout_fields )
2025-06-11 14:48:30 +03:00
if self . instance . media_type == " video " :
self . helper . layout . append ( CustomField ( ' thumbnail_time ' ) )
2025-06-24 11:13:33 +03:00
if getattr ( settings , ' ALLOW_CUSTOM_MEDIA_URLS ' , False ) :
self . helper . layout . insert ( 0 , CustomField ( ' friendly_token ' ) )
2025-06-11 14:48:30 +03:00
self . helper . layout . append ( FormActions ( Submit ( ' submit ' , ' Update Media ' , css_class = ' primaryAction ' ) ) )
2025-06-24 11:13:33 +03:00
def clean_friendly_token ( self ) :
token = self . cleaned_data . get ( " friendly_token " , " " ) . strip ( )
if token :
if not all ( c . isalnum ( ) or c in " -_ " for c in token ) :
raise forms . ValidationError ( " Slug can only contain alphanumeric characters, underscores, or hyphens. " )
if Media . objects . filter ( friendly_token = token ) . exclude ( pk = self . instance . pk ) . exists ( ) :
raise forms . ValidationError ( " This slug is already in use. Please choose a different one. " )
return token
2025-06-11 14:48:30 +03:00
def clean_uploaded_poster ( self ) :
image = self . cleaned_data . get ( " uploaded_poster " , False )
if image :
if image . size > 5 * 1024 * 1024 :
raise forms . ValidationError ( " Image file too large ( > 5mb ) " )
return image
def save ( self , * args , * * kwargs ) :
data = self . cleaned_data # noqa
media = super ( MediaMetadataForm , self ) . save ( * args , * * kwargs )
return media
class MediaPublishForm ( forms . ModelForm ) :
confirm_state = forms . BooleanField ( required = False , initial = False , label = " Acknowledge sharing status " , help_text = " " )
class Meta :
model = Media
2025-09-01 15:11:38 +03:00
fields = ( " category " , " state " , " featured " , " reported_times " , " is_reviewed " , " allow_download " )
2025-06-11 14:48:30 +03:00
2020-12-15 23:33:43 +02:00
widgets = {
2025-06-11 14:48:30 +03:00
" category " : MultipleSelect ( ) ,
2020-12-15 23:33:43 +02:00
}
def __init__ ( self , user , * args , * * kwargs ) :
self . user = user
2025-06-11 14:48:30 +03:00
super ( MediaPublishForm , self ) . __init__ ( * args , * * kwargs )
2025-09-01 15:11:38 +03:00
2020-12-15 23:33:43 +02:00
if not is_mediacms_editor ( user ) :
2025-06-11 14:48:30 +03:00
for field in [ " featured " , " reported_times " , " is_reviewed " ] :
self . fields [ field ] . disabled = True
self . fields [ field ] . widget . attrs [ ' class ' ] = ' read-only-field '
self . fields [ field ] . widget . attrs [ ' title ' ] = " This field can only be modified by MediaCMS admins or editors "
if settings . PORTAL_WORKFLOW not in [ " public " ] :
valid_states = [ " unlisted " , " private " ]
if self . instance . state and self . instance . state not in valid_states :
valid_states . append ( self . instance . state )
self . fields [ " state " ] . choices = [ ( state , dict ( MEDIA_STATES ) . get ( state , state ) ) for state in valid_states ]
2025-04-05 12:44:21 +03:00
if getattr ( settings , ' USE_RBAC ' , False ) and ' category ' in self . fields :
if is_mediacms_editor ( user ) :
pass
else :
self . fields [ ' category ' ] . initial = self . instance . category . all ( )
non_rbac_categories = Category . objects . filter ( is_rbac_category = False )
rbac_categories = user . get_rbac_categories_as_contributor ( )
combined_category_ids = list ( non_rbac_categories . values_list ( ' id ' , flat = True ) ) + list ( rbac_categories . values_list ( ' id ' , flat = True ) )
if self . instance . pk :
instance_category_ids = list ( self . instance . category . all ( ) . values_list ( ' id ' , flat = True ) )
combined_category_ids = list ( set ( combined_category_ids + instance_category_ids ) )
self . fields [ ' category ' ] . queryset = Category . objects . filter ( id__in = combined_category_ids ) . order_by ( ' title ' )
2025-06-11 14:48:30 +03:00
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 ( ' category ' ) ,
CustomField ( ' state ' ) ,
CustomField ( ' featured ' ) ,
CustomField ( ' reported_times ' ) ,
CustomField ( ' is_reviewed ' ) ,
CustomField ( ' allow_download ' ) ,
)
2020-12-15 23:33:43 +02:00
2025-06-11 14:48:30 +03:00
self . helper . layout . append ( FormActions ( Submit ( ' submit ' , ' Publish Media ' , css_class = ' primaryAction ' ) ) )
def clean ( self ) :
cleaned_data = super ( ) . clean ( )
state = cleaned_data . get ( " state " )
categories = cleaned_data . get ( " category " )
if getattr ( settings , ' USE_RBAC ' , False ) and ' category ' in self . fields :
rbac_categories = categories . filter ( is_rbac_category = True ) . values_list ( ' title ' , flat = True )
if rbac_categories and state in [ ' private ' , ' unlisted ' ] :
# Make the confirm_state field visible and add it to the layout
self . fields [ ' confirm_state ' ] . widget = forms . CheckboxInput ( )
# add it after the state field
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 :
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 ' ) :
error_message = f " I understand that although media state is { state } , the media is also shared with users that have access to the following categories: { ' , ' . join ( rbac_categories ) } "
self . add_error ( ' confirm_state ' , error_message )
return cleaned_data
2020-12-15 23:33:43 +02:00
def save ( self , * args , * * kwargs ) :
data = self . cleaned_data
state = data . get ( " state " )
if state != self . initial [ " state " ] :
2021-05-26 18:35:21 +03:00
self . instance . state = get_next_state ( self . user , self . initial [ " state " ] , self . instance . state )
2020-12-15 23:33:43 +02:00
2025-06-11 14:48:30 +03:00
media = super ( MediaPublishForm , self ) . save ( * args , * * kwargs )
2020-12-15 23:33:43 +02:00
return media
2025-09-01 15:11:38 +03:00
class WhisperSubtitlesForm ( forms . ModelForm ) :
class Meta :
model = Media
fields = (
" allow_whisper_transcribe " ,
" allow_whisper_transcribe_and_translate " ,
)
labels = {
2025-09-04 13:39:41 +03:00
" allow_whisper_transcribe " : " Transcription " ,
" allow_whisper_transcribe_and_translate " : " English Translation " ,
2025-09-01 15:11:38 +03:00
}
help_texts = {
2025-09-04 13:39:41 +03:00
" allow_whisper_transcribe " : " " ,
" allow_whisper_transcribe_and_translate " : " " ,
2025-09-01 15:11:38 +03:00
}
def __init__ ( self , user , * args , * * kwargs ) :
self . user = user
super ( WhisperSubtitlesForm , self ) . __init__ ( * args , * * kwargs )
if self . instance . allow_whisper_transcribe :
self . fields [ ' allow_whisper_transcribe ' ] . widget . attrs [ ' readonly ' ] = True
self . fields [ ' allow_whisper_transcribe ' ] . widget . attrs [ ' disabled ' ] = True
if self . instance . allow_whisper_transcribe_and_translate :
self . fields [ ' allow_whisper_transcribe_and_translate ' ] . widget . attrs [ ' readonly ' ] = True
self . fields [ ' allow_whisper_transcribe_and_translate ' ] . widget . attrs [ ' disabled ' ] = True
2025-09-01 20:06:49 +03:00
both_readonly = self . instance . allow_whisper_transcribe and self . instance . allow_whisper_transcribe_and_translate
2025-09-01 15:11:38 +03:00
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 ( ' allow_whisper_transcribe ' ) ,
CustomField ( ' allow_whisper_transcribe_and_translate ' ) ,
)
2025-09-01 20:06:49 +03:00
if not both_readonly :
self . helper . layout . append ( FormActions ( Submit ( ' submit_whisper ' , ' Submit ' , css_class = ' primaryAction ' ) ) )
else :
# Optional: Add a disabled button with explanatory text
self . helper . layout . append (
FormActions ( Submit ( ' submit_whisper ' , ' Submit ' , css_class = ' primaryAction ' , disabled = True ) , HTML ( ' <small class= " text-muted " >Cannot submit - both options are already enabled</small> ' ) )
)
2025-09-01 15:11:38 +03:00
def clean_allow_whisper_transcribe ( self ) :
# Ensure the field value doesn't change if it was originally True
if self . instance and self . instance . allow_whisper_transcribe :
return self . instance . allow_whisper_transcribe
return self . cleaned_data [ ' allow_whisper_transcribe ' ]
def clean_allow_whisper_transcribe_and_translate ( self ) :
# Ensure the field value doesn't change if it was originally True
if self . instance and self . instance . allow_whisper_transcribe_and_translate :
return self . instance . allow_whisper_transcribe_and_translate
return self . cleaned_data [ ' allow_whisper_transcribe_and_translate ' ]
2020-12-15 23:33:43 +02:00
class SubtitleForm ( forms . ModelForm ) :
class Meta :
model = Subtitle
fields = [ " language " , " subtitle_file " ]
2025-09-01 15:11:38 +03:00
labels = {
2025-09-04 13:39:41 +03:00
" subtitle_file " : " Upload Caption File " ,
2025-09-01 15:11:38 +03:00
}
help_texts = {
" subtitle_file " : " SubRip (.srt) and WebVTT (.vtt) are supported file formats. " ,
}
2020-12-15 23:33:43 +02:00
def __init__ ( self , media_item , * args , * * kwargs ) :
super ( SubtitleForm , self ) . __init__ ( * args , * * kwargs )
self . instance . media = media_item
2025-09-01 15:11:38 +03:00
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 ( ' subtitle_file ' ) ,
CustomField ( ' language ' ) ,
)
self . helper . layout . append ( FormActions ( Submit ( ' submit ' , ' Submit ' , css_class = ' primaryAction ' ) ) )
2020-12-15 23:33:43 +02:00
def save ( self , * args , * * kwargs ) :
self . instance . user = self . instance . media . user
media = super ( SubtitleForm , self ) . save ( * args , * * kwargs )
return media
2025-03-09 20:29:26 +02:00
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 " )
2020-12-15 23:33:43 +02:00
class ContactForm ( forms . Form ) :
from_email = forms . EmailField ( required = True )
name = forms . CharField ( required = False )
message = forms . CharField ( widget = forms . Textarea , required = True )
def __init__ ( self , user , * args , * * kwargs ) :
super ( ContactForm , self ) . __init__ ( * args , * * kwargs )
self . fields [ " name " ] . label = " Your name: "
self . fields [ " from_email " ] . label = " Your email: "
self . fields [ " message " ] . label = " Please add your message here and submit: "
self . user = user
if user . is_authenticated :
self . fields . pop ( " name " )
self . fields . pop ( " from_email " )