# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1236810329 -3600 # Node ID f2a85f52b9e51feac04714875fb3fea98e8c0457 # Parent c07f3accf04ac0f6e0b94a0f7078ac380fb2888c move fields and widgets to their own module diff -r c07f3accf04a -r f2a85f52b9e5 web/form.py --- a/web/form.py Wed Mar 11 19:54:30 2009 +0100 +++ b/web/form.py Wed Mar 11 23:25:29 2009 +0100 @@ -251,535 +251,6 @@ ############################################################################### -from cubicweb.common import tags - -# widgets ############ - -class FieldWidget(object): - needs_js = () - needs_css = () - setdomid = True - settabindex = True - - def __init__(self, attrs=None, setdomid=None, settabindex=None): - self.attrs = attrs or {} - if setdomid is not None: - # override class's default value - self.setdomid = setdomid - if settabindex is not None: - # override class's default value - self.settabindex = settabindex - - def add_media(self, form): - """adds media (CSS & JS) required by this widget""" - req = form.req - if self.needs_js: - req.add_js(self.needs_js) - if self.needs_css: - req.add_css(self.needs_css) - - def render(self, form, field): - raise NotImplementedError - - def _render_attrs(self, form, field): - name = form.context[field]['name'] - values = form.context[field]['value'] - if not isinstance(values, (tuple, list)): - values = (values,) - attrs = dict(self.attrs) - if self.setdomid: - attrs['id'] = form.context[field]['id'] - if self.settabindex: - attrs['tabindex'] = form.req.next_tabindex() - return name, values, attrs - -class Input(FieldWidget): - type = None - - def render(self, form, field): - self.add_media(form) - name, values, attrs = self._render_attrs(form, field) - inputs = [tags.input(name=name, value=value, type=self.type, **attrs) - for value in values] - return u'\n'.join(inputs) - -class TextInput(Input): - type = 'text' - -class PasswordInput(Input): - type = 'password' - - def render(self, form, field): - self.add_media(form) - name, values, attrs = self._render_attrs(form, field) - assert len(values) == 1 - inputs = [tags.input(name=name, value=values[0], type=self.type, **attrs), - '
', - tags.input(name=name+'-confirm', type=self.type, **attrs), - ' ', tags.span(form.req._('confirm password'), - **{'class': 'emphasis'})] - return u'\n'.join(inputs) - -class FileInput(Input): - type = 'file' - - def _render_attrs(self, form, field): - # ignore value which makes no sense here (XXX even on form validation error?) - name, values, attrs = super(FileInput, self)._render_attrs(form, field) - return name, ('',), attrs - -class HiddenInput(Input): - type = 'hidden' - setdomid = False # by default, don't set id attribute on hidden input - settabindex = False - -class ButtonInput(Input): - type = 'button' - -class TextArea(FieldWidget): - def render(self, form, field): - name, values, attrs = self._render_attrs(form, field) - attrs.setdefault('onkeypress', 'autogrow(this)') - if not values: - value = u'' - elif len(values) == 1: - value = values[0] - else: - raise ValueError('a textarea is not supposed to be multivalued') - return tags.textarea(value, name=name, **attrs) - - -class FCKEditor(TextArea): - def __init__(self, *args, **kwargs): - super(FCKEditor, self).__init__(*args, **kwargs) - self.attrs['cubicweb:type'] = 'wysiwyg' - - def render(self, form, field): - form.req.fckeditor_config() - return super(FCKEditor, self).render(form, field) - - -class Select(FieldWidget): - def __init__(self, attrs=None, multiple=False): - super(Select, self).__init__(attrs) - self.multiple = multiple - - def render(self, form, field): - name, curvalues, attrs = self._render_attrs(form, field) - options = [] - for label, value in field.vocabulary(form): - if value in curvalues: - options.append(tags.option(label, value=value, selected='selected')) - else: - options.append(tags.option(label, value=value)) - return tags.select(name=name, multiple=self.multiple, - options=options, **attrs) - - -class CheckBox(Input): - type = 'checkbox' - - def render(self, form, field): - name, curvalues, attrs = self._render_attrs(form, field) - options = [] - for label, value in field.vocabulary(form): - if value in curvalues: - tag = tags.input(name=name, value=value, type=self.type, - checked='checked', **attrs) - else: - tag = tags.input(name=name, value=value, type=self.type, - **attrs) - options.append(tag + label) - return '
\n'.join(options) - - -class Radio(Input): - type = 'radio' - setdomid = False - - def render(self, form, field): - name, curvalues, attrs = self._render_attrs(form, field) - options = [] - for label, value in field.vocabulary(form): - if value in curvalues: - options.append(tags.input(name=name, type=self.type, value=value, checked='checked', **attrs)) - else: - options.append(tags.option(name=name, type=self.type, value=value, **attrs)) - options[-1] += label + '
' - return '\n'.join(options) - - -class DateTimePicker(TextInput): - monthnames = ('january', 'february', 'march', 'april', - 'may', 'june', 'july', 'august', - 'september', 'october', 'november', 'december') - daynames = ('monday', 'tuesday', 'wednesday', 'thursday', - 'friday', 'saturday', 'sunday') - - needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js') - needs_css = ('cubicweb.calendar_popup.css',) - - @classmethod - def add_localized_infos(cls, req): - """inserts JS variables defining localized months and days""" - # import here to avoid dependancy from cubicweb-common to simplejson - _ = req._ - monthnames = [_(mname) for mname in cls.monthnames] - daynames = [_(dname) for dname in cls.daynames] - req.html_headers.define_var('MONTHNAMES', monthnames) - req.html_headers.define_var('DAYNAMES', daynames) - - def render(self, form, field): - txtwidget = super(DateTimePicker, self).render(form, field) - self.add_localized_infos(form.req) - cal_button = self._render_calendar_popup(form, field) - return txtwidget + cal_button - - def _render_calendar_popup(self, form, field): - req = form.req - value = form.context[field]['rawvalue'] - inputid = form.context[field]['id'] - helperid = '%shelper' % inputid - if not value: - value = date.today() - year, month = value.year, value.month - onclick = "toggleCalendar('%s', '%s', %s, %s);" % ( - helperid, inputid, year, month) - return (u""" -""" - % (helperid, inputid, year, month, - req.external_resource('CALENDAR_ICON'), - req._('calendar'), helperid) ) - - -class AjaxWidget(FieldWidget): - def __init__(self, wdgtype, inputid=None, **kwargs): - super(AjaxWidget, self).__init__(**kwargs) - self.attrs.setdefault('class', 'widget') - self.attrs.setdefault('cubicweb:loadtype', 'auto') - self.attrs['cubicweb:wdgtype'] = wdgtype - if inputid is not None: - self.attrs['cubicweb:inputid'] = inputid - - def render(self, form, field): - self.add_media(form) - name, values, attrs = self._render_attrs(form, field) - return tags.div(**attrs) - - -# fields ############ - -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 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 __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 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 - -# forms ############ class metafieldsform(type): def __new__(mcs, name, bases, classdict): allfields = [] @@ -1214,67 +685,3 @@ for button in form.form_buttons(): w(u'%s\n' % button) w(u'') - - -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, - } 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/ diff -r c07f3accf04a -r f2a85f52b9e5 web/formwidgets.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/formwidgets.py Wed Mar 11 23:25:29 2009 +0100 @@ -0,0 +1,220 @@ +"""widget 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" + +from cubicweb.common import tags + +class FieldWidget(object): + needs_js = () + needs_css = () + setdomid = True + settabindex = True + + def __init__(self, attrs=None, setdomid=None, settabindex=None): + self.attrs = attrs or {} + if setdomid is not None: + # override class's default value + self.setdomid = setdomid + if settabindex is not None: + # override class's default value + self.settabindex = settabindex + + def add_media(self, form): + """adds media (CSS & JS) required by this widget""" + req = form.req + if self.needs_js: + req.add_js(self.needs_js) + if self.needs_css: + req.add_css(self.needs_css) + + def render(self, form, field): + raise NotImplementedError + + def _render_attrs(self, form, field): + name = form.context[field]['name'] + values = form.context[field]['value'] + if not isinstance(values, (tuple, list)): + values = (values,) + attrs = dict(self.attrs) + if self.setdomid: + attrs['id'] = form.context[field]['id'] + if self.settabindex: + attrs['tabindex'] = form.req.next_tabindex() + return name, values, attrs + +class Input(FieldWidget): + type = None + + def render(self, form, field): + self.add_media(form) + name, values, attrs = self._render_attrs(form, field) + inputs = [tags.input(name=name, value=value, type=self.type, **attrs) + for value in values] + return u'\n'.join(inputs) + +class TextInput(Input): + type = 'text' + +class PasswordInput(Input): + type = 'password' + + def render(self, form, field): + self.add_media(form) + name, values, attrs = self._render_attrs(form, field) + assert len(values) == 1 + inputs = [tags.input(name=name, value=values[0], type=self.type, **attrs), + '
', + tags.input(name=name+'-confirm', type=self.type, **attrs), + ' ', tags.span(form.req._('confirm password'), + **{'class': 'emphasis'})] + return u'\n'.join(inputs) + +class FileInput(Input): + type = 'file' + + def _render_attrs(self, form, field): + # ignore value which makes no sense here (XXX even on form validation error?) + name, values, attrs = super(FileInput, self)._render_attrs(form, field) + return name, ('',), attrs + +class HiddenInput(Input): + type = 'hidden' + setdomid = False # by default, don't set id attribute on hidden input + settabindex = False + +class ButtonInput(Input): + type = 'button' + +class TextArea(FieldWidget): + def render(self, form, field): + name, values, attrs = self._render_attrs(form, field) + attrs.setdefault('onkeypress', 'autogrow(this)') + if not values: + value = u'' + elif len(values) == 1: + value = values[0] + else: + raise ValueError('a textarea is not supposed to be multivalued') + return tags.textarea(value, name=name, **attrs) + + +class FCKEditor(TextArea): + def __init__(self, *args, **kwargs): + super(FCKEditor, self).__init__(*args, **kwargs) + self.attrs['cubicweb:type'] = 'wysiwyg' + + def render(self, form, field): + form.req.fckeditor_config() + return super(FCKEditor, self).render(form, field) + + +class Select(FieldWidget): + def __init__(self, attrs=None, multiple=False): + super(Select, self).__init__(attrs) + self.multiple = multiple + + def render(self, form, field): + name, curvalues, attrs = self._render_attrs(form, field) + options = [] + for label, value in field.vocabulary(form): + if value in curvalues: + options.append(tags.option(label, value=value, selected='selected')) + else: + options.append(tags.option(label, value=value)) + return tags.select(name=name, multiple=self.multiple, + options=options, **attrs) + + +class CheckBox(Input): + type = 'checkbox' + + def render(self, form, field): + name, curvalues, attrs = self._render_attrs(form, field) + options = [] + for label, value in field.vocabulary(form): + if value in curvalues: + tag = tags.input(name=name, value=value, type=self.type, + checked='checked', **attrs) + else: + tag = tags.input(name=name, value=value, type=self.type, + **attrs) + options.append(tag + label) + return '
\n'.join(options) + + +class Radio(Input): + type = 'radio' + setdomid = False + + def render(self, form, field): + name, curvalues, attrs = self._render_attrs(form, field) + options = [] + for label, value in field.vocabulary(form): + if value in curvalues: + options.append(tags.input(name=name, type=self.type, value=value, checked='checked', **attrs)) + else: + options.append(tags.option(name=name, type=self.type, value=value, **attrs)) + options[-1] += label + '
' + return '\n'.join(options) + + +class DateTimePicker(TextInput): + monthnames = ('january', 'february', 'march', 'april', + 'may', 'june', 'july', 'august', + 'september', 'october', 'november', 'december') + daynames = ('monday', 'tuesday', 'wednesday', 'thursday', + 'friday', 'saturday', 'sunday') + + needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js') + needs_css = ('cubicweb.calendar_popup.css',) + + @classmethod + def add_localized_infos(cls, req): + """inserts JS variables defining localized months and days""" + # import here to avoid dependancy from cubicweb-common to simplejson + _ = req._ + monthnames = [_(mname) for mname in cls.monthnames] + daynames = [_(dname) for dname in cls.daynames] + req.html_headers.define_var('MONTHNAMES', monthnames) + req.html_headers.define_var('DAYNAMES', daynames) + + def render(self, form, field): + txtwidget = super(DateTimePicker, self).render(form, field) + self.add_localized_infos(form.req) + cal_button = self._render_calendar_popup(form, field) + return txtwidget + cal_button + + def _render_calendar_popup(self, form, field): + req = form.req + value = form.context[field]['rawvalue'] + inputid = form.context[field]['id'] + helperid = '%shelper' % inputid + if not value: + value = date.today() + year, month = value.year, value.month + onclick = "toggleCalendar('%s', '%s', %s, %s);" % ( + helperid, inputid, year, month) + return (u""" +""" + % (helperid, inputid, year, month, + req.external_resource('CALENDAR_ICON'), + req._('calendar'), helperid) ) + + +class AjaxWidget(FieldWidget): + def __init__(self, wdgtype, inputid=None, **kwargs): + super(AjaxWidget, self).__init__(**kwargs) + self.attrs.setdefault('class', 'widget') + self.attrs.setdefault('cubicweb:loadtype', 'auto') + self.attrs['cubicweb:wdgtype'] = wdgtype + if inputid is not None: + self.attrs['cubicweb:inputid'] = inputid + + def render(self, form, field): + self.add_media(form) + name, values, attrs = self._render_attrs(form, field) + return tags.div(**attrs)