Пример #1
0
    def __init__(self, name, data=None, filter=None):
        """

        :param name:
        :param data: data from request
        :param filter: subtree filter for nuci config
        :type filter: Element
        :return:
        """
        super(ForisForm, self).__init__(name)
        if isinstance(data, MultiDict):
            self._request_data = {}  # values from request
            # convert MultiDict to normal dict with multiple values in lists
            # if value is suffixed with '[]' (i.e. it is multifield)
            for key, value in data.iteritems():
                if key.endswith("[]"):
                    # we don't want to alter lists in MultiDict instance
                    values = copy.deepcopy(data.getall(key))
                    logger.debug("%s: %s (%s)", key, values, value)
                    # split value by \r\n - sent by textarea
                    if "\r\n" in value:
                        values = value.split("\r\n")
                    # remove dummy value from hidden field
                    elif len(values) and not values[0]:
                        del values[0]
                    # strip [] suffix
                    self._request_data[key[:-2]] = values
                else:
                    self._request_data[key] = value
        else:
            self._request_data = data or {}
        self._nuci_data = {}  # values fetched from nuci
        self.defaults = {}  # default values from field definitions
        self.__data_cache = None  # cached data
        self.__form_cache = None
        self.validated = False
        # _nuci_config is not required every time, lazy-evaluate it
        self._nuci_config = Lazy(lambda: client.get(filter))
        self.requirement_map = defaultdict(list)  # mapping: requirement -> list of required_by
        self.callbacks = []
        self.callback_results = {}  # name -> result
Пример #2
0
class ForisForm(ForisFormElement):
    def __init__(self, name, data=None, filter=None):
        """

        :param name:
        :param data: data from request
        :param filter: subtree filter for nuci config
        :type filter: Element
        :return:
        """
        super(ForisForm, self).__init__(name)
        if isinstance(data, MultiDict):
            self._request_data = {}  # values from request
            # convert MultiDict to normal dict with multiple values in lists
            # if value is suffixed with '[]' (i.e. it is multifield)
            for key, value in data.iteritems():
                if key.endswith("[]"):
                    # we don't want to alter lists in MultiDict instance
                    values = copy.deepcopy(data.getall(key))
                    logger.debug("%s: %s (%s)", key, values, value)
                    # split value by \r\n - sent by textarea
                    if "\r\n" in value:
                        values = value.split("\r\n")
                    # remove dummy value from hidden field
                    elif len(values) and not values[0]:
                        del values[0]
                    # strip [] suffix
                    self._request_data[key[:-2]] = values
                else:
                    self._request_data[key] = value
        else:
            self._request_data = data or {}
        self._nuci_data = {}  # values fetched from nuci
        self.defaults = {}  # default values from field definitions
        self.__data_cache = None  # cached data
        self.__form_cache = None
        self.validated = False
        # _nuci_config is not required every time, lazy-evaluate it
        self._nuci_config = Lazy(lambda: client.get(filter))
        self.requirement_map = defaultdict(list)  # mapping: requirement -> list of required_by
        self.callbacks = []
        self.callback_results = {}  # name -> result

    @property
    def nuci_config(self):
        return self._nuci_config

    @property
    def data(self):
        """
        Data are union of defaults + nuci values + request data.

        :return: currently known form data
        """
        if self.__data_cache is not None:
            return self.__data_cache
        self._update_nuci_data()
        data = {}
        logger.debug("Updating with defaults: %s", self.defaults)
        data.update(self.defaults)
        logger.debug("Updating with Nuci data: %s", self._nuci_data)
        data.update(self._nuci_data)
        logger.debug("Updating with data: %s", dict(self._request_data))
        data.update(self._request_data)
        if data:
            data = self.clean_data(data)
        self.__data_cache = data
        return data

    def clean_data(self, data):
        new_data = {}
        fields = self._get_all_fields()
        for field in fields:
            new_data[field.name] = data[field.name]
            if field.name in data:
                if issubclass(field.type, Checkbox):
                    # coerce checkbox values to boolean
                    new_data[field.name] = False if data[field.name] == "0" else bool(data[field.name])
        # get names of active fields according to new_data
        active_field_names = map(lambda x: x.name, self.get_active_fields(data=new_data))
        # get new dict of data of active fields
        return {k: v for k, v in new_data.iteritems() if k in active_field_names}

    def invalidate_data(self):
        self.__data_cache = None

    @property
    def _form(self):
        if self.__form_cache is not None:
            return self.__form_cache
        inputs = map(lambda x: x.field, self.get_active_fields())
        # TODO: creating the form everytime might by a wrong approach...
        logger.debug("Creating Form()...")
        form = Form(*inputs)
        form.fill(self.data)
        self.__form_cache = form
        return form

    @property
    def valid(self):
        return self._form.valid

    def _get_all_fields(self, element=None, fields=None):
        element = element or self
        fields = fields or []
        for c in element.children.itervalues():
            if c.children:
                fields = self._get_all_fields(c, fields)
            if isinstance(c, Field):
                fields.append(c)
        return fields

    def get_active_fields(self, element=None, data=None):
        """Get all fields that meet their requirements.

        :param element:
        :param data: data to check requirements against
        :return: list of fields
        """
        fields = self._get_all_fields(element)
        if fields:
            data = data or self.data
        return filter(lambda f: f.has_requirements(data), fields)

    def _update_nuci_data(self):
        for field in self._get_all_fields():
            if field.nuci_path:
                value = self._nuci_config.find_child(field.nuci_path)
                if value:
                    if not field.nuci_preproc:
                        preprocessed = value.value
                    else:
                        preprocessed = field.nuci_preproc(value)
                    # update if result is not None
                    if preprocessed is not None:
                        self._nuci_data[field.name] = preprocessed
            elif field.nuci_preproc:
                # we have preproc method, but no path - just pass all the data to preproc function
                preprocessed = field.nuci_preproc(self._nuci_config)
                # update if result is not None
                if preprocessed is not None:
                    self._nuci_data[field.name] = preprocessed

    def add_section(self, *args, **kwargs):
        """

        :param args:
        :param kwargs:
        :return: new Section
        :rtype: Section
        """
        if len(args) and isinstance(args[0], Section):
            return self._add(args[0])
        return self._add(Section(self, *args, **kwargs))

    @property
    def active_fields(self):
        return self.get_active_fields()

    @property
    def errors(self):
        return self._form.note

    def render(self):
        result = "<div class=\"errors\">%s</div>" % self.errors
        result += "\n".join(c.render() for c in self.children.itervalues())
        return result

    def save(self):
        self.process_callbacks(self.data)
        commit()

    def validate(self):
        self.validated = True
        return self._form.validates(self.data)

    def add_callback(self, cb):
        """Add callback function.

        Callback is a function taking argument `data` (contains form data) and returning
        a tuple `(action, *args)`.
        Action can be one of following:
            - edit_config: args is Uci instance - send command for modifying Uci structure
            - save_result: arg[0] is dict of result_name->result - results are saved to
                           dictionary callback_results (instance attribute)
                           ValueError is raised when two callbacks use same result_name
            - none: do nothing, everything has been processed in the callback function

        :param cb: callback function
        :return: None
        """
        self.callbacks.append(cb)

    def process_callbacks(self, form_data):
        logger.debug("Processing callbacks")
        for cb in self.callbacks:
            logger.debug("Processing callback: %s", cb)
            cb_result = cb(form_data)
            operation = cb_result[0]
            if operation == "none":
                pass
            elif operation == "save_result":
                for k, v in cb_result[1].iteritems():
                    if k in self.callback_results:
                        raise ValueError("save_result callback returned result with duplicate name: '%s'" % k)
                    self.callback_results[k] = v
            elif operation == "edit_config":
                data = cb_result[1:] if len(cb_result) > 1 else ()
                add_config_update(*data)
            else:
                raise NotImplementedError("Unsupported callback operation: %s" % operation)