Пример #1
0
    def save_entry(self):
        '''Saves an answer POSTed to the form, and stores a new entry to it.'''
        collector, form = self._get_collector_and_form()
        if collector is None:
            return HTTPNotFound()
        form_schema, entry_form = self._get_schema_and_form(form)
        form_data = self.request.params.items()
        try:
            form_data = entry_form.validate(form_data)
        except d.ValidationFailure as e:
            return dict(collector=collector, entry_form=e.render(), form=form)
        entry = Entry()
        # Get the last increment of the entry number and update entry and form
        new_entry_number = form.last_entry_number + 1
        entry.entry_number = new_entry_number
        entry.form = form
        entry.collector = collector
        sas.add(entry)
        sas.flush()
        form.last_entry_number = new_entry_number
        form.new_entries += 1
        for f in form.fields:
            field = fields_dict[f.typ.name](f)
            field.save_data(entry, form_data['input-{}'.format(f.id)])

        # Sends email to facilitator if that's the case
        if collector.email_each_entry:
            self._send_email_entry(entry)

        if collector.on_completion=='url' and collector.thanks_url:
            return HTTPFound(location=collector.thanks_url)
        else:
            return HTTPFound(location=self.url('entry_form_slug',
                action='thank', slug=collector.slug))
Пример #2
0
    def __setitem__(self, name, value):
        if not value or not 'uid' in value:
            return

        uid = value['uid']
        if not uid:
            return

        path = os.path.join(self.upload_temp_dir, uid)

        fp = value['fp']
        if not fp or fp.closed or (hasattr(fp, 'name') and fp.name == path):
            return

        f = open(path, 'wb')
        try:
            f.write(fp.read())
        finally:
            f.close()
            fp.close()
        data = sas.query(FileUploadTempStore) \
                .filter(FileUploadTempStore.uid == unicode(uid)).first()
        if not data:
            data = FileUploadTempStore()
        data.created = datetime.datetime.utcnow()
        data.uid = unicode(uid)
        data.mimetype = unicode(value['mimetype'])
        data.filename = unicode(value['filename'])
        data.size = int(value['size'])
        data.path = unicode(path)
        data.thumbnail_path = unicode('')
        sas.add(data)
Пример #3
0
    def save_entry(self):
        '''Saves an answer POSTed to the form, and stores a new entry to it.'''
        collector, form = self._get_collector_and_form()
        if collector is None:
            return HTTPNotFound()
        form_schema, entry_form = self._get_schema_and_form(form)
        form_data = self.request.params.items()
        try:
            form_data = entry_form.validate(form_data)
        except d.ValidationFailure as e:
            return dict(collector=collector, entry_form=e.render(), form=form)
        entry = Entry()
        # Get the last increment of the entry number and update entry and form
        new_entry_number = form.last_entry_number + 1
        entry.entry_number = new_entry_number
        entry.form = form
        entry.collector = collector
        sas.add(entry)
        sas.flush()
        form.last_entry_number = new_entry_number
        form.new_entries += 1
        for f in form.fields:
            field = fields_dict[f.typ.name](f)
            field.save_data(entry, form_data['input-{}'.format(f.id)])

        # Sends email to facilitator if that's the case
        if collector.email_each_entry:
            self._send_email_entry(entry)

        if collector.on_completion == 'url' and collector.thanks_url:
            return HTTPFound(location=collector.thanks_url)
        else:
            return HTTPFound(location=self.url(
                'entry_form_slug', action='thank', slug=collector.slug))
Пример #4
0
    def __setitem__(self, name, value):
        if not value or not 'uid' in value:
            return

        uid = value['uid']
        if not uid:
            return

        path = os.path.join(self.upload_temp_dir, uid)

        fp = value['fp']
        if not fp or fp.closed or (hasattr(fp, 'name') and fp.name == path):
            return

        f = open(path, 'wb')
        try:
            f.write(fp.read())
        finally:
            f.close()
            fp.close()
        data = sas.query(FileUploadTempStore) \
                .filter(FileUploadTempStore.uid == unicode(uid)).first()
        if not data:
            data = FileUploadTempStore()
        data.created = datetime.datetime.utcnow()
        data.uid = unicode(uid)
        data.mimetype = unicode(value['mimetype'])
        data.filename = unicode(value['filename'])
        data.size = int(value['size'])
        data.path = unicode(path)
        data.thumbnail_path = unicode('')
        sas.add(data)
Пример #5
0
 def edit_category(self):
     '''Receives and validates POSTed data. If data is okay, creates the
     category. Otherwise, returns the form with errors.
     '''
     user = self.request.user
     controls = self.request.POST.items()
     try:
         appstruct = new_category_form(user).validate(controls)
     except d.ValidationFailure as e:
         self.request.override_renderer = 'create_category.genshi'
         return dict(pagetitle=_("New category"),
                     new_category_form=e.render())
     user = self.request.user
     cat_name = appstruct['name']
     cat_desc = appstruct['description']
     if cat_name != '':
         category = sas.query(FormCategory) \
                 .filter(FormCategory.name==cat_name) \
                 .filter(FormCategory.user==user) \
                 .first()
     if category:  # If the system found a category, don't create
         errors = _("That category already exists.")
         return {'errors': errors}
     else:  # Create a category!
         new_category = FormCategory(name=cat_name,
                                     description=cat_desc,
                                     user=user)
         sas.add(new_category)
         sas.flush()
         all_data = user.all_categories_and_forms()
         return dict(changed=True, all_data=all_data)
Пример #6
0
    def save_website_code(self):
        '''Responds to the AJAX request and saves a collector.'''
        request = self.request
        posted = request.POST
        id = request.matchdict['id']
        form_id = request.matchdict['form_id']
        form = FormView(request)._get_form_if_belongs_to_user(form_id=form_id)
        if not form:
            return dict(error=_("Form not found."))

        # Validate `posted` with colander:
        try:
            posted = website_code_schema.deserialize(posted)
        except c.Invalid as e:
            return e.asdict()

        # Validation passes, so create or update the model.
        if id == 'new':
            collector = WebsiteCodeCollector(form=form)
            sas.add(collector)
        else:
            collector = self._get_collector_if_belongs_to_user(id)
        # Copy the data
        self._parse_start_and_end_date(posted)
        for k, v in posted.items():
            setattr(collector, k, v)
        sas.flush()
        return collector.to_dict()
Пример #7
0
 def edit_category(self):
     '''Receives and validates POSTed data. If data is okay, creates the
     category. Otherwise, returns the form with errors.
     '''
     user = self.request.user
     controls = self.request.POST.items()
     try:
         appstruct = new_category_form(user).validate(controls)
     except d.ValidationFailure as e:
         self.request.override_renderer = 'create_category.genshi'
         return dict(pagetitle=_("New category"),
             new_category_form=e.render())
     user = self.request.user
     cat_name = appstruct['name']
     cat_desc = appstruct['description']
     if cat_name != '':
         category = sas.query(FormCategory) \
                 .filter(FormCategory.name==cat_name) \
                 .filter(FormCategory.user==user) \
                 .first()
     if category:  # If the system found a category, don't create
         errors = _("That category already exists.")
         return {'errors': errors}
     else:  # Create a category!
         new_category = FormCategory(name=cat_name, description=cat_desc,
                                     user=user)
         sas.add(new_category)
         sas.flush()
         all_data = user.all_categories_and_forms()
         return dict(changed=True, all_data=all_data)
Пример #8
0
    def save_data(self, entry, value):
        if value is c.null:
            return # There is nothing to save

        uid = value['uid']
        if not uid:
            return # Invalid value

        tmp = tmpstore.get(uid)

        if tmp and 'fp' in tmp:

            form_id = entry.form_id
            entry_number = entry.entry_number
            collector_slug = entry.collector.slug

            # Create directory hierarchy
            from mootiro_web.pyramid_starter import makedirs
            bunch_dir = os.path.join(self.upload_data_dir,
                                     '%03d' % (form_id % 1000))
            makedirs(bunch_dir)

            form_dir = os.path.join(bunch_dir,'form_%d' % form_id)
            makedirs(form_dir)

            collector_dir = os.path.join(form_dir, collector_slug)
            makedirs(collector_dir)

            start = entry_number - (entry_number % 100)
            end = start + 99
            entry_range = '%d-%d' % (start, end)
            range_dir = os.path.join(collector_dir, entry_range)
            makedirs(range_dir)

            mimetype = value['mimetype']
            original_filename = value['filename']
            extension = mimetypes.guess_extension(mimetype)

            filename = 'entry{entry_number}_field{field_id}{extension}' \
                           .format(entry_number=entry_number,
                                   field_id=self.field.id,
                                   extension=extension)

            path = os.path.join(range_dir, filename)

            # Save file
            fp = tmp['fp']
            open(path, 'wb').write(fp.read())
            size = os.path.getsize(path)

            # Save db row
            self.data = FileData()
            self.data.mimetype = unicode(mimetype)
            self.data.filename = unicode(original_filename)
            self.data.size = int(size)
            self.data.path = unicode(path)
            self.data.field_id = int(self.field.id)
            self.data.entry_id = int(entry.id)
            sas.add(self.data)
Пример #9
0
    def save_data(self, entry, value):
        if value is c.null:
            return  # There is nothing to save

        uid = value['uid']
        if not uid:
            return  # Invalid value

        tmp = tmpstore.get(uid)

        if tmp and 'fp' in tmp:

            form_id = entry.form_id
            entry_number = entry.entry_number
            collector_slug = entry.collector.slug

            # Create directory hierarchy
            from mootiro_web.pyramid_starter import makedirs
            bunch_dir = os.path.join(self.upload_data_dir,
                                     '%03d' % (form_id % 1000))
            makedirs(bunch_dir)

            form_dir = os.path.join(bunch_dir, 'form_%d' % form_id)
            makedirs(form_dir)

            collector_dir = os.path.join(form_dir, collector_slug)
            makedirs(collector_dir)

            start = entry_number - (entry_number % 100)
            end = start + 99
            entry_range = '%d-%d' % (start, end)
            range_dir = os.path.join(collector_dir, entry_range)
            makedirs(range_dir)

            mimetype = value['mimetype']
            original_filename = value['filename']
            extension = mimetypes.guess_extension(mimetype)

            filename = 'entry{entry_number}_field{field_id}{extension}' \
                           .format(entry_number=entry_number,
                                   field_id=self.field.id,
                                   extension=extension)

            path = os.path.join(range_dir, filename)

            # Save file
            fp = tmp['fp']
            open(path, 'wb').write(fp.read())
            size = os.path.getsize(path)

            # Save db row
            self.data = FileData()
            self.data.mimetype = unicode(mimetype)
            self.data.filename = unicode(original_filename)
            self.data.size = int(size)
            self.data.path = unicode(path)
            self.data.field_id = int(self.field.id)
            self.data.entry_id = int(entry.id)
            sas.add(self.data)
Пример #10
0
 def save_data(self, entry, value):
     if value != '':
         value = value.replace(',', '.')
         self.data = NumberData()
         self.data.field_id = self.field.id
         self.data.entry_id = entry.id
         self.data.value = value
         sas.add(self.data)
Пример #11
0
 def save_data(self, entry, value):
     if value != '':
         value = value.replace(',', '.')
         self.data = NumberData()
         self.data.field_id = self.field.id
         self.data.entry_id = entry.id
         self.data.value = value
         sas.add(self.data)
Пример #12
0
 def save_data(self, entry, value):
     if value != c.null:
         self.data = DateData()
         self.data.field_id = self.field.id
         self.data.entry_id = entry.id
         self.data.value = datetime.strptime(value,
                 df.formats[int(self.field.get_option \
                     ('input_date_format'))]['py'])
         sas.add(self.data)
Пример #13
0
 def save_data(self, entry, value):
     if value != c.null:
         self.data = DateData()
         self.data.field_id = self.field.id
         self.data.entry_id = entry.id
         self.data.value = datetime.strptime(value,
                 df.formats[int(self.field.get_option \
                     ('input_date_format'))]['py'])
         sas.add(self.data)
Пример #14
0
    def copy(self):
        field_option_copy = FieldOption()

        # field option instance copy
        for attr in ('option', 'value'):
            field_option_copy.__setattr__(attr, self.__getattribute__(attr))

        sas.add(field_option_copy)

        return field_option_copy
Пример #15
0
    def copy(self):
        field_option_copy = FieldOption()

        # field option instance copy
        for attr in ('option', 'value'):
            field_option_copy.__setattr__(attr, self.__getattribute__(attr))

        sas.add(field_option_copy)

        return field_option_copy
Пример #16
0
 def copy(self):
     form_copy = Form()
     # form instance copy
     for attr in self.copy_props:
         setattr(form_copy, attr, getattr(self, attr))
     # fields copy
     for f in self.fields:
         form_copy.fields.append(f.copy())
     sas.add(form_copy)
     return form_copy
Пример #17
0
 def copy(self):
     form_copy = Form()
     # form instance copy
     for attr in self.copy_props:
         setattr(form_copy, attr, getattr(self, attr))
     # fields copy
     for f in self.fields:
         form_copy.fields.append(f.copy())
     sas.add(form_copy)
     return form_copy
Пример #18
0
 def copy(self):
     field_copy = Field()
     # field instance copy
     for attr in self.copy_props:
         field_copy.__setattr__(attr, self.__getattribute__(attr))
     # field options copy
     for o in self.options:
         field_copy.options.append(o.copy())
     # field specific options copy
     fieldtype = fields_dict[self.typ.name](field_copy)
     if getattr(fieldtype, 'copy', None):
         fieldtype.copy(self)
     sas.add(field_copy)
     return field_copy
Пример #19
0
    def copy(self, base_field):
        '''Receives base_field and copies its specific options into self.field
        options.
        Do not copy options of the field_option model, just specific ones.
        '''
        # iterate over all list options
        for base_lo in base_field.list_options:
            # option instance copy
            lo_copy = ListOption()
            lo_copy.field = self.field
            for attr in ('label', 'value', 'opt_default', 'position', 'status'):
                lo_copy.__setattr__(attr, base_lo.__getattribute__(attr))

            sas.add(lo_copy)
Пример #20
0
 def copy(self):
     field_copy = Field()
     # field instance copy
     for attr in self.copy_props:
         field_copy.__setattr__(attr, self.__getattribute__(attr))
     # field options copy
     for o in self.options:
         field_copy.options.append(o.copy())
     # field specific options copy
     fieldtype = fields_dict[self.typ.name](field_copy)
     if getattr(fieldtype, 'copy', None):
         fieldtype.copy(self)
     sas.add(field_copy)
     return field_copy
Пример #21
0
    def save_data(self, entry, value):
        list_type = self.field.get_option('list_type')

        if value:
            if value['option'] and value['option'] != c.null:
                if list_type != 'radio':
                    for opt in filter(lambda o: o != '', value['option']):
                        self.data = ListData()
                        # TODO: Check if is a valid value
                        self.data.value = opt
                        self.data.entry_id = entry.id
                        self.data.field_id = self.field.id
                        sas.add(self.data)
                else:
                    self.data = ListData()
                    # TODO: Check if is a valid value
                    self.data.value = value['option']
                    self.data.entry_id = entry.id
                    self.data.field_id = self.field.id
                    sas.add(self.data)

            if value.has_key('other') and value['other'] != '':
                moderated = self.field.get_option('moderated')
                case_sensitive = self.field.get_option('case_sensitive')

                if case_sensitive == 'true':
                    option = sas.query(ListOption) \
                                .filter(ListOption.label == value['other']) \
                                .filter(ListOption.field_id == self.field.id) \
                                .first()
                else:
                    option = sas.query(ListOption) \
                            .filter(ListOption.label.ilike(value['other'])) \
                            .filter(ListOption.field_id == self.field.id) \
                            .first()

                no_options = sas.query(ListOption) \
                        .filter(ListOption.field_id == self.field.id).count()

                if not option:
                    lo = ListOption()
                    lo.label = value['other']
                    lo.value = lo.label
                    lo.field = self.field
                    lo.position = no_options
                    lo.status = 'Approved' if moderated == 'false' \
                        else 'Awaiting moderation'
                    sas.add(lo)
                    sas.flush()
                else:
                    lo = option

                data = ListData()
                # TODO: Check if is a valid value
                data.value = lo.id
                data.entry_id = entry.id
                data.field_id = self.field.id
                sas.add(data)
Пример #22
0
    def save_options(self, options):
        '''Persists specific field properties.'''
        self.save_option('default', options['defaul'])  # the default value

        for key in ('list_type', 'multiple_choice', 'sort_choices',
                    'new_option_label',
                    'min_num',  # minimum number of choices
                    'max_num',  # maximum number of choices
                    'size_options',     # number of choices
                    'moderated',  # other moderated
                    'new_option',  # possible to add a new option
                    'case_sensitive',  # other case sensitive
                    'opt_restrictions', # restricted number of options
                   # 'export_in_columns',  # when creating a CSV
                   ):
            self.save_option(key, options[key])

        inserted_options = {}
        for option_id, opt in options['options'].items():
            if opt['option_id'] != 'new':
                lo = sas.query(ListOption).get(opt['option_id'])
                lo.label = opt['label']
                lo.value = opt['value']
                lo.opt_default = opt['opt_default']
                lo.position = opt['position']
                # lo.status = opt['status']
                # To prevent KeyError, Nando changed the above line to:
                lo.status = opt.get('status', 'Form owner')
            else:
                lo = ListOption()
                lo.label = opt['label']
                lo.value = opt['value']
                lo.opt_default = opt['opt_default']
                lo.field = self.field
                lo.position = opt['position']
                lo.status = 'Form owner'
                sas.add(lo)
                sas.flush()
                inserted_options[option_id] = lo.id

        # Delete options
        for list_option_id in options['deleteOptions']:
            lo = sas.query(ListOption).get(list_option_id)
            if lo:
                sas.delete(lo)
        return {'insertedOptions': inserted_options}
Пример #23
0
    def save_thumbnail(self, entry, value):
        from PIL import Image
        f = self.field

        path = self.path(entry)
        thumbnail_path = path[:path.rfind('.')] + ".thumbnail.jpg"
        size = max(int(f.get_option('width')), int(f.get_option('height')))
        size = size, size

        if not path:
            return

        img = Image.open(path)
        img.thumbnail(size, Image.ANTIALIAS)
        img.save(thumbnail_path, "JPEG")

        data = sas.query(FileData) \
               .filter(FileData.field_id == f.id) \
               .filter(FileData.entry_id == entry.id).first()
        if data:
            data.thumbnail_path = thumbnail_path
            sas.add(data)
Пример #24
0
    def save_thumbnail(self, entry, value):
        from PIL import Image
        f = self.field

        path = self.path(entry)
        thumbnail_path = path[:path.rfind('.')] + ".thumbnail.jpg"
        size = max(int(f.get_option('width')), int(f.get_option('height')))
        size = size, size

        if not path:
            return

        img = Image.open(path)
        img.thumbnail(size, Image.ANTIALIAS)
        img.save(thumbnail_path, "JPEG")

        data = sas.query(FileData) \
               .filter(FileData.field_id == f.id) \
               .filter(FileData.entry_id == entry.id).first()
        if data:
            data.thumbnail_path = thumbnail_path
            sas.add(data)
Пример #25
0
    def save_form(self):
        """Responds to the AJAX request and saves a form with its fields."""
        request = self.request
        # TODO: Clean the posted json from malicious attacks such as XSS
        posted = json.loads(request.POST.pop("json"))
        # Validate the form panel (especially form name length)
        # TODO: Using deform for this was a mistake. We should use colander
        # only, and display errors using javascript, as we did on the
        # following method "rename".
        form_props = [
            ("_charset_", ""),
            ("__formid__", "FirstPanel"),
            ("name", posted["form_title"]),
            ("description", posted["form_desc"]),
            ("rich", posted["rich"]),
            ("use_rich", posted["use_rich"]),
            ("submit_label", posted["submit_label"]),
        ]
        dform = d.Form(form_schema, formid="FirstPanel")
        try:
            fprops = dform.validate(form_props)
        except d.ValidationFailure as e:
            # print(e.args, e.cstruct, e.error, e.field, e.message)
            return dict(panel_form=e.render(), error=_("Error loading your form"))
        # the form panel is validated and should always be returned
        panel_form = dform.render(form_props)

        # Validation passes, so create or update the form.
        form_id = posted["form_id"]
        if form_id == "new":
            form = Form(user=request.user)
            sas.add(form)
        else:
            form = self._get_form_if_belongs_to_user(form_id=form_id)
            if not form:
                return dict(error=_("Form not found."))

        # Set the form tab properties
        form.name = fprops["name"]
        form.description = fprops["description"]
        sl = fprops["submit_label"]
        form.submit_label = self.tr(sl) if isinstance(sl, TranslationString) else sl
        form.use_rich = posted["use_rich"]

        # Sanitize / scrub the rich HTML
        rich = posted["rich"]
        if rich:
            rich = self.clnr.clean_html(rich)
        form.rich = rich

        # Visual Tab Info
        st_id = posted["system_template_id"]
        if st_id:
            st = sas.query(FormTemplate).filter(FormTemplate.system_template_id == st_id).first()
            form.template = st

        if form_id == "new":  # TODO: really necessary anymore?
            sas.flush()  # so we get the form id

        # Get field positions
        positions = {f[: -len("_container")]: p for p, f in enumerate(posted["fields_position"])}

        # Save/Update the fields
        # Fields to delete
        for f_id in posted["deleteFields"]:
            # TODO: check what to do with the field answer data!!!
            field = sas.query(Field).join(Form).filter(Field.id == f_id).filter(Form.user_id == request.user.id).first()
            sas.delete(field)

        new_fields_id = {}
        save_options_result = {}
        for f in posted["fields"]:
            # Sanitize / scrub the rich HTML
            rich = f["rich"]
            if rich:
                f["rich"] = rich = self.clnr.clean_html(rich)

            if not f["field_id"]:
                raise RuntimeError("Cannot instantiate a field of ID {}".format(f["field_id"]))
            elif f["field_id"] == "new":
                field_type = sas.query(FieldType).filter(FieldType.name == f["type"]).first()
                # To solve a bug where field.save_options() would fail because
                # of a missing field ID, we instantiate the field here and flush
                field = Field(
                    typ=field_type,
                    form=form,
                    label=f["label"],
                    description=f["description"],
                    help_text=None,
                    use_rich=f["use_rich"],
                    rich=f["rich"],
                )
                sas.add(field)
                sas.flush()
                # TODO: Populating the above instance variables is probably
                # redundantly done elsewhere, but it must be done here.
            else:
                field = sas.query(Field).get(f["field_id"])
                if not field:
                    return dict(error=_("Sorry, your field could not be found: {}").format(f["field_id"]))

            f["position"] = positions[f["id"]]
            # Before the following call, the field must have an ID.
            # If the following line raises a FieldValidationError, Pyramid will
            # call the field_validation_error action.
            result = field.validate_and_save(f)
            if result:
                save_options_result[f["id"]] = result

            # If is a new field, need to inform the client about
            # the field id on DB after a flush
            if f["field_id"] == "new":
                sas.flush()
                new_fields_id[f["id"]] = {"field_id": field.id}

        rdict = {
            "form_id": form.id,
            "new_fields_id": new_fields_id,
            "save_options_result": save_options_result,
            "panel_form": panel_form,
        }
        return rdict
Пример #26
0
    def save_form(self):
        '''Responds to the AJAX request and saves a form with its fields.'''
        request = self.request
        # TODO: Clean the posted json from malicious attacks such as XSS
        posted = json.loads(request.POST.pop('json'))
        # Validate the form panel (especially form name length)
        # TODO: Using deform for this was a mistake. We should use colander
        # only, and display errors using javascript, as we did on the
        # following method "rename".
        form_props = [('_charset_', ''),
            ('__formid__', 'FirstPanel'),
            ('name', posted['form_title']),
            ('description', posted['form_desc']),
            ('rich', posted['rich']),
            ('use_rich', posted['use_rich']),
            ('submit_label', posted['submit_label'])
        ]
        dform = d.Form(form_schema, formid='FirstPanel')
        try:
            fprops = dform.validate(form_props)
        except d.ValidationFailure as e:
            # print(e.args, e.cstruct, e.error, e.field, e.message)
            return dict(panel_form=e.render(),
                        error=_('Error loading your form'))
        # the form panel is validated and should always be returned
        panel_form = dform.render(form_props)

        # Validation passes, so create or update the form.
        form_id = posted['form_id']
        if form_id == 'new':
            form = Form(user=request.user)
            sas.add(form)
        else:
            form = self._get_form_if_belongs_to_user(form_id=form_id)
            if not form:
                return dict(error=_('Form not found.'))

        # Set the form tab properties
        form.name = fprops['name']
        form.description = fprops['description']
        sl = fprops['submit_label']
        form.submit_label = \
            self.tr(sl) if isinstance(sl, TranslationString) else sl
        form.use_rich = posted['use_rich']

        # Sanitize / scrub the rich HTML
        rich = posted['rich']
        if rich:
            rich = self.clnr.clean_html(rich)
        form.rich = rich

        # Visual Tab Info
        st_id = posted['system_template_id']
        if st_id:
            st = sas.query(FormTemplate). \
                filter(FormTemplate.system_template_id == st_id).first()
            form.template = st

        if form_id == 'new':  # TODO: really necessary anymore?
            sas.flush()  # so we get the form id

        # Get field positions
        positions = {f[:-len("_container")]: p for p, f in \
                            enumerate(posted['fields_position'])}

        # Save/Update the fields
        # Fields to delete
        for f_id in posted['deleteFields']:
            # TODO: check what to do with the field answer data!!!
            field = sas.query(Field).join(Form).filter(Field.id == f_id)\
                        .filter(Form.user_id == request.user.id).first()
            sas.delete(field)

        new_fields_id = {}
        save_options_result = {}
        for f in posted['fields']:
            # Sanitize / scrub the rich HTML
            rich = f['rich']
            if rich:
                f['rich'] = rich = self.clnr.clean_html(rich)

            if not f['field_id']:
                raise RuntimeError('Cannot instantiate a field of ID {}' \
                    .format(f['field_id']))
            elif f['field_id'] == 'new':
                field_type = sas.query(FieldType) \
                    .filter(FieldType.name == f['type']).first()
                # To solve a bug where field.save_options() would fail because
                # of a missing field ID, we instantiate the field here and flush
                field = Field(typ=field_type, form=form, label=f['label'],
                    description=f['description'], help_text=None,
                    use_rich=f['use_rich'], rich=f['rich'])
                sas.add(field)
                sas.flush()
                # TODO: Populating the above instance variables is probably
                # redundantly done elsewhere, but it must be done here.
            else:
                field = sas.query(Field).get(f['field_id'])
                if not field:
                    return dict(error=_("Sorry, your field could not be found: {}") \
                        .format(f['field_id']))

            f['position'] = positions[f['id']]
            # Before the following call, the field must have an ID.
            # If the following line raises a FieldValidationError, Pyramid will
            # call the field_validation_error action.
            result = field.validate_and_save(f)
            if result:
                save_options_result[f['id']] = result

            # If is a new field, need to inform the client about
            # the field id on DB after a flush
            if f['field_id'] == 'new':
                sas.flush()
                new_fields_id[f['id']] = {'field_id': field.id}

        rdict = {'form_id': form.id,
                'new_fields_id': new_fields_id,
                'save_options_result': save_options_result,
                'panel_form': panel_form,
                }
        return rdict