""" open/DurusWorks/qp/fill/form.qpy """ from qp.fill.html import htmltag from qp.fill.widget import CheckboxWidget, SingleSelectWidget, IntWidget from qp.fill.widget import HiddenWidget, StringWidget, TextWidget from qp.fill.widget import PasswordWidget, subname, FileWidget from qp.fill.widget import RadiobuttonsWidget, MultipleSelectWidget from qp.fill.widget import SubmitWidget, FloatWidget, ResetWidget from qp.fill.widget import OptionSelectWidget, ButtonWidget, ButtonSubmitWidget from qp.pub.common import get_request, get_hit, get_user, get_fields from qpy import xml def get_javascript_code(): if 'javascript_code' not in get_hit().get_info(): get_hit().get_info()['javascript_code'] = {} return get_hit().get_info().get('javascript_code') def add_javascript_code(name, javascript): if 'javascript_code' not in get_hit().get_info(): get_hit().get_info()['javascript_code'] = {} get_hit().get_info()['javascript_code'][name] = javascript class FormTokenWidget(HiddenWidget): def _parse(self, request): token = request.get_field(self.name) tokens = get_user().get_tokens() if token not in tokens: self.error = 'invalid' else: tokens.remove(token) def render_error(self, error): return '' def render(self): self.value = get_user().get_tokens().new_token() return HiddenWidget.render(self) class Form(object): """ Provides a high-level mechanism for collecting and processing user input that is based on HTML forms. Instance attributes: widgets : [Widget] widgets that are not subclasses of SubmitWidget or HiddenWidget submit_widgets : [SubmitWidget] subclasses of SubmitWidget, normally rendered at the end of the form hidden_widgets : [HiddenWidget] subclasses of HiddenWidget, normally rendered at the beginning of the form _names : { name:string : Widget } names used in the form and the widgets associated with them """ TOKEN_NAME = "_form_id" # name of hidden token widget JAVASCRIPT_MARKUP = xml('\n') TOKEN_NOTICE = xml( '
' 'The form you have submitted has already been submitted once. ' 'Please review the form data and submit the form again.' '
') ERROR_NOTICE = xml( '
' 'There were errors processing your form.
' 'See details below.' '
') def __init__(self, method="post", action=None, enctype=None, use_tokens=True, prefix=None, log_errors=False, **attrs): if method not in ("post", "get"): raise ValueError("Form method must be 'post' or 'get', " "not %r" % method) self.method = method self.action = action or self._get_default_action() if 'class' not in attrs: attrs['class'] = 'qp' self.prefix = prefix or attrs.get('id') self.attrs = attrs self.log_errors = log_errors self.widgets = [] self.submit_widgets = [] self.hidden_widgets = [] self._names = {} if enctype is not None and enctype not in ( "application/x-www-form-urlencoded", "multipart/form-data"): raise ValueError("Form enctype must be " "'application/x-www-form-urlencoded' or " "'multipart/form-data', not %r" % enctype) self.enctype = enctype if use_tokens and self.method == "post": # unique token for each form, this prevents many cross-site # attacks and prevents a form from being submitted twice self.add(FormTokenWidget, self.TOKEN_NAME, value=None) def _get_default_action(self): return get_request().get_path_query() def __getitem__(self, name): """(name:string) -> any Return a widget's value. Raises KeyError if widget named 'name' does not exist. """ try: return self._names[name].parse() except KeyError: raise KeyError('no widget named %r' % name) def gen_all_widgets(self): for widget in self.widgets + self.submit_widgets + self.hidden_widgets: for w in widget: yield w def has_key(self, name): """Return true if the widget named 'name' is in the form.""" return name in self._names def get(self, name, default=None): """(name:string, default=None) -> any Return a widget's value. Returns 'default' if widget named 'name' does not exist. """ widget = self._names.get(name) if widget is not None: return widget.parse() else: return default def get_widget(self, name): """(name:string) -> Widget | None Return the widget named 'name'. Returns None if the widget does not exist. """ return self._names.get(name) def get_submit_widgets(self): """() -> [SubmitWidget] """ return self.submit_widgets def get_all_widgets(self): """() -> [Widget] Return all the widgets that have been added to the form. Note that this while this list includes submit widgets and hidden widgets, it does not include sub-widgets (e.g. widgets that are part of CompositeWidgets) """ return list(self._names.values()) def __iter__(self): for widget in self._names.values(): for w in widget: yield w def is_submitted(self): """() -> bool Has this form been submitted? This is true if any widget name appears in the fields of the request. If the form has been submitted, this *modifies* the request fields as necessary to make sure that every widget of this form has a key in the fields dictionary. """ request = get_request() for widget in self: if widget.is_submitted(request=request): fields = get_fields() for widget in self: fields.setdefault(widget.get_name(), '') return True return False def has_errors(self): """() -> bool Ensure that all components of the form have parsed themselves. Return true if any of them have errors. """ request = get_request() has_errors = False if self.is_submitted(): for widget in self: widget.parse(request) for widget in self: if widget.has_error(request=request): has_errors = True return has_errors def clear_errors(self): """Ensure that all components of the form have parsed themselves. Clear any errors that might have occurred during parsing. """ request = get_request() for widget in self: widget.clear_error(request) def clear(self): """Clear submitted values. """ request = get_request() for widget in self: widget.clear(request) def get_submit(self): """() -> string | bool Get the name of the submit button that was used to submit the current form. If the form is submitted but not by any known form-level SubmitWidget then return True. Otherwise, return False. """ request = get_request() for button in self.submit_widgets: if button.parse(request=request): return button.name return self.is_submitted() def set_error(self, name, error): """(name : string, error : string) Set the error attribute of the widget named 'name'. """ widget = self._names.get(name) if not widget: raise KeyError("unknown name %r" % name) widget.set_error(error) def add_widget(self, widget_class, name, *args, **kwargs): if name in self._names: raise ValueError("form already has '%s' widget" % name) if self.prefix is not None: widget_name = subname(self.prefix, name) else: widget_name = name widget = widget_class(widget_name, *args, **kwargs) self._names[name] = widget self.widgets.append(widget) # Ensure the new widget appears in the fields of the request request = get_request() for w in self: if w.is_submitted(request=request): fields = get_fields() for sub_w in widget: fields.setdefault(sub_w.get_name(), '') break return widget def add(self, widget_class, name, *args, **kwargs): widget = self.add_widget(widget_class, name, *args, **kwargs) if isinstance(widget, SubmitWidget): self.widgets.remove(widget) self.submit_widgets.append(widget) elif isinstance(widget, HiddenWidget): self.widgets.remove(widget) self.hidden_widgets.append(widget) if isinstance(widget, FileWidget): assert self.enctype == "multipart/form-data" def add_button(self, name, value=None, **kwargs): self.add(ButtonWidget, name, value, **kwargs) def add_submit(self, name, value=None, **kwargs): self.add(SubmitWidget, name, value, **kwargs) def add_button_submit(self, name, value=None, **kwargs): self.add(ButtonSubmitWidget, name, value, **kwargs) def add_reset(self, name, value=None, **kwargs): self.add(ResetWidget, name, value, **kwargs) def add_hidden(self, name, value=None, **kwargs): self.add(HiddenWidget, name, value, **kwargs) def add_string(self, name, value=None, **kwargs): self.add(StringWidget, name, value, **kwargs) def add_text(self, name, value=None, **kwargs): self.add(TextWidget, name, value, **kwargs) def add_password(self, name, value=None, **kwargs): self.add(PasswordWidget, name, value, **kwargs) def add_checkbox(self, name, value=None, **kwargs): self.add(CheckboxWidget, name, value, **kwargs) def add_single_select(self, name, value=None, **kwargs): self.add(SingleSelectWidget, name, value, **kwargs) def add_multiple_select(self, name, value=None, **kwargs): self.add(MultipleSelectWidget, name, value, **kwargs) def add_option_select(self, name, value=None, **kwargs): self.add(OptionSelectWidget, name, value, **kwargs) def add_radiobuttons(self, name, value=None, **kwargs): self.add(RadiobuttonsWidget, name, value, **kwargs) def add_float(self, name, value=None, **kwargs): self.add(FloatWidget, name, value, **kwargs) def add_int(self, name, value=None, **kwargs): self.add(IntWidget, name, value, **kwargs) def add_file(self, name, value=None, **kwargs): self.add(FileWidget, name, value, **kwargs) # -- Layout (rendering) methods ------------------------------------ def render:xml(self): self._render_start() self._render_body() self._render_finish() if self.log_errors: self._log_errors() def _log_errors(self): errors = [] for widget in self.gen_all_widgets(): has_errors = True if widget.has_error(): errors.append((widget.get_name(), widget.get_error())) if errors: print('') for error in errors: print('Widget error in %s: %s' % (error[0], error[1])) print('Form fields: %s' % get_fields()) print('') def _render_start:xml(self): '\n' htmltag('form', method=self.method, enctype=self.enctype, action=self.action or "", **self.attrs) '\n
' self._render_hidden_widgets() def _render_finish:xml(self): '\n
\n' code = get_javascript_code() if code: self._render_javascript(code) '\n' def _render_widgets:xml(self): for widget in self.widgets: '\n' widget.render() def _render_hidden_widgets:xml(self): for widget in self.hidden_widgets: '\n' widget.render() def _render_submit_widgets:xml(self): if self.submit_widgets: '\n
' for widget in self.submit_widgets: '\n' widget.render() '\n
\n' def _render_error_notice(self): token_widget = self.get_widget(self.TOKEN_NAME) if token_widget is not None and token_widget.has_error(): # form tokens are enabled but the token data in the request # does not match anything in the session. It could be an # a cross-site attack but most likely the back button has # been used return self.TOKEN_NOTICE else: return self.ERROR_NOTICE def _render_javascript(self, javascript_code): """Render javacript code for the form. Insert code lexically sorted by code_id. """ form_code = [] code_ids = sorted(javascript_code.keys()) for code_id in code_ids: code = javascript_code[code_id] if code: form_code.append(code) javascript_code[code_id] = '' if form_code: return self.JAVASCRIPT_MARKUP % xml(''.join(form_code)) else: return '' def _render_body:xml(self): if self.has_errors(): self._render_error_notice() self._render_widgets() self._render_submit_widgets()