refactor: move form stuff out of source code

This commit is contained in:
Jakob Ketterl 2021-04-29 15:28:18 +02:00
parent 35dcff90ea
commit 29bce9e07a
8 changed files with 137 additions and 130 deletions

View File

@ -1,7 +1,6 @@
from owrx.config import Config from owrx.config import Config
from owrx.controllers.admin import AuthorizationMixin from owrx.controllers.admin import AuthorizationMixin
from owrx.controllers.template import WebpageController from owrx.controllers.template import WebpageController
from owrx.form.error import FormError
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from urllib.parse import parse_qs from urllib.parse import parse_qs
@ -11,45 +10,6 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Section(object):
def __init__(self, title, *inputs):
self.title = title
self.inputs = inputs
def render_input(self, input, data, errors):
return input.render(data, errors)
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, errors):
return """
<div class="{classes}">
<h3 class="settings-header">
{title}
</h3>
{inputs}
</div>
""".format(
classes=" ".join(self.classes()), title=self.title, inputs=self.render_inputs(data, errors)
)
def parse(self, data):
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): class SettingsController(AuthorizationMixin, WebpageController):
def indexAction(self): def indexAction(self):
self.serve_template("settings.html", **self.template_variables()) self.serve_template("settings.html", **self.template_variables())

View File

@ -1,4 +1,5 @@
from owrx.controllers.settings import SettingsFormController, Section from owrx.controllers.settings import SettingsFormController
from owrx.form.section import Section
from owrx.form.input import CheckboxInput, ServicesCheckboxInput from owrx.form.input import CheckboxInput, ServicesCheckboxInput
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem from owrx.breadcrumb import Breadcrumb, BreadcrumbItem
from owrx.controllers.settings import SettingsBreadcrumb from owrx.controllers.settings import SettingsBreadcrumb

View File

@ -1,4 +1,5 @@
from owrx.controllers.settings import SettingsFormController, Section, SettingsBreadcrumb from owrx.controllers.settings import SettingsFormController, SettingsBreadcrumb
from owrx.form.section import Section
from owrx.form.input import CheckboxInput, NumberInput, DropdownInput, Js8ProfileCheckboxInput, MultiCheckboxInput, Option from owrx.form.input import CheckboxInput, NumberInput, DropdownInput, Js8ProfileCheckboxInput, MultiCheckboxInput, Option
from owrx.form.input.wfm import WfmTauValues from owrx.form.input.wfm import WfmTauValues
from owrx.form.input.wsjt import Q65ModeMatrix, WsjtDecodingDepthsInput from owrx.form.input.wsjt import Q65ModeMatrix, WsjtDecodingDepthsInput

View File

@ -1,4 +1,5 @@
from owrx.controllers.settings import Section, SettingsFormController from owrx.controllers.settings import SettingsFormController
from owrx.form.section import Section
from owrx.config.core import CoreConfig from owrx.config.core import CoreConfig
from owrx.form.input import ( from owrx.form.input import (
TextInput, TextInput,

View File

@ -1,4 +1,5 @@
from owrx.controllers.settings import SettingsFormController, Section, SettingsBreadcrumb from owrx.controllers.settings import SettingsFormController, SettingsBreadcrumb
from owrx.form.section import Section
from owrx.form.input.converter import OptionalConverter from owrx.form.input.converter import OptionalConverter
from owrx.form.input.aprs import AprsBeaconSymbols, AprsAntennaDirections from owrx.form.input.aprs import AprsBeaconSymbols, AprsAntennaDirections
from owrx.form.input import TextInput, CheckboxInput, DropdownInput, NumberInput from owrx.form.input import TextInput, CheckboxInput, DropdownInput, NumberInput

View File

@ -4,7 +4,8 @@ from owrx.controllers.settings import SettingsFormController
from owrx.source import SdrDeviceDescription, SdrDeviceDescriptionMissing, SdrClientClass from owrx.source import SdrDeviceDescription, SdrDeviceDescriptionMissing, SdrClientClass
from owrx.config import Config from owrx.config import Config
from owrx.connection import OpenWebRxReceiverClient from owrx.connection import OpenWebRxReceiverClient
from owrx.controllers.settings import Section, SettingsBreadcrumb from owrx.controllers.settings import SettingsBreadcrumb
from owrx.form.section import Section
from urllib.parse import quote, unquote from urllib.parse import quote, unquote
from owrx.sdr import SdrService from owrx.sdr import SdrService
from owrx.form.input import TextInput, DropdownInput, Option from owrx.form.input import TextInput, DropdownInput, Option

126
owrx/form/section.py Normal file
View File

@ -0,0 +1,126 @@
from owrx.form.error import FormError
from owrx.form.input import Input
from typing import List
class Section(object):
def __init__(self, title, *inputs):
self.title = title
self.inputs = inputs
def render_input(self, input, data, errors):
return input.render(data, errors)
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, errors):
return """
<div class="{classes}">
<h3 class="settings-header">
{title}
</h3>
{inputs}
</div>
""".format(
classes=" ".join(self.classes()), title=self.title, inputs=self.render_inputs(data, errors)
)
def parse(self, data):
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 OptionalSection(Section):
def __init__(self, title, inputs: List[Input], mandatory, optional):
super().__init__(title, *inputs)
self.mandatory = mandatory
self.optional = optional
self.optional_inputs = []
def classes(self):
classes = super().classes()
classes.append("optional-section")
return classes
def _is_optional(self, input):
return input.id in self.optional
def render_optional_select(self):
return """
<hr class="row" />
<div class="form-group row">
<label class="col-form-label col-form-label-sm col-3">
Additional optional settings
</label>
<div class="add-group col-9 p-0">
<div class="add-group-select">
<select class="form-control form-control-sm optional-select">
{options}
</select>
</div>
<button type="button" class="btn btn-sm btn-success option-add-button">Add</button>
</div>
</div>
""".format(
options="".join(
"""
<option value="{value}">{name}</option>
""".format(
value=input.id,
name=input.getLabel(),
)
for input in self.optional_inputs
)
)
def render_optional_inputs(self, data, errors):
return """
<div class="optional-inputs" style="display: none;">
{inputs}
</div>
""".format(
inputs="".join(self.render_input(input, data, errors) for input in self.optional_inputs)
)
def render_inputs(self, data, errors):
return (
super().render_inputs(data, errors)
+ self.render_optional_select()
+ self.render_optional_inputs(data, errors)
)
def render(self, data, errors):
indexed_inputs = {input.id: input for input in self.inputs}
visible_keys = set(self.mandatory + [k for k in self.optional if k in data])
optional_keys = set(k for k in self.optional if k not in data)
self.inputs = [input for k, input in indexed_inputs.items() if k in visible_keys]
for input in self.inputs:
if self._is_optional(input):
input.setRemovable()
self.optional_inputs = [input for k, input in indexed_inputs.items() if k in optional_keys]
for input in self.optional_inputs:
input.setRemovable()
input.setDisabled()
return super().render(data, errors)
def parse(self, data):
data, errors = super().parse(data)
# filter out errors for optional fields
errors = [e for e in errors if e.getKey() not in self.optional or e.getKey() in data]
# remove optional keys if they have been removed from the form by setting them to None
for k in self.optional:
if k not in data:
data[k] = None
return data, errors

View File

@ -16,7 +16,7 @@ from owrx.form.input import Input, TextInput, NumberInput, CheckboxInput, ModesI
from owrx.form.input.converter import OptionalConverter from owrx.form.input.converter import OptionalConverter
from owrx.form.input.device import GainInput, SchedulerInput, WaterfallLevelsInput from owrx.form.input.device import GainInput, SchedulerInput, WaterfallLevelsInput
from owrx.form.input.validator import RequiredValidator from owrx.form.input.validator import RequiredValidator
from owrx.controllers.settings import Section from owrx.form.section import OptionalSection
from owrx.feature import FeatureDetector from owrx.feature import FeatureDetector
from typing import List from typing import List
from enum import Enum from enum import Enum
@ -447,90 +447,6 @@ class SdrDeviceDescriptionMissing(Exception):
pass pass
class OptionalSection(Section):
def __init__(self, title, inputs: List[Input], mandatory, optional):
super().__init__(title, *inputs)
self.mandatory = mandatory
self.optional = optional
self.optional_inputs = []
def classes(self):
classes = super().classes()
classes.append("optional-section")
return classes
def _is_optional(self, input):
return input.id in self.optional
def render_optional_select(self):
return """
<hr class="row" />
<div class="form-group row">
<label class="col-form-label col-form-label-sm col-3">
Additional optional settings
</label>
<div class="add-group col-9 p-0">
<div class="add-group-select">
<select class="form-control form-control-sm optional-select">
{options}
</select>
</div>
<button type="button" class="btn btn-sm btn-success option-add-button">Add</button>
</div>
</div>
""".format(
options="".join(
"""
<option value="{value}">{name}</option>
""".format(
value=input.id,
name=input.getLabel(),
)
for input in self.optional_inputs
)
)
def render_optional_inputs(self, data, errors):
return """
<div class="optional-inputs" style="display: none;">
{inputs}
</div>
""".format(
inputs="".join(self.render_input(input, data, errors) for input in self.optional_inputs)
)
def render_inputs(self, data, errors):
return (
super().render_inputs(data, errors)
+ self.render_optional_select()
+ self.render_optional_inputs(data, errors)
)
def render(self, data, errors):
indexed_inputs = {input.id: input for input in self.inputs}
visible_keys = set(self.mandatory + [k for k in self.optional if k in data])
optional_keys = set(k for k in self.optional if k not in data)
self.inputs = [input for k, input in indexed_inputs.items() if k in visible_keys]
for input in self.inputs:
if self._is_optional(input):
input.setRemovable()
self.optional_inputs = [input for k, input in indexed_inputs.items() if k in optional_keys]
for input in self.optional_inputs:
input.setRemovable()
input.setDisabled()
return super().render(data, errors)
def parse(self, data):
data, errors = super().parse(data)
# filter out errors for optional fields
errors = [e for e in errors if e.getKey() not in self.optional or e.getKey() in data]
# remove optional keys if they have been removed from the form by setting them to None
for k in self.optional:
if k not in data:
data[k] = None
return data, errors
class SdrDeviceDescription(object): class SdrDeviceDescription(object):
@staticmethod @staticmethod
def getByType(sdr_type: str) -> "SdrDeviceDescription": def getByType(sdr_type: str) -> "SdrDeviceDescription":