takahe/users/views/settings/settings_page.py

117 lines
4.4 KiB
Python
Raw Normal View History

from functools import partial
from typing import ClassVar
from django import forms
from django.core.files import File
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views.generic import FormView
from core.models.config import Config, UploadedImage
2023-04-27 06:54:38 +00:00
from django.contrib.auth.decorators import login_required
from users.shortcuts import by_handle_or_404
2023-04-27 06:54:38 +00:00
@method_decorator(login_required, name="dispatch")
class SettingsPage(FormView):
"""
Shows a settings page dynamically created from our settings layout
at the bottom of the page. Don't add this to a URL directly - subclass!
"""
options_class = Config.IdentityOptions
template_name = "settings/settings.html"
section: ClassVar[str]
options: dict[str, dict[str, str | int]]
layout: dict[str, list[str]]
def get_form_class(self):
# Create the fields dict from the config object
fields = {}
for key, details in self.options.items():
field_kwargs = {}
config_field = self.options_class.__fields__[key]
if config_field.type_ is bool:
form_field = partial(
forms.BooleanField,
widget=forms.Select(
choices=[(True, "Enabled"), (False, "Disabled")]
),
)
elif config_field.type_ is UploadedImage:
form_field = forms.ImageField
elif config_field.type_ is str:
if details.get("display") == "textarea":
form_field = partial(
forms.CharField,
widget=forms.Textarea,
)
else:
form_field = forms.CharField
elif config_field.type_ is int:
choices = details.get("choices")
if choices:
field_kwargs["widget"] = forms.Select(choices=choices)
for int_kwarg in {"min_value", "max_value", "step_size"}:
val = details.get(int_kwarg)
if val:
field_kwargs[int_kwarg] = val
form_field = forms.IntegerField
else:
raise ValueError(f"Cannot render settings type {config_field.type_}")
fields[key] = form_field(
label=details["title"],
help_text=details.get("help_text", ""),
required=details.get("required", False),
**field_kwargs,
)
# Create a form class dynamically (yeah, right?) and return that
return type("SettingsForm", (forms.Form,), fields)
2023-04-27 06:54:38 +00:00
def dispatch(self, request, *args, **kwargs):
if "handle" in kwargs:
self.identity = by_handle_or_404(request, kwargs["handle"])
return super().dispatch(request, *args, **kwargs)
def load_config(self):
2023-04-27 06:54:38 +00:00
return Config.load_identity(self.identity)
def save_config(self, key, value):
2023-04-27 06:54:38 +00:00
Config.set_identity(self.identity, key, value)
def get_initial(self):
config = self.load_config()
initial = {}
for key in self.options.keys():
initial[key] = getattr(config, key)
return initial
def get_context_data(self):
context = super().get_context_data()
context["section"] = self.section
# Gather fields into fieldsets
context["fieldsets"] = {}
for title, fields in self.layout.items():
context["fieldsets"][title] = [context["form"][field] for field in fields]
2023-04-27 06:54:38 +00:00
if hasattr(self, "identity"):
context["identity"] = self.identity
return context
def form_valid(self, form):
# Save each key
for field in form:
if field.field.__class__.__name__ == "ImageField":
# These can be cleared with an extra checkbox
if self.request.POST.get(f"{field.name}__clear"):
self.save_config(field.name, None)
continue
# We shove the preview values in initial_data, so only save file
# fields if they have a File object.
if not isinstance(form.cleaned_data[field.name], File):
continue
self.save_config(
field.name,
form.cleaned_data[field.name],
)
return redirect(".")