mirror of
https://github.com/meeb/tubesync.git
synced 2025-06-22 21:16:38 +00:00
192 lines
6.8 KiB
Python
192 lines
6.8 KiB
Python
from collections import namedtuple
|
|
from typing import Any, Dict
|
|
from django import forms
|
|
from django.db import connection, models
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
# as stolen from:
|
|
# - https://wiki.sponsor.ajay.app/w/Types
|
|
# - https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/postprocessor/sponsorblock.py
|
|
#
|
|
# The spacing is a little odd, it is for easy copy/paste selection.
|
|
# Please don't change it.
|
|
# Every possible category fits in a string < 128 characters
|
|
class SponsorBlock_Category(models.TextChoices):
|
|
SPONSOR = 'sponsor', _( 'Sponsor' )
|
|
INTRO = 'intro', _( 'Intermission/Intro Animation' )
|
|
OUTRO = 'outro', _( 'Endcards/Credits' )
|
|
SELFPROMO = 'selfpromo', _( 'Unpaid/Self Promotion' )
|
|
PREVIEW = 'preview', _( 'Preview/Recap' )
|
|
FILLER = 'filler', _( 'Filler Tangent' )
|
|
INTERACTION = 'interaction', _( 'Interaction Reminder' )
|
|
MUSIC_OFFTOPIC = 'music_offtopic', _( 'Non-Music Section' )
|
|
|
|
|
|
CommaSepChoice = namedtuple(
|
|
'CommaSepChoice', [
|
|
'allow_all',
|
|
'all_choice',
|
|
'all_label',
|
|
'possible_choices',
|
|
'selected_choices',
|
|
'separator',
|
|
],
|
|
defaults = (
|
|
False,
|
|
None,
|
|
'All',
|
|
list(),
|
|
list(),
|
|
',',
|
|
),
|
|
)
|
|
|
|
# this is a form field!
|
|
class CustomCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
|
|
template_name = 'widgets/checkbox_select.html'
|
|
option_template_name = 'widgets/checkbox_option.html'
|
|
# perhaps set the 'selected' attribute too?
|
|
# checked_attribute = {'checked': True, 'selected': True}
|
|
|
|
def get_context(self, name: str, value: Any, attrs) -> Dict[str, Any]:
|
|
data = value
|
|
select_all = False
|
|
if isinstance(data, CommaSepChoice):
|
|
select_all = (data.allow_all and data.all_choice in data.selected_choices)
|
|
value = list(data.selected_choices)
|
|
context = super().get_context(name, value, attrs)
|
|
widget = context['widget']
|
|
options = widget['optgroups']
|
|
# This is a new key in widget
|
|
widget['multipleChoiceProperties'] = list()
|
|
for _group, single_option_list, _index in options:
|
|
for option in single_option_list:
|
|
option['selected'] |= select_all
|
|
widget['multipleChoiceProperties'].append(option)
|
|
|
|
return { 'widget': widget }
|
|
|
|
|
|
# this is a database field!
|
|
class CommaSepChoiceField(models.CharField):
|
|
'''
|
|
Implements comma-separated storage of lists
|
|
'''
|
|
|
|
form_class = forms.MultipleChoiceField
|
|
widget = CustomCheckboxSelectMultiple
|
|
from common.logger import log
|
|
|
|
def __init__(self, *args, separator=",", possible_choices=(("","")), all_choice="", all_label="All", allow_all=False, **kwargs):
|
|
kwargs.setdefault('max_length', 128)
|
|
self.separator = str(separator)
|
|
self.possible_choices = possible_choices or choices
|
|
self.selected_choices = list()
|
|
self.allow_all = allow_all
|
|
self.all_label = all_label
|
|
self.all_choice = all_choice
|
|
self.choices = self.get_all_choices()
|
|
super().__init__(*args, **kwargs)
|
|
self.validators.clear()
|
|
|
|
|
|
# Override these functions to prevent unwanted behaviors
|
|
def to_python(self, value):
|
|
return value
|
|
|
|
def get_internal_type(self):
|
|
return super().get_internal_type()
|
|
|
|
|
|
# standard functions for this class
|
|
def deconstruct(self):
|
|
# set it back to the default for models.Field
|
|
# this way it is never in the returned values
|
|
self.choices = None
|
|
name, path, args, kwargs = super().deconstruct()
|
|
self.choices = self.get_all_choices()
|
|
if ',' != self.separator:
|
|
kwargs['separator'] = self.separator
|
|
kwargs['possible_choices'] = self.possible_choices
|
|
if self.allow_all:
|
|
kwargs['allow_all'] = self.allow_all
|
|
if self.all_choice:
|
|
kwargs['all_choice'] = self.all_choice
|
|
if 'All' != self.all_label:
|
|
kwargs['all_label'] = self.all_label
|
|
return name, path, args, kwargs
|
|
|
|
def formfield(self, **kwargs):
|
|
# This is a fairly standard way to set up some defaults
|
|
# while letting the caller override them.
|
|
defaults = {
|
|
'form_class': self.form_class,
|
|
# 'choices_form_class': self.form_class,
|
|
'widget': self.widget,
|
|
# use a callable for choices
|
|
'choices': self.get_all_choices,
|
|
'label': '',
|
|
'required': False,
|
|
}
|
|
# Keep the part from CharField we want,
|
|
# then call Field to skip the 'max_length' entry.
|
|
db_empty_string_as_null = connection.features.interprets_empty_strings_as_nulls
|
|
if self.null and not db_empty_string_as_null:
|
|
defaults['empty_value'] = None
|
|
defaults.update(kwargs)
|
|
return models.Field.formfield(self, **defaults)
|
|
# This is a more compact way to do the same thing
|
|
# return super().formfield(**{
|
|
# 'form_class': self.form_class,
|
|
# **kwargs,
|
|
# })
|
|
|
|
def from_db_value(self, value, expression, connection):
|
|
'''
|
|
Create a data structure to be used in Python code.
|
|
'''
|
|
if isinstance(value, str) and len(value) > 0:
|
|
value = value.split(self.separator)
|
|
if not isinstance(value, list):
|
|
value = list()
|
|
self.selected_choices = value
|
|
args_dict = {key: self.__dict__[key] for key in CommaSepChoice._fields}
|
|
return CommaSepChoice(**args_dict)
|
|
|
|
def get_prep_value(self, value):
|
|
'''
|
|
Create a value to be stored in the database.
|
|
'''
|
|
data = value
|
|
if not isinstance(data, CommaSepChoice):
|
|
# The data was lost; we can regenerate it.
|
|
args_dict = {key: self.__dict__[key] for key in CommaSepChoice._fields}
|
|
args_dict['selected_choices'] = list(value)
|
|
data = CommaSepChoice(**args_dict)
|
|
value = data.selected_choices
|
|
s_value = super().get_prep_value(value)
|
|
if set(s_value) != set(value):
|
|
self.log.warn(f'CommaSepChoiceField:get_prep_value: values did not match. '
|
|
f'CommaSepChoiceField({value}) versus CharField({s_value})')
|
|
if not isinstance(value, list):
|
|
return ''
|
|
if data.all_choice in value:
|
|
return data.all_choice
|
|
ordered_unique = list(dict.fromkeys(value))
|
|
return data.separator.join(ordered_unique)
|
|
|
|
# extra functions not used by any parent classes
|
|
def get_all_choices(self):
|
|
choice_list = list()
|
|
if self.possible_choices is None:
|
|
return choice_list
|
|
if self.allow_all:
|
|
choice_list.append((self.all_choice, _(self.all_label)))
|
|
|
|
for choice in self.possible_choices:
|
|
choice_list.append(choice)
|
|
|
|
return choice_list
|
|
|