Files
vrobbler/vrobbler/apps/scrobbles/forms.py
Colin Powell 8ac6ed542f
Some checks failed
build & deploy / test (push) Successful in 1m41s
build & deploy / deploy (push) Failing after 2m2s
[black] Apparently we didn't get it all
2026-03-13 17:57:39 -04:00

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