110 lines
3.2 KiB
Python
110 lines
3.2 KiB
Python
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:
|
|
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
|