class OptionsField(CompositeField): """ Options field ``key``: Name of group key name ``defaults``: Build defaults for unselected groups ``extract_all``: Extract values for all groups """ key = '' defaults = False extract_all = False tmpl_input = 'form:options' def __init__(self, *args, **kw): super(OptionsField, self).__init__(*args, **kw) voc = vocabulary.Vocabulary( *[vocabulary.Term(fname, fname, field.title) for fname, field in self.fields.items()]) if not self.key: self.key = self.name self.fields = Fieldset( RadioField( self.key, missing = voc[0].value, default = voc[0].value, required = False, vocabulary = voc)) + self.fields def to_field(self, value): value = super(OptionsField, self).to_field(value) if self.defaults: for name, f in self.fields.items(): if name not in value: value[name] = (f.default if f.default is not null else f.missing) return value def validate(self, value): key = value.get(self.key) if key not in self.fields: key = self.fields[self.key].default super(OptionsField, self).validate( {key: value.get(key, self.fields[key].missing)}) def extract(self): value = super(OptionsField, self).extract() if not self.extract_all: opotion = value[self.key] if opotion in value: return {self.key: opotion, opotion: value[opotion]} else: return {} return value
class CompositeField(Field): """ Composit field """ fields = None tmpl_input = 'form:composite' tmpl_widget = 'form:widget-composite' inline = False consolidate_errors = False def __init__(self, *args, **kw): super(CompositeField, self).__init__(*args, **kw) if not self.fields: raise ValueError('Fields are required for composite field.') if not isinstance(self.fields, Fieldset): self.fields = Fieldset(*self.fields) self.fields.prefix = '%s.'%self.name @reify def default(self): return dict( [(name, field.default if field.default is not null else field.missing) for name, field in self.fields.items()]) def bind(self, request, prefix, value, params, context=None): """ Bind field to value and request params """ if value in (null, None): value = {} clone = super(CompositeField, self).bind( request, prefix, value, params, context) clone.fields = self.fields.bind(request, value, params, '', context) return clone def set_id_prefix(self, prefix): self.id = ('%s%s'%(prefix, self.name)).replace('.', '-') prefix = '%s%s.'%(prefix, self.name) for name, field in self.fields.items(): field.set_id_prefix(prefix) def update(self): """ Update field, prepare field for rendering """ super(CompositeField, self).update() for field in self.fields.values(): field.update() def to_field(self, value): """ convert form value to field value """ result = {} errors = [] for name, val in value.items(): field = self.fields[name] try: result[name] = field.to_field(val) except Invalid as error: error.name = name errors.append(error) if field.error is None: field.error = error if errors: if self.consolidate_errors: raise CompositeError(errors[0].msg, field=self) else: raise CompositeError(field=self, errors=errors) return result def validate(self, value): """ validate value """ errors = [] for name, val in value.items(): field = self.fields[name] try: field.validate(val) except Invalid as error: error.name = name errors.append(error) if field.error is None: field.error = error if errors: if self.consolidate_errors: raise CompositeError(errors[0].msg, field=self) else: raise CompositeError(field=self, errors=errors) if self.validator is not None: self.validator(self, value) def extract(self): value = {} for name, field in self.fields.items(): val = field.extract() if val is null and field.missing is not null: val = copy.copy(field.missing) value[name] = val return value def flatten(self, value): for name, field in self.fields.items(): if field.flat and name in value: value.update(field.flatten(value.pop(name))) return value