diff -r c07f3accf04a -r f2a85f52b9e5 web/formfields.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/formfields.py Wed Mar 11 23:25:29 2009 +0100 @@ -0,0 +1,382 @@ +"""field classes for form construction + +:organization: Logilab +:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +class Field(object): + """field class is introduced to control what's displayed in edition form + """ + widget = TextInput + needs_multipart = False + creation_rank = 0 + + def __init__(self, name=None, id=None, label=None, + widget=None, required=False, initial=None, + choices=None, help=None, eidparam=False): + self.required = required + if widget is not None: + self.widget = widget + if isinstance(self.widget, type): + self.widget = self.widget() + self.name = name + self.label = label or name + self.id = id or name + self.initial = initial + self.choices = choices + self.help = help + self.eidparam = eidparam + self.role = 'subject' + # global fields ordering in forms + self.creation_rank = Field.creation_rank + Field.creation_rank += 1 + + def __unicode__(self): + return u'<%s name=%r label=%r id=%r initial=%r>' % ( + self.__class__.__name__, self.name, self.label, + self.id, self.initial) + + def __repr__(self): + return self.__unicode__().encode('utf-8') + + def set_name(self, name): + assert name + self.name = name + if not self.id: + self.id = name + if not self.label: + self.label = name + + def is_visible(self): + return not isinstance(self.widget, HiddenInput) + + def actual_fields(self, form): + yield self + + def format_value(self, req, value): + if isinstance(value, (list, tuple)): + return [self.format_single_value(req, val) for val in value] + return self.format_single_value(req, value) + + def format_single_value(self, req, value): + if value is None: + return u'' + return unicode(value) + + def get_widget(self, form): + return self.widget + + def example_format(self, req): + return u'' + + def render(self, form, renderer): + return self.get_widget(form).render(form, self) + + def vocabulary(self, form): + if self.choices is not None: + return self.choices + return form.form_field_vocabulary(self) + + +class StringField(Field): + def __init__(self, max_length=None, **kwargs): + super(StringField, self).__init__(**kwargs) + self.max_length = max_length + + +class TextField(Field): + widget = TextArea + def __init__(self, rows=10, cols=80, **kwargs): + super(TextField, self).__init__(**kwargs) + self.rows = rows + self.cols = cols + + +class RichTextField(TextField): + widget = None + def __init__(self, format_field=None, **kwargs): + super(RichTextField, self).__init__(**kwargs) + self.format_field = format_field + + def get_widget(self, form): + if self.widget is None: + if self.use_fckeditor(form): + return FCKEditor() + return TextArea() + return self.widget + + def get_format_field(self, form): + if self.format_field: + return self.format_field + # we have to cache generated field since it's use as key in the + # context dictionnary + req = form.req + try: + return req.data[self] + except KeyError: + if self.use_fckeditor(form): + # if fckeditor is used and format field isn't explicitly + # deactivated, we want an hidden field for the format + widget = HiddenInput() + choices = None + else: + # else we want a format selector + # XXX compute vocabulary + widget = Select + choices = [(req._(format), format) for format in FormatConstraint().vocabulary(req=req)] + field = StringField(name=self.name + '_format', widget=widget, + choices=choices) + req.data[self] = field + return field + + def actual_fields(self, form): + yield self + format_field = self.get_format_field(form) + if format_field: + yield format_field + + def use_fckeditor(self, form): + """return True if fckeditor should be used to edit entity's attribute named + `attr`, according to user preferences + """ + if form.req.use_fckeditor(): + return form.form_field_format(self) == 'text/html' + return False + + def render(self, form, renderer): + format_field = self.get_format_field(form) + if format_field: + result = format_field.render(form, renderer) + else: + result = u'' + return result + self.get_widget(form).render(form, self) + + +class FileField(StringField): + widget = FileInput + needs_multipart = True + + def __init__(self, format_field=None, encoding_field=None, **kwargs): + super(FileField, self).__init__(**kwargs) + self.format_field = format_field + self.encoding_field = encoding_field + + def actual_fields(self, form): + yield self + if self.format_field: + yield self.format_field + if self.encoding_field: + yield self.encoding_field + + def render(self, form, renderer): + wdgs = [self.get_widget(form).render(form, self)] + if self.format_field or self.encoding_field: + divid = '%s-advanced' % form.context[self]['name'] + wdgs.append(u'%s' % + (html_escape(toggle_action(divid)), + form.req._('show advanced fields'), + html_escape(form.req.build_url('data/puce_down.png')), + form.req._('show advanced fields'))) + wdgs.append(u'') + if not self.required and form.context[self]['value']: + # trick to be able to delete an uploaded file + wdgs.append(u'
') + wdgs.append(tags.input(name=u'%s__detach' % form.context[self]['name'], + type=u'checkbox')) + wdgs.append(form.req._('detach attached file')) + return u'\n'.join(wdgs) + + def render_subfield(self, form, field, renderer): + return (renderer.render_label(form, field) + + field.render(form, renderer) + + renderer.render_help(form, field) + + u'
') + + +class EditableFileField(FileField): + editable_formats = ('text/plain', 'text/html', 'text/rest') + + def render(self, form, renderer): + wdgs = [super(EditableFileField, self).render(form, renderer)] + if form.form_field_format(self) in self.editable_formats: + data = form.form_field_value(self, {}, load_bytes=True) + if data: + encoding = form.form_field_encoding(self) + try: + form.context[self]['value'] = unicode(data.getvalue(), encoding) + except UnicodeError: + pass + else: + if not self.required: + msg = form.req._( + 'You can either submit a new file using the browse button above' + ', or choose to remove already uploaded file by checking the ' + '"detach attached file" check-box, or edit file content online ' + 'with the widget below.') + else: + msg = form.req._( + 'You can either submit a new file using the browse button above' + ', or edit file content online with the widget below.') + wdgs.append(u'

%s

' % msg) + wdgs.append(TextArea(setdomid=False).render(form, self)) + # XXX restore form context? + return '\n'.join(wdgs) + + +class IntField(Field): + def __init__(self, min=None, max=None, **kwargs): + super(IntField, self).__init__(**kwargs) + self.min = min + self.max = max + + +class BooleanField(Field): + widget = Radio + + def vocabulary(self, form): + if self.choices: + return self.choices + return [(form.req._('yes'), '1'), (form.req._('no'), '')] + + +class FloatField(IntField): + def format_single_value(self, req, value): + formatstr = entity.req.property_value('ui.float-format') + if value is None: + return u'' + return formatstr % float(value) + + def render_example(self, req): + return self.format_value(req, 1.234) + + +class DateField(StringField): + format_prop = 'ui.date-format' + widget = DateTimePicker + + def format_single_value(self, req, value): + return value and ustrftime(value, req.property_value(self.format_prop)) or u'' + + def render_example(self, req): + return self.format_value(req, datetime.now()) + + +class DateTimeField(DateField): + format_prop = 'ui.datetime-format' + + +class TimeField(DateField): + format_prop = 'ui.datetime-format' + + +class HiddenInitialValueField(Field): + def __init__(self, visible_field, name): + super(HiddenInitialValueField, self).__init__(name=name, + widget=HiddenInput, + eidparam=True) + self.visible_field = visible_field + + +class RelationField(Field): + def __init__(self, **kwargs): + super(RelationField, self).__init__(**kwargs) + + @staticmethod + def fromcardinality(card, role, **kwargs): + return RelationField(widget=Select(multiple=card in '*+'), + **kwargs) + + def vocabulary(self, form): + entity = form.entity + req = entity.req + # first see if its specified by __linkto form parameters + linkedto = entity.linked_to(self.name, self.role) + if linkedto: + entities = (req.eid_rset(eid).get_entity(0, 0) for eid in linkedto) + return [(entity.view('combobox'), entity.eid) for entity in entities] + # it isn't, check if the entity provides a method to get correct values + res = [] + if not self.required: + res.append(('', INTERNAL_FIELD_VALUE)) + # vocabulary doesn't include current values, add them + if entity.has_eid(): + rset = entity.related(self.name, self.role) + relatedvocab = [(e.view('combobox'), e.eid) for e in rset.entities()] + else: + relatedvocab = [] + return res + form.form_field_vocabulary(self) + relatedvocab + + def format_single_value(self, req, value): + return value + + +def stringfield_from_constraints(constraints, **kwargs): + field = None + for cstr in constraints: + if isinstance(cstr, StaticVocabularyConstraint): + return StringField(widget=Select(vocabulary=cstr.vocabulary), + **kwargs) + if isinstance(cstr, SizeConstraint) and cstr.max is not None: + if cstr.max > 257: + field = textfield_from_constraint(cstr, **kwargs) + else: + field = StringField(max_length=cstr.max, **kwargs) + return field or TextField(**kwargs) + + +def textfield_from_constraint(constraint, **kwargs): + if 256 < constraint.max < 513: + rows, cols = 5, 60 + else: + rows, cols = 10, 80 + return TextField(rows, cols, **kwargs) + + +def find_field(eclass, subjschema, rschema, role='subject'): + """return the most adapated widget to edit the relation + 'subjschema rschema objschema' according to information found in the schema + """ + fieldclass = None + if role == 'subject': + objschema = rschema.objects(subjschema)[0] + cardidx = 0 + else: + objschema = rschema.subjects(subjschema)[0] + cardidx = 1 + card = rschema.rproperty(subjschema, objschema, 'cardinality')[cardidx] + required = card in '1+' + if rschema in eclass.widgets: + fieldclass = eclass.widgets[rschema] + if isinstance(fieldclass, basestring): + return StringField(name=rschema.type) + elif not rschema.is_final(): + return RelationField.fromcardinality(card, role,name=rschema.type, + required=required) + else: + fieldclass = FIELDS[objschema] + if fieldclass is StringField: + constraints = rschema.rproperty(subjschema, objschema, 'constraints') + return stringfield_from_constraints(constraints, name=rschema.type, + required=required) + return fieldclass(name=rschema.type, required=required) + +FIELDS = { + 'Boolean': BooleanField, + 'Bytes': FileField, + 'Date': DateField, + 'Datetime': DateTimeField, + 'Int': IntField, + 'Float': FloatField, + 'Decimal': StringField, + 'Password': StringField, + 'String' : StringField, + 'Time': TimeField, + } +views/