implement basic error handling and validation for forms

This commit is contained in:
Jakob Ketterl
2021-03-24 22:46:51 +01:00
parent 4cbce9c840
commit 6ddced4689
9 changed files with 206 additions and 99 deletions

View File

@ -1,6 +1,7 @@
from owrx.config import Config
from owrx.controllers.admin import AuthorizationMixin
from owrx.controllers.template import WebpageController
from owrx.form.error import FormError
from abc import ABCMeta, abstractmethod
from urllib.parse import parse_qs
@ -10,16 +11,16 @@ class Section(object):
self.title = title
self.inputs = inputs
def render_input(self, input, data):
return input.render(data)
def render_input(self, input, data, errors):
return input.render(data, errors)
def render_inputs(self, data):
return "".join([self.render_input(i, data) for i in self.inputs])
def render_inputs(self, data, errors):
return "".join([self.render_input(i, data, errors) for i in self.inputs])
def classes(self):
return ["col-12", "settings-section"]
def render(self, data):
def render(self, data, errors):
return """
<div class="{classes}">
<h3 class="settings-header">
@ -28,11 +29,20 @@ class Section(object):
{inputs}
</div>
""".format(
classes=" ".join(self.classes()), title=self.title, inputs=self.render_inputs(data)
classes=" ".join(self.classes()), title=self.title, inputs=self.render_inputs(data, errors)
)
def parse(self, data):
return {k: v for i in self.inputs for k, v in i.parse(data).items()}
parsed_data = {}
errors = []
for i in self.inputs:
try:
parsed_data.update(i.parse(data))
except FormError as e:
errors.append(e)
except Exception as e:
errors.append(FormError(i.id, "{}: {}".format(type(e).__name__, e)))
return parsed_data, errors
class SettingsController(AuthorizationMixin, WebpageController):
@ -41,6 +51,10 @@ class SettingsController(AuthorizationMixin, WebpageController):
class SettingsFormController(AuthorizationMixin, WebpageController, metaclass=ABCMeta):
def __init__(self, handler, request, options):
super().__init__(handler, request, options)
self.errors = {}
@abstractmethod
def getSections(self):
pass
@ -52,8 +66,11 @@ class SettingsFormController(AuthorizationMixin, WebpageController, metaclass=AB
def getData(self):
return Config.get()
def getErrors(self):
return self.errors
def render_sections(self):
sections = "".join(section.render(self.getData()) for section in self.getSections())
sections = "".join(section.render(self.getData(), self.getErrors()) for section in self.getSections())
buttons = self.render_buttons()
return """
<form class="settings-body" method="POST">
@ -84,15 +101,34 @@ class SettingsFormController(AuthorizationMixin, WebpageController, metaclass=AB
def parseFormData(self):
data = parse_qs(self.get_body().decode("utf-8"), keep_blank_values=True)
return {k: v for i in self.getSections() for k, v in i.parse(data).items()}
result = {}
errors = []
for section in self.getSections():
section_data, section_errors = section.parse(data)
result.update(section_data)
errors += section_errors
return result, errors
def getSuccessfulRedirect(self):
return self.request.path
def _mergeErrors(self, errors):
result = {}
for e in errors:
if e.getKey() not in result:
result[e.getKey()] = []
result[e.getKey()].append(e.getMessage())
return result
def processFormData(self):
self.processData(self.parseFormData())
self.store()
self.send_redirect(self.getSuccessfulRedirect())
data, errors = self.parseFormData()
if errors:
self.errors = self._mergeErrors(errors)
self.indexAction()
else:
self.processData(data)
self.store()
self.send_redirect(self.getSuccessfulRedirect())
def processData(self, data):
config = self.getData()

View File

@ -8,6 +8,7 @@ from owrx.controllers.settings import Section
from urllib.parse import quote, unquote
from owrx.sdr import SdrService
from owrx.form import TextInput, DropdownInput, Option
from owrx.form.validator import RequiredValidator
from owrx.property import PropertyLayer, PropertyStack
from abc import ABCMeta, abstractmethod
@ -235,7 +236,7 @@ class SdrDeviceController(SdrFormControllerWithModal):
if self.device is None:
self.send_response("device not found", code=404)
return
self.serve_template("settings/general.html", **self.template_variables())
super().indexAction()
def processFormData(self):
if self.device is None:
@ -276,7 +277,7 @@ class NewSdrDeviceController(SettingsFormController):
"New device settings",
TextInput("name", "Device name"),
DropdownInput("type", "Device type", [Option(name, name) for name in SdrDeviceDescription.getTypes()]),
TextInput("id", "Device ID"),
TextInput("id", "Device ID", validator=RequiredValidator()),
)
]
@ -331,7 +332,7 @@ class SdrProfileController(SdrFormControllerWithModal):
if self.profile is None:
self.send_response("profile not found", code=404)
return
self.serve_template("settings/general.html", **self.template_variables())
super().indexAction()
def processFormData(self):
if self.profile is None:
@ -377,7 +378,7 @@ class NewProfileController(SdrProfileController):
return [
Section(
"New profile settings",
TextInput("id", "Profile ID"),
TextInput("id", "Profile ID", validator=RequiredValidator()),
)
] + super().getSections()