# HG changeset patch # User Aurelien Campeas # Date 1272982473 -7200 # Node ID 37a2455639b986464b5756e06d8c5fe06fab1452 # Parent a37127c8bf23c5d63d32ce2ecf2480e7ccc778da [doc/book] move examples to a separate chapter, fix autoform module docstring diff -r a37127c8bf23 -r 37a2455639b9 doc/book/en/devweb/edition/examples.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/devweb/edition/examples.rst Tue May 04 16:14:33 2010 +0200 @@ -0,0 +1,189 @@ +Examples +-------- + +Example of ad-hoc fields form +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We want to define a form doing something else than editing an entity. The idea is +to propose a form to send an email to entities in a resultset which implements +:class:`IEmailable`. Let's take a simplified version of what you'll find in +:mod:`cubicweb.web.views.massmailing`. + +Here is the source code: + +.. sourcecode:: python + + def sender_value(form): + return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email()) + + def recipient_choices(form, field): + return [(e.get_email(), e.eid) + for e in form.cw_rset.entities() + if e.get_email()] + + def recipient_value(form): + return [e.eid for e in form.cw_rset.entities() + if e.get_email()] + + class MassMailingForm(forms.FieldsForm): + __regid__ = 'massmailing' + + needs_js = ('cubicweb.widgets.js',) + domid = 'sendmail' + action = 'sendmail' + + sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}), + label=_('From:'), + value=sender_value) + + recipient = ff.StringField(widget=CheckBox(), + label=_('Recipients:'), + choices=recipient_choices, + value=recipients_value) + + subject = ff.StringField(label=_('Subject:'), max_length=256) + + mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField', + inputid='mailbody')) + + form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()", + _('send email'), 'SEND_EMAIL_ICON'), + ImgButton('cancelbutton', "javascript: history.back()", + stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')] + +Let's detail what's going on up there. Our form will hold four fields: + +* a sender field, which is disabled and will simply contains the user's name and + email + +* a recipients field, which will be displayed as a list of users in the context + result set with checkboxes so user can still choose who will receive his mailing + by checking or not the checkboxes. By default all of them will be checked since + field's value return a list containing same eids as those returned by the + vocabulary function. + +* a subject field, limited to 256 characters (hence we know a + :class:`~cubicweb.web.formwidgets.TextInput` will be used, as explained in + :class:`~cubicweb.web.formfields.StringField`) + +* a mailbody field. This field use an ajax widget, defined in `cubicweb.widgets.js`, + and whose definition won't be shown here. Notice though that we tell this form + need this javascript file by using `needs_js` + +Last but not least, we add two buttons control: one to post the form using +javascript (`$('#sendmail')` being the jQuery call to get the element with DOM id +set to 'sendmail', which is our form DOM id as specified by its `domid` +attribute), another to cancel the form which will go back to the previous page +using another javascript call. Also we specify an image to use as button icon as a +resource identifier (see :ref:`external_resources`) given as last argument to +:class:`cubicweb.web.formwidgets.ImgButton`. + +To see this form, we still have to wrap it in a view. This is pretty simple: + +.. sourcecode:: python + + class MassMailingFormView(form.FormViewMixIn, EntityView): + __regid__ = 'massmailing' + __select__ = implements(IEmailable) & authenticated_user() + + def call(self): + form = self._cw.vreg['forms'].select('massmailing', self._cw, + rset=self.cw_rset) + self.w(form.render()) + +As you see, we simply define a view with proper selector so it only apply to a +result set containing :class:`IEmailable` entities, and so that only users in the +managers or users group can use it. Then in the `call()` method for this view we +simply select the above form and write what its `.render()` method returns. + +When this form is submitted, a controller with id 'sendmail' will be called (as +specified using `action`). This controller will be responsible to actually send +the mail to specified recipients. + +Here is what it looks like: + +.. sourcecode:: python + + class SendMailController(Controller): + __regid__ = 'sendmail' + __select__ = (authenticated_user() & + match_form_params('recipient', 'mailbody', 'subject')) + + def publish(self, rset=None): + body = self._cw.form['mailbody'] + subject = self._cw.form['subject'] + eids = self._cw.form['recipient'] + # eids may be a string if only one recipient was specified + if isinstance(eids, basestring): + rset = self._cw.execute('Any X WHERE X eid %(x)s', {'x': eids}) + else: + rset = self._cw.execute('Any X WHERE X eid in (%s)' % (','.join(eids))) + recipients = list(rset.entities()) + msg = format_mail({'email' : self._cw.user.get_email(), + 'name' : self._cw.user.dc_title()}, + recipients, body, subject) + if not self._cw.vreg.config.sendmails([(msg, recipients]): + msg = self._cw._('could not connect to the SMTP server') + else: + msg = self._cw._('emails successfully sent') + raise Redirect(self._cw.build_url(__message=msg)) + + +The entry point of a controller is the publish method. In that case we simply get +back post values in request's `form` attribute, get user instances according +to eids found in the 'recipient' form value, and send email after calling +:func:`format_mail` to get a proper email message. If we can't send email or +if we successfully sent email, we redirect to the index page with proper message +to inform the user. + +Also notice that our controller has a selector that deny access to it +to anonymous users (we don't want our instance to be used as a spam +relay), but also checks if the expected parameters are specified in +forms. That avoids later defensive programming (though it's not enough +to handle all possible error cases). + +To conclude our example, suppose we wish a different form layout and that existent +renderers are not satisfying (we would check that first of course :). We would then +have to define our own renderer: + +.. sourcecode:: python + + class MassMailingFormRenderer(formrenderers.FormRenderer): + __regid__ = 'massmailing' + + def _render_fields(self, fields, w, form): + w(u'') + for field in fields: + if field.name == 'mailbody': + w(u'
') + w(u'
') + w(u'') + w(u'
') + w(u'
') + w(field.render(form, self)) + w(u'
') + else: + w(u'') + w(u'%s' % + self.render_label(form, field)) + w(u'') + w(field.render(form, self)) + w(u'') + + def render_buttons(self, w, form): + pass + +We simply override the `_render_fields` and `render_buttons` method of the base form renderer +to arrange fields as we desire it: here we'll have first a two columns table with label and +value of the sender, recipients and subject field (form order respected), then form controls, +then a div containing the textarea for the email's content. + +To bind this renderer to our form, we should add to our form definition above: + +.. sourcecode:: python + + form_renderer_id = 'massmailing' + diff -r a37127c8bf23 -r 37a2455639b9 doc/book/en/devweb/edition/form.rst --- a/doc/book/en/devweb/edition/form.rst Tue May 04 15:46:36 2010 +0200 +++ b/doc/book/en/devweb/edition/form.rst Tue May 04 16:14:33 2010 +0200 @@ -182,197 +182,12 @@ course, we take care *not* to provide a version the ticket is already linked to (through ``done_in``). - -Example of ad-hoc fields form -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We want to define a form doing something else than editing an entity. The idea is -to propose a form to send an email to entities in a resultset which implements -:class:`IEmailable`. Let's take a simplified version of what you'll find in -:mod:`cubicweb.web.views.massmailing`. - -Here is the source code: - -.. sourcecode:: python - - def sender_value(form): - return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email()) - - def recipient_choices(form, field): - return [(e.get_email(), e.eid) - for e in form.cw_rset.entities() - if e.get_email()] - - def recipient_value(form): - return [e.eid for e in form.cw_rset.entities() - if e.get_email()] - - class MassMailingForm(forms.FieldsForm): - __regid__ = 'massmailing' - - needs_js = ('cubicweb.widgets.js',) - domid = 'sendmail' - action = 'sendmail' - - sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}), - label=_('From:'), - value=sender_value) - - recipient = ff.StringField(widget=CheckBox(), - label=_('Recipients:'), - choices=recipient_choices, - value=recipients_value) - - subject = ff.StringField(label=_('Subject:'), max_length=256) - - mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField', - inputid='mailbody')) - - form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()", - _('send email'), 'SEND_EMAIL_ICON'), - ImgButton('cancelbutton', "javascript: history.back()", - stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')] - -Let's detail what's going on up there. Our form will hold four fields: - -* a sender field, which is disabled and will simply contains the user's name and - email - -* a recipients field, which will be displayed as a list of users in the context - result set with checkboxes so user can still choose who will receive his mailing - by checking or not the checkboxes. By default all of them will be checked since - field's value return a list containing same eids as those returned by the - vocabulary function. - -* a subject field, limited to 256 characters (hence we know a - :class:`~cubicweb.web.formwidgets.TextInput` will be used, as explained in - :class:`~cubicweb.web.formfields.StringField`) - -* a mailbody field. This field use an ajax widget, defined in `cubicweb.widgets.js`, - and whose definition won't be shown here. Notice though that we tell this form - need this javascript file by using `needs_js` - -Last but not least, we add two buttons control: one to post the form using -javascript (`$('#sendmail')` being the jQuery call to get the element with DOM id -set to 'sendmail', which is our form DOM id as specified by its `domid` -attribute), another to cancel the form which will go back to the previous page -using another javascript call. Also we specify an image to use as button icon as a -resource identifier (see :ref:`external_resources`) given as last argument to -:class:`cubicweb.web.formwidgets.ImgButton`. - -To see this form, we still have to wrap it in a view. This is pretty simple: - -.. sourcecode:: python - - class MassMailingFormView(form.FormViewMixIn, EntityView): - __regid__ = 'massmailing' - __select__ = implements(IEmailable) & authenticated_user() - - def call(self): - form = self._cw.vreg['forms'].select('massmailing', self._cw, - rset=self.cw_rset) - self.w(form.render()) - -As you see, we simply define a view with proper selector so it only apply to a -result set containing :class:`IEmailable` entities, and so that only users in the -managers or users group can use it. Then in the `call()` method for this view we -simply select the above form and write what its `.render()` method returns. - -When this form is submitted, a controller with id 'sendmail' will be called (as -specified using `action`). This controller will be responsible to actually send -the mail to specified recipients. - -Here is what it looks like: - -.. sourcecode:: python - - class SendMailController(Controller): - __regid__ = 'sendmail' - __select__ = (authenticated_user() & - match_form_params('recipient', 'mailbody', 'subject')) - - def publish(self, rset=None): - body = self._cw.form['mailbody'] - subject = self._cw.form['subject'] - eids = self._cw.form['recipient'] - # eids may be a string if only one recipient was specified - if isinstance(eids, basestring): - rset = self._cw.execute('Any X WHERE X eid %(x)s', {'x': eids}) - else: - rset = self._cw.execute('Any X WHERE X eid in (%s)' % (','.join(eids))) - recipients = list(rset.entities()) - msg = format_mail({'email' : self._cw.user.get_email(), - 'name' : self._cw.user.dc_title()}, - recipients, body, subject) - if not self._cw.vreg.config.sendmails([(msg, recipients]): - msg = self._cw._('could not connect to the SMTP server') - else: - msg = self._cw._('emails successfully sent') - raise Redirect(self._cw.build_url(__message=msg)) - - -The entry point of a controller is the publish method. In that case we simply get -back post values in request's `form` attribute, get user instances according -to eids found in the 'recipient' form value, and send email after calling -:func:`format_mail` to get a proper email message. If we can't send email or -if we successfully sent email, we redirect to the index page with proper message -to inform the user. - -Also notice that our controller has a selector that deny access to it -to anonymous users (we don't want our instance to be used as a spam -relay), but also checks if the expected parameters are specified in -forms. That avoids later defensive programming (though it's not enough -to handle all possible error cases). - -To conclude our example, suppose we wish a different form layout and that existent -renderers are not satisfying (we would check that first of course :). We would then -have to define our own renderer: - -.. sourcecode:: python - - class MassMailingFormRenderer(formrenderers.FormRenderer): - __regid__ = 'massmailing' - - def _render_fields(self, fields, w, form): - w(u'') - for field in fields: - if field.name == 'mailbody': - w(u'
') - w(u'
') - w(u'') - w(u'
') - w(u'
') - w(field.render(form, self)) - w(u'
') - else: - w(u'') - w(u'%s' % - self.render_label(form, field)) - w(u'') - w(field.render(form, self)) - w(u'') - - def render_buttons(self, w, form): - pass - -We simply override the `_render_fields` and `render_buttons` method of the base form renderer -to arrange fields as we desire it: here we'll have first a two columns table with label and -value of the sender, recipients and subject field (form order respected), then form controls, -then a div containing the textarea for the email's content. - -To bind this renderer to our form, we should add to our form definition above: - -.. sourcecode:: python - - form_renderer_id = 'massmailing' - -API -~~~ +APIs +~~~~ .. automodule:: cubicweb.web.formfields .. automodule:: cubicweb.web.formwidgets .. automodule:: cubicweb.web.views.forms .. automodule:: cubicweb.web.views.formrenderers + + diff -r a37127c8bf23 -r 37a2455639b9 doc/book/en/devweb/edition/index.rst --- a/doc/book/en/devweb/edition/index.rst Tue May 04 15:46:36 2010 +0200 +++ b/doc/book/en/devweb/edition/index.rst Tue May 04 16:14:33 2010 +0200 @@ -11,3 +11,4 @@ form editcontroller + examples diff -r a37127c8bf23 -r 37a2455639b9 web/views/autoform.py --- a/web/views/autoform.py Tue May 04 15:46:36 2010 +0200 +++ b/web/views/autoform.py Tue May 04 16:14:33 2010 +0200 @@ -25,7 +25,7 @@ ``````````````````````````` It is possible to manage which and how an entity's attributes and relations -will be edited in the various context where the automatic entity form is used +will be edited in the various contexts where the automatic entity form is used by using proper uicfg tags. The details of the uicfg syntax can be found in the :ref:`uicfg` chapter. @@ -53,7 +53,7 @@ section may be one of: -* 'hidden', don't display (not even in an hidden input, right?) +* 'hidden', don't display (not even in a hidden input) * 'attributes', display in the attributes section @@ -104,7 +104,11 @@ autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'), {'widget': fw.TextInput}) +.. note:: + the widget argument can be either a class or an instance (the later + case being convenient to pass the Widget specific initialisation + options) Overriding permissions ^^^^^^^^^^^^^^^^^^^^^^ @@ -621,13 +625,13 @@ # The automatic entity form #################################################### class AutomaticEntityForm(forms.EntityFieldsForm): - """AutomaticEntityForm is an automagic form to edit any entity. It is - designed to be fully generated from schema but highly configurable through - :ref:`uicfg`. + """AutomaticEntityForm is an automagic form to edit any entity. It + is designed to be fully generated from schema but highly + configurable through uicfg. Of course, as for other forms, you can also customise it by specifying various standard form parameters on selection, overriding, or - adding/removing fields in a selected instances. + adding/removing fields in selected instances. """ __regid__ = 'edition'