18 from cubicweb import typed_eid |
18 from cubicweb import typed_eid |
19 from cubicweb.utils import ustrftime |
19 from cubicweb.utils import ustrftime |
20 from cubicweb.selectors import match_form_params |
20 from cubicweb.selectors import match_form_params |
21 from cubicweb.view import NOINDEX, NOFOLLOW, View, EntityView, AnyRsetView |
21 from cubicweb.view import NOINDEX, NOFOLLOW, View, EntityView, AnyRsetView |
22 from cubicweb.common.registerers import accepts_registerer |
22 from cubicweb.common.registerers import accepts_registerer |
|
23 from cubicweb.common.uilib import toggle_action |
23 from cubicweb.web import stdmsgs |
24 from cubicweb.web import stdmsgs |
24 from cubicweb.web.httpcache import NoHTTPCacheManager |
25 from cubicweb.web.httpcache import NoHTTPCacheManager |
25 from cubicweb.web.controller import NAV_FORM_PARAMETERS, redirect_params |
26 from cubicweb.web.controller import NAV_FORM_PARAMETERS, redirect_params |
26 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param |
27 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param |
27 |
28 |
266 |
267 |
267 def render(self, form, field): |
268 def render(self, form, field): |
268 raise NotImplementedError |
269 raise NotImplementedError |
269 |
270 |
270 def _render_attrs(self, form, field): |
271 def _render_attrs(self, form, field): |
271 # name = form.form_field_name(field) |
|
272 # values = form.form_field_value(field) |
|
273 name = form.context[field]['name'] |
272 name = form.context[field]['name'] |
274 values = form.context[field]['value'] |
273 values = form.context[field]['value'] |
275 if not isinstance(values, (tuple, list)): |
274 if not isinstance(values, (tuple, list)): |
276 values = (values,) |
275 values = (values,) |
|
276 attrs = dict(self.attrs) |
|
277 attrs['id'] = form.context[field]['id'] |
277 return name, values, dict(self.attrs) |
278 return name, values, dict(self.attrs) |
278 |
279 |
279 class Input(FieldWidget): |
280 class Input(FieldWidget): |
280 type = None |
281 type = None |
281 |
282 |
289 class TextInput(Input): |
290 class TextInput(Input): |
290 type = 'text' |
291 type = 'text' |
291 |
292 |
292 class PasswordInput(Input): |
293 class PasswordInput(Input): |
293 type = 'password' |
294 type = 'password' |
|
295 # XXX password validation |
294 |
296 |
295 class FileInput(Input): |
297 class FileInput(Input): |
296 type = 'file' |
298 type = 'file' |
297 |
299 |
|
300 def _render_attrs(self, form, field): |
|
301 # ignore value which makes no sense here (XXX even on form validation error?) |
|
302 name, values, attrs = super(FileInput, self)._render_attrs(form, field) |
|
303 return name, ('',), attrs |
|
304 |
298 class HiddenInput(Input): |
305 class HiddenInput(Input): |
299 type = 'hidden' |
306 type = 'hidden' |
300 |
307 |
301 class Button(Input): |
308 class ButtonInput(Input): |
302 type = 'button' |
309 type = 'button' |
303 |
310 |
304 class TextArea(FieldWidget): |
311 class TextArea(FieldWidget): |
305 def render(self, form, field): |
312 def render(self, form, field): |
306 name, values, attrs = self._render_attrs(form, field) |
313 name, values, attrs = self._render_attrs(form, field) |
345 return tags.select(name=name, options=options) |
353 return tags.select(name=name, options=options) |
346 return tags.select(name=name, multiple=self.multiple, |
354 return tags.select(name=name, multiple=self.multiple, |
347 options=options, **attrs) |
355 options=options, **attrs) |
348 |
356 |
349 |
357 |
350 class CheckBox(FieldWidget): |
358 class CheckBox(Input): |
351 |
359 type = 'checkbox' |
|
360 |
352 def _render_attrs(self, form, field): |
361 def _render_attrs(self, form, field): |
353 name, value, attrs = super(CheckBox, self)._render_attrs(form, field) |
362 name, values, attrs = super(CheckBox, self)._render_attrs(form, field) |
354 if value: |
363 if values and values[0]: |
355 attrs['checked'] = u'checked' |
364 attrs['checked'] = u'checked' |
356 return name, None, attrs |
365 return name, values, attrs |
357 |
366 |
358 |
367 |
359 class Radio(FieldWidget): |
368 class Radio(FieldWidget): |
360 pass |
369 pass |
361 |
370 |
362 |
371 |
363 class DateTimePicker(TextInput): |
372 class DateTimePicker(TextInput): |
364 monthnames = ("january", "february", "march", "april", |
373 monthnames = ('january', 'february', 'march', 'april', |
365 "may", "june", "july", "august", |
374 'may', 'june', 'july', 'august', |
366 "september", "october", "november", "december") |
375 'september', 'october', 'november', 'december') |
367 |
376 daynames = ('monday', 'tuesday', 'wednesday', 'thursday', |
368 daynames = ("monday", "tuesday", "wednesday", "thursday", |
377 'friday', 'saturday', 'sunday') |
369 "friday", "saturday", "sunday") |
|
370 |
378 |
371 needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js') |
379 needs_js = ('cubicweb.ajax.js', 'cubicweb.calendar.js') |
372 needs_css = ('cubicweb.calendar_popup.css',) |
380 needs_css = ('cubicweb.calendar_popup.css',) |
373 |
381 |
374 @classmethod |
382 @classmethod |
461 def format_single_value(self, req, value): |
469 def format_single_value(self, req, value): |
462 if value is None: |
470 if value is None: |
463 return u'' |
471 return u'' |
464 return unicode(value) |
472 return unicode(value) |
465 |
473 |
466 def get_widget(self, req): |
474 def get_widget(self, form): |
467 return self.widget |
475 return self.widget |
468 |
476 |
469 def render(self, form): |
477 def example_format(self, req): |
470 return self.get_widget(form.req).render(form, self) |
478 return u'' |
|
479 |
|
480 def render(self, form, renderer): |
|
481 return self.get_widget(form).render(form, self) |
471 |
482 |
472 |
483 |
473 def vocabulary(self, form): |
484 def vocabulary(self, form): |
474 return self.choices |
485 return self.choices |
|
486 |
475 |
487 |
476 class StringField(Field): |
488 class StringField(Field): |
477 def __init__(self, max_length=None, **kwargs): |
489 def __init__(self, max_length=None, **kwargs): |
478 super(StringField, self).__init__(**kwargs) |
490 super(StringField, self).__init__(**kwargs) |
479 self.max_length = max_length |
491 self.max_length = max_length |
480 |
492 |
|
493 |
481 class TextField(Field): |
494 class TextField(Field): |
|
495 widget = TextArea |
482 def __init__(self, rows=10, cols=80, **kwargs): |
496 def __init__(self, rows=10, cols=80, **kwargs): |
483 widget = TextArea(dict(rows=rows, cols=cols)) |
497 super(TextField, self).__init__(**kwargs) |
484 super(TextField, self).__init__(widget=widget, **kwargs) |
|
485 self.rows = rows |
498 self.rows = rows |
486 self.cols = cols |
499 self.cols = cols |
|
500 |
487 |
501 |
488 class RichTextField(TextField): |
502 class RichTextField(TextField): |
489 widget = None |
503 widget = None |
490 def __init__(self, format_field=None, **kwargs): |
504 def __init__(self, format_field=None, **kwargs): |
491 super(RichTextField, self).__init__(**kwargs) |
505 super(RichTextField, self).__init__(**kwargs) |
492 self.format_field = format_field |
506 self.format_field = format_field |
493 |
507 |
494 def get_widget(self, req): |
508 def get_widget(self, form): |
495 if self.widget is None: |
509 if self.widget is None: |
496 if self.use_fckeditor(req): |
510 if self.use_fckeditor(form): |
497 return FCKEditor() |
511 return FCKEditor() |
498 return TextArea() |
512 return TextArea() |
499 return self.widget |
513 return self.widget |
500 |
514 |
501 def get_format_field(self, form): |
515 def get_format_field(self, form): |
502 if not self.format_field: |
516 if self.format_field: |
503 # if fckeditor is used and format field isn't explicitly |
517 return self.format_field |
504 # deactivated, we want an hidden field for the format |
518 # we have to cache generated field since it's use as key in the |
|
519 # context dictionnary |
|
520 try: |
|
521 return form.req.data[self] |
|
522 except KeyError: |
505 if self.use_fckeditor(form): |
523 if self.use_fckeditor(form): |
506 widget = HiddenInput |
524 # if fckeditor is used and format field isn't explicitly |
507 # else we want a format selector |
525 # deactivated, we want an hidden field for the format |
|
526 widget = HiddenInput() |
508 else: |
527 else: |
|
528 # else we want a format selector |
|
529 # XXX compute vocabulary |
509 widget = Select |
530 widget = Select |
510 return StringField(name=self.name + '_format', widget=widget) |
531 field = StringField(name=self.name + '_format', widget=widget) |
511 else: |
532 form.req.data[self] = field |
512 return self.format_field |
533 return field |
513 |
534 |
514 def actual_fields(self, form): |
535 def actual_fields(self, form): |
515 yield self |
536 yield self |
516 format_field = self.get_format_field(form) |
537 format_field = self.get_format_field(form) |
517 if format_field: |
538 if format_field: |
523 """ |
544 """ |
524 if form.config.fckeditor_installed() and form.req.property_value('ui.fckeditor'): |
545 if form.config.fckeditor_installed() and form.req.property_value('ui.fckeditor'): |
525 return form.form_format_field_value(self) == 'text/html' |
546 return form.form_format_field_value(self) == 'text/html' |
526 return False |
547 return False |
527 |
548 |
528 def render(self, form): |
549 def render(self, form, renderer): |
529 format_field = self.get_format_field(form) |
550 format_field = self.get_format_field(form) |
530 if format_field: |
551 if format_field: |
531 result = format_field.render(form) |
552 result = format_field.render(form, renderer) |
532 else: |
553 else: |
533 result = u'' |
554 result = u'' |
534 return result + self.get_widget(form.req).render(form, self) |
555 return result + self.get_widget(form).render(form, self) |
535 |
556 |
|
557 |
|
558 class FileField(StringField): |
|
559 widget = FileInput |
|
560 needs_multipart = True |
|
561 |
|
562 def __init__(self, format_field=None, encoding_field=None, **kwargs): |
|
563 super(FileField, self).__init__(**kwargs) |
|
564 self.format_field = format_field |
|
565 self.encoding_field = encoding_field |
|
566 |
|
567 def actual_fields(self, form): |
|
568 yield self |
|
569 if self.format_field: |
|
570 yield self.format_field |
|
571 if self.encoding_field: |
|
572 yield self.encoding_field |
|
573 |
|
574 def render(self, form, renderer): |
|
575 wdgs = [self.get_widget(form).render(form, self)] |
|
576 if self.format_field or self.encoding_field: |
|
577 divid = '%s-advanced' % form.context[self]['name'] |
|
578 wdgs.append(u'<a href="%s" title="%s"><img src="%s" alt="%s"/></a>' % |
|
579 (html_escape(toggle_action(divid)), |
|
580 form.req._('show advanced fields'), |
|
581 html_escape(form.req.build_url('data/puce_down.png')), |
|
582 form.req._('show advanced fields'))) |
|
583 wdgs.append(u'<div id="%s" class="hidden">' % divid) |
|
584 if self.format_field: |
|
585 wdgs.append(self.render_subfield(form, self.format_field, renderer)) |
|
586 if self.encoding_field: |
|
587 wdgs.append(self.render_subfield(form, self.encoding_field, renderer)) |
|
588 wdgs.append(u'</div>') |
|
589 if not self.required and form.context[self]['value']: |
|
590 # trick to be able to delete an uploaded file |
|
591 wdgs.append(u'<br/>') |
|
592 wdgs.append(tags.input(name=u'%s__detach' % form.context[self]['name'], |
|
593 type=u'checkbox')) |
|
594 wdgs.append(form.req._('detach attached file')) |
|
595 return u'\n'.join(wdgs) |
|
596 |
|
597 def render_subfield(self, form, field, renderer): |
|
598 return (renderer.render_label(form, field) |
|
599 + field.render(form, renderer) |
|
600 + renderer.render_help(form, field) |
|
601 + u'<br/>') |
|
602 |
536 |
603 |
537 class IntField(Field): |
604 class IntField(Field): |
538 def __init__(self, min=None, max=None, **kwargs): |
605 def __init__(self, min=None, max=None, **kwargs): |
539 super(IntField, self).__init__(**kwargs) |
606 super(IntField, self).__init__(**kwargs) |
540 self.min = min |
607 self.min = min |
541 self.max = max |
608 self.max = max |
542 |
609 |
|
610 |
543 class BooleanField(Field): |
611 class BooleanField(Field): |
544 widget = Radio |
612 widget = Radio |
|
613 |
545 |
614 |
546 class FloatField(IntField): |
615 class FloatField(IntField): |
547 def format_single_value(self, req, value): |
616 def format_single_value(self, req, value): |
548 formatstr = entity.req.property_value('ui.float-format') |
617 formatstr = entity.req.property_value('ui.float-format') |
549 if value is None: |
618 if value is None: |
550 return u'' |
619 return u'' |
551 return formatstr % float(value) |
620 return formatstr % float(value) |
552 |
621 |
|
622 def render_example(self, req): |
|
623 return self.format_value(req, 1.234) |
|
624 |
|
625 |
553 class DateField(StringField): |
626 class DateField(StringField): |
554 format_prop = 'ui.date-format' |
627 format_prop = 'ui.date-format' |
555 widget = DateTimePicker |
628 widget = DateTimePicker |
556 |
629 |
557 def format_single_value(self, req, value): |
630 def format_single_value(self, req, value): |
558 return value and ustrftime(value, req.property_value(self.format_prop)) or u'' |
631 return value and ustrftime(value, req.property_value(self.format_prop)) or u'' |
559 |
632 |
|
633 def render_example(self, req): |
|
634 return self.format_value(req, now()) |
|
635 |
|
636 |
560 class DateTimeField(DateField): |
637 class DateTimeField(DateField): |
561 format_prop = 'ui.datetime-format' |
638 format_prop = 'ui.datetime-format' |
562 |
639 |
|
640 |
563 class TimeField(DateField): |
641 class TimeField(DateField): |
564 format_prop = 'ui.datetime-format' |
642 format_prop = 'ui.datetime-format' |
565 |
643 |
566 class FileField(StringField): |
|
567 needs_multipart = True |
|
568 |
644 |
569 class HiddenInitialValueField(Field): |
645 class HiddenInitialValueField(Field): |
570 def __init__(self, visible_field, name): |
646 def __init__(self, visible_field, name): |
571 super(HiddenInitialValueField, self).__init__(name=name, |
647 super(HiddenInitialValueField, self).__init__(name=name, |
572 widget=HiddenInput, |
648 widget=HiddenInput, |
901 continue |
982 continue |
902 done.add(entity.eid) |
983 done.add(entity.eid) |
903 res.append((entity.view('combobox'), entity.eid)) |
984 res.append((entity.view('combobox'), entity.eid)) |
904 return res |
985 return res |
905 |
986 |
906 |
|
907 |
987 |
908 class MultipleFieldsForm(FieldsForm): |
988 class MultipleFieldsForm(FieldsForm): |
909 def __init__(self, *args, **kwargs): |
989 def __init__(self, *args, **kwargs): |
910 super(MultipleFieldsForm, self).__init__(*args, **kwargs) |
990 super(MultipleFieldsForm, self).__init__(*args, **kwargs) |
911 self.forms = [] |
991 self.forms = [] |
912 |
992 |
913 def form_add_subform(self, subform): |
993 def form_add_subform(self, subform): |
914 self.forms.append(subform) |
994 self.forms.append(subform) |
915 |
995 |
|
996 |
916 # form renderers ############ |
997 # form renderers ############ |
917 class FormRenderer(object): |
998 class FormRenderer(object): |
918 |
999 |
919 def render(self, form, values): |
1000 # renderer interface ###################################################### |
|
1001 |
|
1002 def render(self, form, values, display_help=True): |
920 data = [] |
1003 data = [] |
921 w = data.append |
1004 w = data.append |
922 # XXX form_needs_multipart |
|
923 w(self.open_form(form)) |
1005 w(self.open_form(form)) |
924 w(u'<div id="progress">%s</div>' % form.req._('validating...')) |
1006 w(u'<div id="progress">%s</div>' % form.req._('validating...')) |
925 w(u'<fieldset>') |
1007 w(u'<fieldset>') |
926 w(tags.input(type='hidden', name='__form_id', value=form.domid)) |
1008 w(tags.input(type='hidden', name='__form_id', value=form.domid)) |
927 if form.redirect_path: |
1009 if form.redirect_path: |
928 w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path)) |
1010 w(tags.input(type='hidden', name='__redirectpath', value=form.redirect_path)) |
929 self.render_fields(w, form, values) |
1011 self.render_fields(w, form, values, display_help) |
930 self.render_buttons(w, form) |
1012 self.render_buttons(w, form) |
931 w(u'</fieldset>') |
1013 w(u'</fieldset>') |
932 w(u'</form>') |
1014 w(u'</form>') |
933 return '\n'.join(data) |
1015 return '\n'.join(data) |
934 |
1016 |
|
1017 def render_label(self, form, field): |
|
1018 label = form.req._(field.label) |
|
1019 attrs = {'for': form.context[field]['id']} |
|
1020 if field.required: |
|
1021 attrs['class'] = 'required' |
|
1022 return tags.label(label, **attrs) |
|
1023 |
|
1024 def render_help(self, form, field): |
|
1025 help = [ u'<br/>' ] |
|
1026 descr = field.help |
|
1027 if descr: |
|
1028 help.append('<span class="helper">%s</span>' % req._(descr)) |
|
1029 example = field.example_format(form.req) |
|
1030 if example: |
|
1031 help.append('<span class="helper">(%s: %s)</span>' |
|
1032 % (req._('sample format'), example)) |
|
1033 return u' '.join(help) |
|
1034 |
|
1035 # specific methods (mostly to ease overriding) ############################# |
|
1036 |
935 def open_form(self, form): |
1037 def open_form(self, form): |
936 if form.form_needs_multipart: |
1038 if form.form_needs_multipart: |
937 enctype = 'multipart/form-data' |
1039 enctype = 'multipart/form-data' |
938 else: |
1040 else: |
939 enctype = 'application/x-www-form-urlencoded' |
1041 enctype = 'application/x-www-form-urlencoded' |
947 tag += ' class="%s"' % html_escape(form.cssclass) |
1049 tag += ' class="%s"' % html_escape(form.cssclass) |
948 if form.cwtarget: |
1050 if form.cwtarget: |
949 tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget) |
1051 tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget) |
950 return tag + '>' |
1052 return tag + '>' |
951 |
1053 |
952 def render_fields(self, w, form, values): |
1054 def render_fields(self, w, form, values, display_help=True): |
953 form.form_build_context(values) |
1055 form.form_build_context(values) |
954 fields = form.fields[:] |
1056 fields = form.fields[:] |
955 for field in form.fields: |
1057 for field in form.fields: |
956 if not field.is_visible(): |
1058 if not field.is_visible(): |
957 w(field.render(form)) |
1059 w(field.render(form, self)) |
958 fields.remove(field) |
1060 fields.remove(field) |
959 if fields: |
1061 if fields: |
960 w(u'<table>') |
1062 w(u'<table>') |
961 for field in fields: |
1063 for field in fields: |
962 w(u'<tr>') |
1064 w(u'<tr>') |
963 w('<th>%s</th>' % self.render_label(form, field)) |
1065 w('<th>%s</th>' % self.render_label(form, field)) |
964 w(u'<td style="width:100%;">') |
1066 w(u'<td style="width:100%;">') |
965 w(field.render(form)) |
1067 w(field.render(form, self)) |
|
1068 if display_help == True: |
|
1069 w(self.render_help(form, field)) |
966 w(u'</td></tr>') |
1070 w(u'</td></tr>') |
967 w(u'</table>') |
1071 w(u'</table>') |
968 for childform in getattr(form, 'forms', []): |
1072 for childform in getattr(form, 'forms', []): |
969 self.render_fields(w, childform, values) |
1073 self.render_fields(w, childform, values) |
970 |
|
971 #def render_field(self, w, form, field): |
|
972 |
1074 |
973 def render_buttons(self, w, form): |
1075 def render_buttons(self, w, form): |
974 w(u'<table class="formButtonBar">\n<tr>\n') |
1076 w(u'<table class="formButtonBar">\n<tr>\n') |
975 for button in form.form_buttons(): |
1077 for button in form.form_buttons(): |
976 w(u'<td>%s</td>\n' % button) |
1078 w(u'<td>%s</td>\n' % button) |
977 w(u'</tr></table>') |
1079 w(u'</tr></table>') |
978 |
|
979 def render_label(self, form, field): |
|
980 label = form.req._(field.label) |
|
981 attrs = {'for': form.context[field]['id']} |
|
982 if field.required: |
|
983 attrs['class'] = 'required' |
|
984 return tags.label(label, **attrs) |
|
985 |
1080 |
986 |
1081 |
987 def stringfield_from_constraints(constraints, **kwargs): |
1082 def stringfield_from_constraints(constraints, **kwargs): |
988 field = None |
1083 field = None |
989 for cstr in constraints: |
1084 for cstr in constraints: |