156 lines
4.6 KiB
Python
156 lines
4.6 KiB
Python
import json
|
|
from dataclasses import fields
|
|
from typing import Union, get_args, get_origin
|
|
|
|
from django import forms
|
|
|
|
|
|
class ExportScrobbleForm(forms.Form):
|
|
"""Provide options for downloading scrobbles"""
|
|
|
|
EXPORT_TYPES = (
|
|
("as", "Audioscrobbler"),
|
|
("csv", "CSV"),
|
|
("html", "HTML"),
|
|
)
|
|
export_type = forms.ChoiceField(choices=EXPORT_TYPES)
|
|
|
|
|
|
class ScrobbleForm(forms.Form):
|
|
item_id = forms.CharField(
|
|
label="",
|
|
widget=forms.TextInput(
|
|
attrs={
|
|
"class": "form-control form-control-dark w-100",
|
|
"placeholder": "Scrobble something (ttIMDB, -v Video Game title, -b Book title, -s TheSportsDB ID, -f Food name - calories)",
|
|
"aria-label": "Scrobble something",
|
|
}
|
|
),
|
|
)
|
|
|
|
|
|
# Mapping of types to Django form field classes
|
|
TYPE_FIELD_MAP = {
|
|
int: forms.IntegerField,
|
|
float: forms.FloatField,
|
|
bool: forms.BooleanField,
|
|
str: forms.CharField,
|
|
dict: forms.JSONField,
|
|
list: forms.JSONField,
|
|
}
|
|
|
|
# Optional: type-to-widget mapping
|
|
TYPE_WIDGET_MAP = {
|
|
str: forms.TextInput(attrs={"size": 80}),
|
|
dict: forms.Textarea(attrs={"rows": 10, "cols": 80}),
|
|
list: forms.Textarea(attrs={"rows": 6, "cols": 80}),
|
|
bool: forms.CheckboxInput(),
|
|
}
|
|
|
|
|
|
def django_form_field_from_type(field_type, required=True):
|
|
origin = get_origin(field_type)
|
|
|
|
# Handle Optional / Union
|
|
if origin is Union:
|
|
args = get_args(field_type)
|
|
if type(None) in args:
|
|
required = False
|
|
non_none_type = [arg for arg in args if arg is not type(None)][0]
|
|
return django_form_field_from_type(non_none_type, required=required)
|
|
|
|
# Determine actual type
|
|
base_type = origin if origin else field_type
|
|
field_class = TYPE_FIELD_MAP.get(base_type, forms.CharField)
|
|
widget = TYPE_WIDGET_MAP.get(base_type)
|
|
|
|
return (
|
|
field_class(required=required, widget=widget)
|
|
if widget
|
|
else field_class(required=required)
|
|
)
|
|
|
|
|
|
def form_from_dataclass(dataclass):
|
|
form_fields = {}
|
|
override_fields = {}
|
|
for klass in dataclass.mro():
|
|
if hasattr(klass, "override_fields"):
|
|
override_fields.update(klass.override_fields())
|
|
for f in fields(dataclass):
|
|
if f.name in override_fields:
|
|
form_fields[f.name] = override_fields[f.name]
|
|
continue
|
|
|
|
required = f.default is None and f.default_factory is None
|
|
form_fields[f.name] = django_form_field_from_type(f.type, required=required)
|
|
|
|
if f.name in dataclass._excluded_fields:
|
|
form_fields[f.name].disabled = True
|
|
|
|
form_cls = type(f"{dataclass.__name__}Form", (forms.Form,), form_fields)
|
|
|
|
if "notes" in form_cls.base_fields and "notes" not in override_fields:
|
|
form_cls.base_fields["notes"] = forms.CharField(
|
|
required=False,
|
|
widget=forms.Textarea(attrs={"rows": 4}),
|
|
)
|
|
|
|
def clean_notes(self):
|
|
notes_str = self.cleaned_data.get("notes", "")
|
|
return [line.strip() for line in notes_str.splitlines() if line.strip()]
|
|
|
|
form_cls.clean_notes = clean_notes
|
|
return form_cls
|
|
|
|
|
|
class NotesDictWidget(forms.Widget):
|
|
template_name = "tasks/task_notes_widget.html"
|
|
|
|
class Media:
|
|
js = ("tasks/task_notes.js",)
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
timestamps = data.getlist(f"{name}_timestamps")
|
|
if timestamps:
|
|
contents = data.getlist(f"{name}_contents")
|
|
result = {}
|
|
for i, ts in enumerate(timestamps):
|
|
if i < len(contents) and ts:
|
|
result[ts] = contents[i]
|
|
return result if result else ""
|
|
return data.get(name, "")
|
|
|
|
def get_context(self, name, value, attrs):
|
|
context = super().get_context(name, value, attrs)
|
|
notes = {}
|
|
if value:
|
|
if isinstance(value, str):
|
|
try:
|
|
notes = json.loads(value)
|
|
except (json.JSONDecodeError, TypeError):
|
|
notes = {}
|
|
elif isinstance(value, dict):
|
|
notes = value
|
|
context["widget"]["notes"] = notes
|
|
return context
|
|
|
|
|
|
class NotesDictField(forms.Field):
|
|
widget = NotesDictWidget
|
|
|
|
def clean(self, value):
|
|
if not value:
|
|
return {}
|
|
|
|
if isinstance(value, str):
|
|
if value.strip():
|
|
from scrobbles.utils import make_note_timestamp
|
|
return {make_note_timestamp(): value.strip()}
|
|
return {}
|
|
|
|
if isinstance(value, dict):
|
|
return value
|
|
|
|
return {}
|