--- a/.hgtags Wed May 05 18:08:34 2010 +0200
+++ b/.hgtags Wed May 05 18:10:33 2010 +0200
@@ -119,6 +119,8 @@
44c7bf90df71dd562e5a7be5ced3019da603d24f cubicweb-debian-version-3.7.3-1
ec23f3ebcd34a92b9898b312f44d56cca748d0d6 cubicweb-version-3.7.4
fefeda65bb83dcc2d775255fe69fdee0e793d135 cubicweb-debian-version-3.7.4-1
+c476d106705ebdd9205d97e64cafa72707acabe7 cubicweb-version-3.7.5
+2d0982252e8d780ba964f293a0e691d48070db6d cubicweb-debian-version-3.7.5-1
3c703f3245dc7696341ae1d66525554d9fa2d11d cubicweb-version-3.8.0
24cc65ab2eca05729d66cef3de6f69bb7f9dfa35 cubicweb-debian-version-3.8.0-1
1e074c6150fe00844160986852db364cc5992848 cubicweb-version-3.8.1
--- a/__init__.py Wed May 05 18:08:34 2010 +0200
+++ b/__init__.py Wed May 05 18:10:33 2010 +0200
@@ -21,6 +21,13 @@
"""
__docformat__ = "restructuredtext en"
+# ignore the pygments UserWarnings
+import warnings
+warnings.filterwarnings('ignore', category=UserWarning,
+ message='.*was already imported',
+ module='.*pygments')
+
+
import __builtin__
# '_' is available in builtins to mark internationalized string but should
# not be used to do the actual translation
--- a/__pkginfo__.py Wed May 05 18:08:34 2010 +0200
+++ b/__pkginfo__.py Wed May 05 18:10:33 2010 +0200
@@ -78,7 +78,7 @@
join('hooks', 'test', 'data'),
join('web', 'test', 'data'),
join('devtools', 'test', 'data'),
- 'skeleton']
+ 'schemas', 'skeleton']
_server_migration_dir = join('misc', 'migration')
--- a/cwconfig.py Wed May 05 18:08:34 2010 +0200
+++ b/cwconfig.py Wed May 05 18:10:33 2010 +0200
@@ -361,7 +361,7 @@
'default': False,
'help': "don't display actual email addresses but mangle them if \
this option is set to yes",
- 'group': 'email', 'level': 2,
+ 'group': 'email', 'level': 3,
}),
)
# static and class methods used to get instance independant resources ##
--- a/dbapi.py Wed May 05 18:08:34 2010 +0200
+++ b/dbapi.py Wed May 05 18:10:33 2010 +0200
@@ -674,10 +674,11 @@
self._repo.rollback(self.sessionid)
def cursor(self, req=None):
- """Return a new Cursor Object using the connection. If the database
- does not provide a direct cursor concept, the module will have to
- emulate cursors using other means to the extent needed by this
- specification.
+ """Return a new Cursor Object using the connection.
+
+ On pyro connection, you should get cursor after calling if
+ load_appobjects method if desired (which you should call if you intend
+ to use ORM abilities).
"""
if self._closed is not None:
raise ProgrammingError('Can\'t get cursor on closed connection')
--- a/debian/changelog Wed May 05 18:08:34 2010 +0200
+++ b/debian/changelog Wed May 05 18:10:33 2010 +0200
@@ -10,6 +10,12 @@
-- Sylvain Thénault <sylvain.thenault@logilab.fr> Tue, 20 Apr 2010 16:31:44 +0200
+cubicweb (3.7.5-1) unstable; urgency=low
+
+ * new upstream release on the 3.7 branch
+
+ -- Alexandre Fayolle <afayolle@debian.org> Thu, 29 Apr 2010 13:51:52 +0200
+
cubicweb (3.7.4-1) unstable; urgency=low
* new upstream release
--- a/devtools/devctl.py Wed May 05 18:08:34 2010 +0200
+++ b/devtools/devctl.py Wed May 05 18:10:33 2010 +0200
@@ -176,9 +176,10 @@
'inlined:%s.%s.%s' % (etype, rschema, role))
add_msg(w, str(tschema),
'inlined:%s.%s.%s' % (etype, rschema, role))
- if appearsin_addmenu.etype_get(eschema, rschema, role, tschema) and \
- (libconfig is None or not
- libappearsin_addmenu.etype_get(eschema, rschema, role, tschema)):
+ if appearsin_addmenu.etype_get(eschema, rschema, role, tschema):
+ if libconfig is not None and libappearsin_addmenu.etype_get(eschema, rschema, role, tschema):
+ if eschema in libschema and tschema in libschema:
+ continue
if role == 'subject':
label = 'add %s %s %s %s' % (eschema, rschema,
tschema, role)
--- a/doc/book/en/devweb/controllers.rst Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/devweb/controllers.rst Wed May 05 18:10:33 2010 +0200
@@ -36,16 +36,16 @@
operations in response to a form being submitted; it works in close
association with the Forms, to which it delegates some of the work
-* the Form validator controller provides form validation from Ajax
+* the ``Form validator controller`` provides form validation from Ajax
context, using the Edit controller, to implement the classic form
- handling loop (user edits, hits 'submit/apply', validation occurs
+ handling loop (user edits, hits `submit/apply`, validation occurs
server-side by way of the Form validator controller, and the UI is
decorated with failure information, either global or per-field ,
until it is valid)
`Other`:
-* the SendMail controller (web/views/basecontrollers.py) is reponsible
+* the ``SendMail controller`` (web/views/basecontrollers.py) is reponsible
for outgoing email notifications
* the MailBugReport controller (web/views/basecontrollers.py) allows
@@ -57,14 +57,16 @@
All controllers (should) live in the 'controllers' namespace within
the global registry.
-API
-+++
+Concrete controllers
+++++++++++++++++++++
Most API details should be resolved by source code inspection, as the
-various controllers have differing goals.
+various controllers have differing goals. See for instance the
+:ref:`edit_controller` chapter.
-`web/controller.py` contains the top-level abstract Controller class and
-its (NotImplemented) entry point `publish(rset=None)` method.
+:mod:`cubicweb.web.controller` contains the top-level abstract
+Controller class and its unimplemented entry point
+`publish(rset=None)` method.
A handful of helpers are also provided there:
@@ -77,125 +79,3 @@
implementations dealing with HTTP (thus, for instance, not the
SendMail controller) may very well call this in their publication
process.
-
-
-.. _edit_controller:
-
-The `edit controller`
-+++++++++++++++++++++
-
-It can be found in (:mod:`cubicweb.web.views.editcontroller`).
-
-Editing control
-~~~~~~~~~~~~~~~~
-
-.. XXX this look obsolete
-
-The parameters related to entities to edit are specified as follows ::
-
- <field name>:<entity eid>
-
-where entity eid could be a letter in case of an entity to create. We
-name those parameters as *qualified*.
-
-1. Retrieval of entities to edit by looking for the forms parameters
- starting by `eid:` and also having a parameter `__type` associated
- (also *qualified* by eid)
-
-2. For all the attributes and the relations of an entity to edit:
-
- 1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
- qualified in the case of a relation where the entity is object
- 2. if found, the value returned is considered as the initial value
- for this relaiton and we then look for the new value(s) in the parameter
- <relation name> (qualified)
- 3. if the value returned is different from the initial value, an database update
- request is done
-
-3. For each entity to edit:
-
- 1. if a qualified parameter `__linkto` is specified, its value has to be
- a string (or a list of string) such as: ::
-
- <relation type>:<eids>:<target>
-
- where <target> is either `subject` or `object` and each eid could be
- separated from the others by a `_`. Target specifies if the *edited entity*
- is subject or object of the relation and each relation specified will
- be inserted.
-
- 2. if a qualified parameter `__clone_eid` is specified for an entity, the
- relations of the specified entity passed as value of this parameter are
- copied on the edited entity.
-
- 3. if a qualified parameter `__delete` is specified, its value must be
- a string or a list of string such as follows: ::
-
- <ssubjects eids>:<relation type>:<objects eids>
-
- where each eid subject or object can be seperated from the other
- by `_`. Each relation specified will be deleted.
-
- 4. if a qualified parameter `__insert` is specified, its value should
- follow the same pattern as `__delete`, but each relation specified is
- inserted.
-
-4. If the parameters `__insert` and/or `__delete` are found not qualified,
- they are interpreted as explained above (independantly from the number
- of entities edited).
-
-5. If no entity is edited but the form contains the parameters `__linkto`
- and `eid`, this one is interpreted by using the value specified for `eid`
- to designate the entity on which to add the relations.
-
-
-.. note::
-
- * If the parameter `__action_delete` is found, all the entities specified
- as to be edited will be deleted.
-
- * If the parameter `__action_cancel` is found, no action is completed.
-
- * If the parameter `__action_apply` is found, the editing is
- applied normally but the redirection is done on the form (see
- :ref:`RedirectionControl`).
-
- * The parameter `__method` is also supported as for the main template
-
- * If no entity is found to be edited and if there is no parameter
- `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
- `__insert`, an error is raised.
-
- * Using the parameter `__message` in the form will allow to use its value
- as a message to provide the user once the editing is completed.
-
-
-.. _RedirectionControl:
-
-Redirection control
-~~~~~~~~~~~~~~~~~~~
-Once editing is completed, there is still an issue left: where should we go
-now? If nothing is specified, the controller will do his job but it does not
-mean we will be happy with the result. We can control that by using the
-following parameters:
-
-* `__redirectpath`: path of the URL (relative to the root URL of the site,
- no form parameters
-
-* `__redirectparams`: forms parameters to add to the path
-
-* `__redirectrql`: redirection RQL request
-
-* `__redirectvid`: redirection view identifier
-
-* `__errorurl`: initial form URL, used for redirecting in case a validation
- error is raised during editing. If this one is not specified, an error page
- is displayed instead of going back to the form (which is, if necessary,
- responsible for displaying the errors)
-
-* `__form_id`: initial view form identifier, used if `__action_apply` is
- found
-
-In general we use either `__redirectpath` and `__redirectparams` or
-`__redirectrql` and `__redirectvid`.
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/dissection.rst Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,276 @@
+
+Dissection of a Form
+--------------------
+
+This is done (again) with a vanilla instance of the `tracker`_
+cube. We will populate the database with a bunch of entities and see
+what kind of job the automatic entity form does.
+
+.. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker
+
+Patching the session object
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to play interactively with web side application objects, we
+have to cheat a bit: we will decorate the session object with some
+missing artifacts that should belong to a web request object. With
+that we can instantiate and render forms interactively.
+
+The function below does the minimum to allow going through this
+exercice. Some attributes or methods may be missing for other
+purposes. It is nevertheless not complicated to enhance it if need
+arises.
+
+.. sourcecode:: python
+
+ def monkey_patch_session(session):
+ """ useful to use the cw shell session object
+ with web appobjects, which expect more than a plain
+ data repository session
+ """
+ # for autoform selection
+ session.json_request = False
+ session.url = lambda: u'http://perdu.com'
+ session.session = session
+ session.form = {}
+ session.list_form_param = lambda *args: []
+ # for render
+ session.use_fckeditor = lambda: False
+ session._ressources = []
+ session.add_js = session.add_css = lambda *args: session._ressources.append(args)
+ session.external_resource = lambda x:{}
+ session._tabcount = 0
+ def next_tabindex():
+ session._tabcount += 1
+ return session._tabcount
+ session.next_tabindex = next_tabindex
+ return session
+
+Populating the database
+~~~~~~~~~~~~~~~~~~~~~~~
+
+We should start by setting up a bit of context: a project with two
+unpublished versions, and a ticket linked to the project and the first
+version.
+
+.. sourcecode:: python
+
+ >>> p = rql('INSERT Project P: P name "cubicweb"')
+ >>> for num in ('0.1.0', '0.2.0'):
+ ... rql('INSERT Version V: V num "%s", V version_of P WHERE P eid %%(p)s' % num, {'p': p[0][0]})
+ ...
+ <resultset 'INSERT Version V: V num "0.1.0", V version_of P WHERE P eid %(p)s' (1 rows): [765L] (('Version',))>
+ <resultset 'INSERT Version V: V num "0.2.0", V version_of P WHERE P eid %(p)s' (1 rows): [766L] (('Version',))>
+ >>> t = rql('INSERT Ticket T: T title "let us write more doc", T done_in V, '
+ 'T concerns P WHERE V num "0.1.0"', P eid %(p)s', {'p': p[0][0]})
+ >>> commit()
+
+Now let's see what the edition form builds for us.
+
+.. sourcecode:: python
+
+ >>> monkey_patch_session(session)
+ >>> form = session.vreg['forms'].select('edition', session, rset=rql('Ticket T'))
+ >>> html = form.render()
+
+This creates an automatic entity form. The ``.render()`` call yields
+an html (unicode) string. The html output is shown below (with
+internal fieldset omitted).
+
+Looking at the html output
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The form enveloppe
+''''''''''''''''''
+
+.. sourcecode:: html
+
+ <div class="iformTitle"><span>main informations</span></div>
+ <div class="formBody">
+ <form action="http://crater:9999/validateform" method="post" enctype="application/x-www-form-urlencoded"
+ id="entityForm" onsubmit="return freezeFormButtons('entityForm');"
+ class="entityForm" cubicweb:target="eformframe">
+ <div id="progress">validating...</div>
+ <fieldset>
+ <input name="__form_id" type="hidden" value="edition" />
+ <input name="__errorurl" type="hidden" value="http://perdu.com#entityForm" />
+ <input name="__domid" type="hidden" value="entityForm" />
+ <input name="__type:763" type="hidden" value="Ticket" />
+ <input name="eid" type="hidden" value="763" />
+ <input name="__maineid" type="hidden" value="763" />
+ <input name="_cw_edited_fields:763" type="hidden"
+ value="concerns-subject,done_in-subject,priority-subject,type-subject,title-subject,description-subject,__type,_cw_generic_field" />
+ ...
+ </fieldset>
+ </form>
+ </div>
+
+The main fieldset encloses a set of hidden fields containing various
+metadata, that will be used by the `edit controller` to process it
+back correctly.
+
+The `freezeFormButtons(...)` javascript callback defined on the
+``conlick`` event of the form element prevents accidental multiple
+clicks in a row.
+
+The ``action`` of the form is mapped to the `validateform` controller
+(situated in :mod:`cubicweb.web.views.basecontrollers`).
+
+The attributes section
+''''''''''''''''''''''
+
+We can have a look at some of the inner nodes of the form. Some fields
+are omitted as they are redundant for our purposes.
+
+.. sourcecode:: html
+
+ <fieldset class="default">
+ <table class="attributeForm">
+ <tr class="title_subject_row">
+ <th class="labelCol"><label class="required" for="title-subject:763">title</label></th>
+ <td>
+ <input id="title-subject:763" maxlength="128" name="title-subject:763" size="45"
+ tabindex="1" type="text" value="let us write more doc" />
+ </td>
+ </tr>
+ ... (description field omitted) ...
+ <tr class="priority_subject_row">
+ <th class="labelCol"><label class="required" for="priority-subject:763">priority</label></th>
+ <td>
+ <select id="priority-subject:763" name="priority-subject:763" size="1" tabindex="4">
+ <option value="important">important</option>
+ <option selected="selected" value="normal">normal</option>
+ <option value="minor">minor</option>
+ </select>
+ <div class="helper">importance</div>
+ </td>
+ </tr>
+ ... (type field omitted) ...
+ <tr class="concerns_subject_row">
+ <th class="labelCol"><label class="required" for="concerns-subject:763">concerns</label></th>
+ <td>
+ <select id="concerns-subject:763" name="concerns-subject:763" size="1" tabindex="6">
+ <option selected="selected" value="760">Foo</option>
+ </select>
+ </td>
+ </tr>
+ <tr class="done_in_subject_row">
+ <th class="labelCol"><label for="done_in-subject:763">done in</label></th>
+ <td>
+ <select id="done_in-subject:763" name="done_in-subject:763" size="1" tabindex="7">
+ <option value="__cubicweb_internal_field__"></option>
+ <option selected="selected" value="761">Foo 0.1.0</option>
+ <option value="762">Foo 0.2.0</option>
+ </select>
+ <div class="helper">version in which this ticket will be / has been done</div>
+ </td>
+ </tr>
+ </table>
+ </fieldset>
+
+
+Note that the whole form layout has been computed by the form
+renderer. It is the renderer which produces the table
+structure. Otherwise, the fields html structure is emitted by their
+associated widget.
+
+While it is called the `attributes` section of the form, it actually
+contains attributes and *mandatory relations*. For each field, we
+observe:
+
+* a dedicated row with a specific class, such as ``title_subject_row``
+ (responsability of the form renderer)
+
+* an html widget (input, select, ...) with:
+
+ * an id built from the ``rtype-role:eid`` pattern
+
+ * a name built from the same pattern
+
+ * possible values or preselected options
+
+The relations section
+'''''''''''''''''''''
+
+.. sourcecode:: html
+
+ <fieldset class="This ticket :">
+ <legend>This ticket :</legend>
+ <table class="attributeForm">
+ <tr class="_cw_generic_field_None_row">
+ <td colspan="2">
+ <table id="relatedEntities">
+ <tr><th> </th><td> </td></tr>
+ <tr id="relationSelectorRow_763" class="separator">
+ <th class="labelCol">
+ <select id="relationSelector_763" tabindex="8"
+ onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,763);">
+ <option value="">select a relation</option>
+ <option value="appeared_in_subject">appeared in</option>
+ <option value="custom_workflow_subject">custom workflow</option>
+ <option value="depends_on_object">dependency of</option>
+ <option value="depends_on_subject">depends on</option>
+ <option value="identical_to_subject">identical to</option>
+ <option value="see_also_subject">see also</option>
+ </select>
+ </th>
+ <td id="unrelatedDivs_763"></td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ </fieldset>
+
+The optional relations are grouped into a drop-down combo
+box. Selection of an item triggers a javascript function which will:
+
+* show already related entities in the div of id `relatedentities`
+ using a two-colown layout, with an action to allow deletion of
+ individual relations (there are none in this example)
+
+* provide a relation selector in the div of id `relationSelector_EID`
+ to allow the user to set up relations and trigger dynamic action on
+ the last div
+
+* fill the div of id `unrelatedDivs_EID` with a dynamically computed
+ selection widget allowing direct selection of an unrelated (but
+ relatable) entity or a switch towards the `search mode` of
+ |cubicweb| which allows full browsing and selection of an entity
+ using a dedicated action situated in the left column boxes.
+
+
+The buttons zone
+''''''''''''''''
+
+Finally comes the buttons zone.
+
+.. sourcecode:: html
+
+ <table width="100%">
+ <tbody>
+ <tr>
+ <td align="center">
+ <button class="validateButton" tabindex="9" type="submit" value="validate">
+ <img alt="OK_ICON" src="{}" />validate
+ </button>
+ </td>
+ <td style="align: right; width: 50%;">
+ <button class="validateButton"
+ onclick="postForm('__action_apply', 'button_apply', 'entityForm')"
+ tabindex="10" type="button" value="apply">
+ <img alt="APPLY_ICON" src="{}" />apply
+ </button>
+ <button class="validateButton"
+ onclick="postForm('__action_cancel', 'button_cancel', 'entityForm')"
+ tabindex="11" type="button" value="cancel">
+ <img alt="CANCEL_ICON" src="{}" />cancel
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+The most notable artifacts here are the ``postForm(...)`` calls
+defined on click events on these buttons. This function basically
+submits the form. XXX validateform vs validateForm, ajax or not ?
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/editcontroller.rst Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,117 @@
+.. _edit_controller:
+
+The `edit controller`
++++++++++++++++++++++
+
+It can be found in (:mod:`cubicweb.web.views.editcontroller`).
+
+Editing control
+~~~~~~~~~~~~~~~~
+
+.. XXX this look obsolete
+
+The parameters related to entities to edit are specified as follows ::
+
+ <field name>:<entity eid>
+
+where entity eid could be a letter in case of an entity to create. We
+name those parameters as *qualified*.
+
+1. Retrieval of entities to edit by looking for the forms parameters
+ starting by `eid:` and also having a parameter `__type` associated
+ (also *qualified* by eid)
+
+2. For all the attributes and the relations of an entity to edit:
+
+ 1. search for a parameter `edits-<relation name>` or `edito-<relation name>`
+ qualified in the case of a relation where the entity is object
+ 2. if found, the value returned is considered as the initial value
+ for this relaiton and we then look for the new value(s) in the parameter
+ <relation name> (qualified)
+ 3. if the value returned is different from the initial value, an database update
+ request is done
+
+3. For each entity to edit:
+
+ 1. if a qualified parameter `__linkto` is specified, its value has to be
+ a string (or a list of string) such as: ::
+
+ <relation type>:<eids>:<target>
+
+ where <target> is either `subject` or `object` and each eid could be
+ separated from the others by a `_`. Target specifies if the *edited entity*
+ is subject or object of the relation and each relation specified will
+ be inserted.
+
+ 2. if a qualified parameter `__clone_eid` is specified for an entity, the
+ relations of the specified entity passed as value of this parameter are
+ copied on the edited entity.
+
+ 3. if a qualified parameter `__delete` is specified, its value must be
+ a string or a list of string such as follows: ::
+
+ <ssubjects eids>:<relation type>:<objects eids>
+
+ where each eid subject or object can be seperated from the other
+ by `_`. Each relation specified will be deleted.
+
+ 4. if a qualified parameter `__insert` is specified, its value should
+ follow the same pattern as `__delete`, but each relation specified is
+ inserted.
+
+4. If the parameters `__insert` and/or `__delete` are found not qualified,
+ they are interpreted as explained above (independantly from the number
+ of entities edited).
+
+5. If no entity is edited but the form contains the parameters `__linkto`
+ and `eid`, this one is interpreted by using the value specified for `eid`
+ to designate the entity on which to add the relations.
+
+
+.. note::
+
+ * if the parameter `__action_delete` is found, all the entities specified
+ as to be edited will be deleted.
+
+ * if the parameter `__action_cancel` is found, no action is completed.
+
+ * if the parameter `__action_apply` is found, the editing is
+ applied normally but the redirection is done on the form (see
+ :ref:`RedirectionControl`).
+
+ * if no entity is found to be edited and if there is no parameter
+ `__action_delete`, `__action_cancel`, `__linkto`, `__delete` or
+ `__insert`, an error is raised.
+
+ * using the parameter `__message` in the form will allow to use its value
+ as a message to provide the user once the editing is completed.
+
+
+.. _RedirectionControl:
+
+Redirection control
+~~~~~~~~~~~~~~~~~~~
+Once editing is completed, there is still an issue left: where should we go
+now? If nothing is specified, the controller will do his job but it does not
+mean we will be happy with the result. We can control that by using the
+following parameters:
+
+* `__redirectpath`: path of the URL (relative to the root URL of the site,
+ no form parameters
+
+* `__redirectparams`: forms parameters to add to the path
+
+* `__redirectrql`: redirection RQL request
+
+* `__redirectvid`: redirection view identifier
+
+* `__errorurl`: initial form URL, used for redirecting in case a validation
+ error is raised during editing. If this one is not specified, an error page
+ is displayed instead of going back to the form (which is, if necessary,
+ responsible for displaying the errors)
+
+* `__form_id`: initial view form identifier, used if `__action_apply` is
+ found
+
+In general we use either `__redirectpath` and `__redirectparams` or
+`__redirectrql` and `__redirectvid`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/examples.rst Wed May 05 18:10: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'<table class="headersform">')
+ for field in fields:
+ if field.name == 'mailbody':
+ w(u'</table>')
+ w(u'<div id="toolbar">')
+ w(u'<ul>')
+ for button in form.form_buttons:
+ w(u'<li>%s</li>' % button.render(form))
+ w(u'</ul>')
+ w(u'</div>')
+ w(u'<div>')
+ w(field.render(form, self))
+ w(u'</div>')
+ else:
+ w(u'<tr>')
+ w(u'<td class="hlabel">%s</td>' %
+ self.render_label(form, field))
+ w(u'<td class="hvalue">')
+ w(field.render(form, self))
+ w(u'</td></tr>')
+
+ 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'
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/form.rst Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,223 @@
+HTML form construction
+----------------------
+
+CubicWeb provides the somewhat usual form / field / widget / renderer abstraction
+to provide generic building blocks which will greatly help you in building forms
+properly integrated with CubicWeb (coherent display, error handling, etc...),
+while keeping things as flexible as possible.
+
+A ``form`` basically only holds a set of ``fields``, and has te be bound to a
+``renderer`` which is responsible to layout them. Each field is bound to a
+``widget`` that will be used to fill in value(s) for that field (at form
+generation time) and 'decode' (fetch and give a proper Python type to) values
+sent back by the browser.
+
+The ``field`` should be used according to the type of what you want to edit.
+E.g. if you want to edit some date, you'll have to use the
+:class:`cubicweb.web.formfields.DateField`. Then you can choose among multiple
+widgets to edit it, for instance :class:`cubicweb.web.formwidgets.TextInput` (a
+bare text field), :class:`~cubicweb.web.formwidgets.DateTimePicker` (a simple
+calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery
+calendar). You can of course also write your own widget.
+
+Exploring the available forms
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A small excursion into a |cubicweb| shell is the quickest way to
+discover available forms (or application objects in general).
+
+.. sourcecode:: python
+
+ >>> from pprint import pprint
+ >>> pprint( session.vreg['forms'] )
+ {'base': [<class 'cubicweb.web.views.forms.FieldsForm'>,
+ <class 'cubicweb.web.views.forms.EntityFieldsForm'>],
+ 'changestate': [<class 'cubicweb.web.views.workflow.ChangeStateForm'>,
+ <class 'cubes.tracker.views.forms.VersionChangeStateForm'>],
+ 'composite': [<class 'cubicweb.web.views.forms.CompositeForm'>,
+ <class 'cubicweb.web.views.forms.CompositeEntityForm'>],
+ 'deleteconf': [<class 'cubicweb.web.views.editforms.DeleteConfForm'>],
+ 'edition': [<class 'cubicweb.web.views.autoform.AutomaticEntityForm'>,
+ <class 'cubicweb.web.views.workflow.TransitionEditionForm'>,
+ <class 'cubicweb.web.views.workflow.StateEditionForm'>],
+ 'logform': [<class 'cubicweb.web.views.basetemplates.LogForm'>],
+ 'massmailing': [<class 'cubicweb.web.views.massmailing.MassMailingForm'>],
+ 'muledit': [<class 'cubicweb.web.views.editforms.TableEditForm'>],
+ 'sparql': [<class 'cubicweb.web.views.sparql.SparqlForm'>]}
+
+
+The two most important form families here (for all pracitcal purposes)
+are `base` and `edition`. Most of the time one wants alterations of
+the AutomaticEntityForm (from the `edition` category).
+
+The Automatic Entity Form
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. automodule:: cubicweb.web.views.autoform
+
+Anatomy of a choices function
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let's have a look at the `ticket_done_in_choices` function given to
+the `choices` parameter of the relation tag that is applied to the
+('Ticket', 'done_in', '*') relation definition, as it is both typical
+and sophisticated enough. This is a code snippet from the `tracker`_
+cube.
+
+.. _`tracker`: http://www.cubicweb.org/project/cubicweb-tracker
+
+The ``Ticket`` entity type can be related to a ``Project`` and a
+``Version``, respectively through the ``concerns`` and ``done_in``
+relations. When a user is about to edit a ticket, we want to fill the
+combo box for the ``done_in`` relation with values pertinent with
+respect to the context. The important context here is:
+
+* creation or modification (we cannot fetch values the same way in
+ either case)
+
+* ``__linkto`` url parameter given in a creation context
+
+.. sourcecode:: python
+
+ from cubicweb.web import formfields
+
+ def ticket_done_in_choices(form, field):
+ entity = form.edited_entity
+ # first see if its specified by __linkto form parameters
+ linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject')
+ if linkedto:
+ return linkedto
+ # it isn't, get initial values
+ vocab = formfields.relvoc_init(entity, 'done_in', 'subject')
+ veid = None
+ # try to fetch the (already or pending) related version and project
+ if not entity.has_eid():
+ peids = entity.linked_to('concerns', 'subject')
+ peid = peids and peids[0]
+ else:
+ peid = entity.project.eid
+ veid = entity.done_in and entity.done_in[0].eid
+ if peid:
+ # we can complete the vocabulary with relevant values
+ rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
+ rset = form._cw.execute(
+ 'Any V, VN ORDERBY version_sort_value(VN) '
+ 'WHERE V version_of P, P eid %(p)s, V num VN, '
+ 'V in_state ST, NOT ST name "published"', {'p': peid}, 'p')
+ vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
+ if rschema.has_perm(form._cw, 'add', toeid=v.eid)
+ and v.eid != veid]
+ return vocab
+
+The first thing we have to do is fetch potential values from the
+``__linkto`` url parameter that is often found in entity creation
+contexts (the creation action provides such a parameter with a
+predetermined value; for instance in this case, ticket creation could
+occur in the context of a `Version` entity). The
+:mod:`cubicweb.web.formfields` module provides a ``relvoc_linkedto``
+utility function that gets a list suitably filled with vocabulary
+values.
+
+.. sourcecode:: python
+
+ linkedto = formfields.relvoc_linkedto(entity, 'done_in', 'subject')
+ if linkedto:
+ return linkedto
+
+Then, if no ``__linkto`` argument was given, we must prepare the
+vocabulary with an initial empty value (because `done_in` is not
+mandatory, we must allow the user to not select a verson) and already
+linked values. This is done with the ``relvoc_init`` function.
+
+.. sourcecode:: python
+
+ vocab = formfields.relvoc_init(entity, 'done_in', 'subject')
+
+But then, we have to give more: if the ticket is related to a project,
+we should provide all the non published versions of this project
+(`Version` and `Project` can be related through the `version_of`
+relation). Conversely, if we do not know yet the project, it would not
+make sense to propose all existing versions as it could potentially
+lead to incoherences. Even if these will be caught by some
+RQLConstraint, it is wise not to tempt the user with error-inducing
+candidate values.
+
+The "ticket is related to a project" part must be decomposed as:
+
+* this is a new ticket which is created is the context of a project
+
+* this is an already existing ticket, linked to a project (through the
+ `concerns` relation)
+
+* there is no related project (quite unlikely given the cardinality of
+ the `concerns` relation, so it can only mean that we are creating a
+ new ticket, and a project is about to be selected but there is no
+ ``__linkto`` argument)
+
+.. note::
+
+ the last situation could happen in several ways, but of course in a
+ polished application, the paths to ticket creation should be
+ controlled so as to avoid a suboptimal end-user experience
+
+Hence, we try to fetch the related project.
+
+.. sourcecode:: python
+
+ veid = None
+ if not entity.has_eid():
+ peids = entity.linked_to('concerns', 'subject')
+ peid = peids and peids[0]
+ else:
+ peid = entity.project.eid
+ veid = entity.done_in and entity.done_in[0].eid
+
+We distinguish between entity creation and entity modification using
+the ``Entity.has_eid()`` method, which returns `False` on creation. At
+creation time the only way to get a project is through the
+``__linkto`` parameter. Notice that we fetch the version in which the
+ticket is `done_in` if any, for later.
+
+.. note::
+
+ the implementation above assumes that if there is a ``__linkto``
+ parameter, it is only about a project. While it makes sense most of
+ the time, it is not an absolute. Depending on how an entity creation
+ action action url is built, several outcomes could be possible
+ there
+
+If the ticket is already linked to a project, fetching it is
+trivial. Then we add the relevant version to the initial vocabulary.
+
+.. sourcecode:: python
+
+ if peid:
+ rschema = form._cw.vreg.schema['done_in'].rdef('Ticket', 'Version')
+ rset = form._cw.execute(
+ 'Any V, VN ORDERBY version_sort_value(VN) '
+ 'WHERE V version_of P, P eid %(p)s, V num VN, '
+ 'V in_state ST, NOT ST name "published"', {'p': peid})
+ vocab += [(v.view('combobox'), v.eid) for v in rset.entities()
+ if rschema.has_perm(form._cw, 'add', toeid=v.eid)
+ and v.eid != veid]
+
+.. warning::
+
+ we have to defend ourselves against lack of a project eid. Given
+ the cardinality of the `concerns` relation, there *must* be a
+ project, but this rule can only be enforced at validation time,
+ which will happen of course only after form subsmission
+
+Here, given a project eid, we complete the vocabulary with all
+unpublished versions defined in the project (sorted by number) for
+which the current user is allowed to establish the relation.
+
+APIs
+~~~~
+
+.. automodule:: cubicweb.web.formfields
+.. automodule:: cubicweb.web.formwidgets
+.. automodule:: cubicweb.web.views.forms
+.. automodule:: cubicweb.web.views.formrenderers
+
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/edition/index.rst Wed May 05 18:10:33 2010 +0200
@@ -0,0 +1,15 @@
+Edition control
+===============
+
+This chapter covers the editing capabilities of |cubicweb|. It
+explains html Form construction, the Edit Controller and their
+interactions.
+
+
+.. toctree::
+ :maxdepth: 2
+
+ form
+ dissection
+ editcontroller
+ examples
--- a/doc/book/en/devweb/form.rst Wed May 05 18:08:34 2010 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,216 +0,0 @@
-HTML form construction
-----------------------
-
-CubicWeb provides the somewhat usual form / field / widget / renderer abstraction
-to provide generic building blocks which will greatly help you in building forms
-properly integrated with CubicWeb (coherent display, error handling, etc...),
-while keeping things as flexible as possible.
-
-A **form** basically only holds a set of **fields**, and has te be bound to a
-**renderer** which is responsible to layout them. Each field is bound to a
-**widget** that will be used to fill in value(s) for that field (at form
-generation time) and 'decode' (fetch and give a proper Python type to) values
-sent back by the browser.
-
-The **field** should be used according to the type of what you want to edit.
-E.g. if you want to edit some date, you'll have to use the
-:class:`cubicweb.web.formfields.DateField`. Then you can choose among multiple
-widgets to edit it, for instance :class:`cubicweb.web.formwidgets.TextInput` (a
-bare text field), :class:`~cubicweb.web.formwidgets.DateTimePicker` (a simple
-calendar) or even :class:`~cubicweb.web.formwidgets.JQueryDatePicker` (the JQuery
-calendar). You can of course also write your own widget.
-
-
-.. automodule:: cubicweb.web.views.autoform
-
-
-Example of bare 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
-check 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'<table class="headersform">')
- for field in fields:
- if field.name == 'mailbody':
- w(u'</table>')
- w(u'<div id="toolbar">')
- w(u'<ul>')
- for button in form.form_buttons:
- w(u'<li>%s</li>' % button.render(form))
- w(u'</ul>')
- w(u'</div>')
- w(u'<div>')
- w(field.render(form, self))
- w(u'</div>')
- else:
- w(u'<tr>')
- w(u'<td class="hlabel">%s</td>' % self.render_label(form, field))
- w(u'<td class="hvalue">')
- w(field.render(form, self))
- w(u'</td></tr>')
-
- 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
-~~~
-
-.. automodule:: cubicweb.web.formfields
-.. automodule:: cubicweb.web.formwidgets
-.. automodule:: cubicweb.web.views.forms
-.. automodule:: cubicweb.web.views.formrenderers
-
-.. Example of entity fields form
--- a/doc/book/en/devweb/index.rst Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/devweb/index.rst Wed May 05 18:10:33 2010 +0200
@@ -14,7 +14,7 @@
rtags
js
css
- form
+ edition/index
facets
internationalization
.. property
--- a/doc/book/en/devweb/request.rst Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/devweb/request.rst Wed May 05 18:10:33 2010 +0200
@@ -4,20 +4,28 @@
Overview
````````
-A request instance is created when an HTTP request is sent to the web server.
-It contains informations such as form parameters, user authenticated, etc.
+A request instance is created when an HTTP request is sent to the web
+server. It contains informations such as form parameters,
+authenticated user, etc. It is a very prevalent object and is used
+throughout all of the framework and applications.
-**Globally, a request represents a user query, either through HTTP or not
-(we also talk about RQL queries on the server side for example).**
+**A request represents a user query, either through HTTP or not (we
+also talk about RQL queries on the server side for example).**
+
+Here is a non-exhaustive list of attributes and methods available on
+request objects (grouped by category):
-An instance of `Request` has the following attributes:
+* `Browser control`:
-* `user`, instance of `cubicweb.common.utils.User` corresponding to the authenticated
- user
-* `form`, dictionary containing the values of a web form
-* `encoding`, character encoding to use in the response
+ * `ie_browser`: tells if the browser belong to the Internet Explorer
+ family
+ * `xhtml_browser`: tells if the browser is able to properly handle
+ XHTML (at the HTTP content_type level)
-But also:
+* `User and identification`:
+
+ * `user`, instance of `cubicweb.common.utils.User` corresponding to
+ the authenticated user
* `Session data handling`
@@ -27,6 +35,36 @@
* `set_session_data(key, value)`, assign a value to a key
* `del_session_data(key)`, suppress the value associated to a key
+* `Edition` (utilities for edition control):
+
+ * `cancel_edition`: resets error url and cleans up pending operations
+ * `create_entity`: utility to create an entity (from an etype,
+ attributes and relation values)
+ * `datadir_url`: returns the url to the merged external resources
+ (|cubicweb|'s `web/data` directory plus all `data` directories of
+ used cubes)
+ * `edited_eids`: returns the list of eids of entities that are
+ edited under the current http request
+ * `eid_rset(eid)`: utility which returns a result set from an eid
+ * `entity_from_eid(eid)`: returns an entity instance from the given eid
+ * `encoding`: returns the encoding of the current HTTP request
+ * `ensure_ro_rql(rql)`: ensure some rql query is a data request
+ * etype_rset
+ * `form`, dictionary containing the values of a web form
+ * `encoding`, character encoding to use in the response
+ * `next_tabindex()`: returns a monotonically growing integer used to
+ build the html tab index of forms
+
+* `HTTP`
+
+ * `authmode`: returns a string describing the authentication mode
+ (http, cookie, ...)
+ * `lang`: returns the user agents/browser's language as carried by
+ the http request
+ * `demote_to_html()`: in the context of an XHTML compliant browser,
+ this will force emission of the response as an HTML document
+ (using the http content negociation)
+
* `Cookies handling`
* `get_cookie()`, returns a dictionary containing the value of the header
@@ -39,10 +77,28 @@
* `URL handling`
+ * `build_url(__vid, *args, **kwargs)`: return an absolute URL using
+ params dictionary key/values as URL parameters. Values are
+ automatically URL quoted, and the publishing method to use may be
+ specified or will be guessed.
+ * `build_url_params(**kwargs)`: returns a properly prepared (quoted,
+ separators, ...) string from the given parameters
* `url()`, returns the full URL of the HTTP request
* `base_url()`, returns the root URL of the web application
* `relative_path()`, returns the relative path of the request
+* `Web resource (.css, .js files, etc.) handling`:
+
+ * `add_css(cssfiles)`: adds the given list of css resources to the current
+ html headers
+ * `add_js(jsfiles)`: adds the given list of javascript resources to the
+ current html headers
+ * `add_onload(jscode)`: inject the given jscode fragment (an unicode
+ string) into the current html headers, wrapped inside a
+ document.ready(...) or another ajax-friendly one-time trigger event
+ * `add_header(header, values)`: adds the header/value pair to the
+ current html headers
+
* `And more...`
* `set_content_type(content_type, filename=None)`, adds the header HTTP
--- a/doc/book/en/devweb/views/editforms.rst Wed May 05 18:08:34 2010 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-Standard forms
---------------
-
- (:mod:`cubicweb.web.views.editforms`)
-
-XXX feed me
--- a/doc/book/en/tutorials/advanced/index.rst Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/advanced/index.rst Wed May 05 18:10:33 2010 +0200
@@ -508,9 +508,11 @@
It's not complete, but show most things you'll want to do in tests: adding some
content, creating users and connecting as them in the test, etc...
-To run it type: ::
+To run it type:
- [syt@scorpius test]$ pytest unittest_sytweb.py
+.. sourcecode:: bash
+
+ $ pytest unittest_sytweb.py
======================== unittest_sytweb.py ========================
-> creating tables [....................]
-> inserting default user and default groups.
@@ -524,9 +526,11 @@
The first execution is taking time, since it creates a sqlite database for the
-test instance. The second one will be much quicker: ::
+test instance. The second one will be much quicker:
- [syt@scorpius test]$ pytest unittest_sytweb.py
+.. sourcecode:: bash
+
+ $ pytest unittest_sytweb.py
======================== unittest_sytweb.py ========================
.
----------------------------------------------------------------------
@@ -537,12 +541,11 @@
If you do some changes in your schema, you'll have to force regeneration of that
database. You do that by removing the tmpdb files before running the test: ::
- [syt@scorpius test]$ rm tmpdb*
+ $ rm tmpdb*
.. Note::
- pytest is a very convenient utilities to control test execution, from the `logilab-common`_
- package
+ pytest is a very convenient utility used to control test execution. It is available from the `logilab-common`_ package.
.. _`logilab-common`: http://www.logilab.org/project/logilab-common
@@ -578,7 +581,7 @@
To migrate my instance I simply type::
- [syt@scorpius ~]$ cubicweb-ctl upgrade sytweb
+ cubicweb-ctl upgrade sytweb
I'll then be asked some questions to do the migration step by step. You should say
YES when it asks if a backup of your database should be done, so you can get back
--- a/doc/book/en/tutorials/base/blog-in-five-minutes.rst Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/base/blog-in-five-minutes.rst Wed May 05 18:10:33 2010 +0200
@@ -29,9 +29,9 @@
Instance parameters
~~~~~~~~~~~~~~~~~~~
-If the database installation failed, you'd like to change some instance parameters, for example, the database host or the user name. These informations can be edited in the `source` file located in the /etc/cubicweb.d/myblog directory.
+If you would like to change some instance parameters, for example, the database host or the user name, edit the `source` file located in the /etc/cubicweb.d/myblog directory.
-Then relaunch the database creation:
+Then relaunch the database creation::
cubicweb-ctl db-create myblog
--- a/doc/book/en/tutorials/base/conclusion.rst Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/base/conclusion.rst Wed May 05 18:10:33 2010 +0200
@@ -3,13 +3,11 @@
What's next?
------------
-We demonstrated how from a straight out of the box *CubicWeb* installation, you
-can build your web application based on a data model. It's all already there:
-views, templates, permissions, etc. The step forward is now for you to customize
-according to your needs.
+In this chapter, we have seen have you can, right after the installation of *CubicWeb*, build a web application in five minutes by defining a data model. Everything is there already: views, templates, permissions, etc.
-Many features are available to extend your application, for example: RSS channel
-integration (:ref:`XmlAndRss`), hooks (:ref:`hooks`), support of sources such as
-Google App Engine (:ref:`GoogleAppEngineSource`) and lots of others to discover
-through our book.
+The next step is to change the design and learn about the many features available to customize and extend your application: RSS channels (:ref:`XmlAndRss`), events (:ref:`hooks`), support of sources such as
+Google App Engine (:ref:`GoogleAppEngineSource`), etc.
+You will find more `tutorials and howtos`_ in the blog published on the CubicWeb.org website.
+
+.. _`tutorials and howtos`: http://www.cubicweb.org/view?rql=Any+X+ORDERBY+D+DESC+WHERE+X+is+BlogEntry%2C+T+tags+X%2C+T+name+IN+%28%22tutorial%22%2C+%22howto%22%29%2C+X+creation_date+D
--- a/doc/book/en/tutorials/base/create-cube.rst Wed May 05 18:08:34 2010 +0200
+++ b/doc/book/en/tutorials/base/create-cube.rst Wed May 05 18:10:33 2010 +0200
@@ -30,7 +30,7 @@
Customize the views of your data: how and which part of your data are showed.
-Note: views don't concern the look'n'feel or design of the site. For that, you should use CSS instead, and default CSS or your new cube are located in 'blog/data/'.
+.. note:: views do not define the look'n'feel and the design of your application. For that, you will use CSS and the files located 'blog/data/'.
5. :ref:`DefineEntities`
@@ -396,6 +396,7 @@
want to add a ``category`` attribute in the ``Blog`` data type. This is called a migration.
The required steps are:
+
1. modify the file ``schema.py``. The ``Blog`` class looks now like this:
.. sourcecode:: python
@@ -405,7 +406,11 @@
description = String()
category = String(required=True, vocabulary=(_('Professional'), _('Personal')), default='Personal')
-2. stop your ``blogdemo`` instance
+2. stop your ``blogdemo`` instance:
+
+.. sourcecode:: bash
+
+ cubicweb-ctl stop blogdemo
3. start the cubicweb shell for your instance by running the following command:
@@ -413,15 +418,21 @@
cubicweb-ctl shell blogdemo
-4. in the shell, execute:
+4. at the cubicweb shell prompt, execute:
.. sourcecode:: python
add_attribute('Blog', 'category')
-5. you can restart your instance, modify a blog entity and check that the new attribute
+5. restart your instance:
+
+.. sourcecode:: bash
+
+ cubicweb-ctl start blogdemo
+
+6. modify a blog entity and check that the new attribute
``category`` has been added.
-Of course, you may also want to add relations, entity types, ... See :ref:`migration`
+Of course, you may also want to add relations, entity types, etc. See :ref:`migration`
for a list of all available migration commands.
--- a/entities/test/unittest_wfobjs.py Wed May 05 18:08:34 2010 +0200
+++ b/entities/test/unittest_wfobjs.py Wed May 05 18:10:33 2010 +0200
@@ -56,7 +56,7 @@
self.commit()
wf.add_state(u'foo')
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'name-subject': 'workflow already have a state of that name'})
+ self.assertEquals(ex.errors, {'state_of-subject': 'workflow already have a state of that name'})
# no pb if not in the same workflow
wf2 = add_wf(self, 'Company')
foo = wf2.add_state(u'foo', initial=True)
@@ -432,7 +432,7 @@
self.execute('SET X custom_workflow WF WHERE X eid %(x)s, WF eid %(wf)s',
{'wf': wf.eid, 'x': self.member.eid})
ex = self.assertRaises(ValidationError, self.commit)
- self.assertEquals(ex.errors, {'custom_workflow-subject': 'workflow isn\'t a workflow for this type'})
+ self.assertEquals(ex.errors, {'custom_workflow-subject': u"workflow isn't a workflow for this type"})
def test_del_custom_wf(self):
"""member in some state shared by the new workflow, nothing has to be
--- a/etwist/twconfig.py Wed May 05 18:08:34 2010 +0200
+++ b/etwist/twconfig.py Wed May 05 18:10:33 2010 +0200
@@ -80,7 +80,7 @@
{'type' : 'string',
'default': None,
'help': 'profile code and use the specified file to store stats if this option is set',
- 'group': 'main', 'level': 2,
+ 'group': 'main', 'level': 3,
}),
('pyro-server',
{'type' : 'yn',
--- a/hooks/integrity.py Wed May 05 18:08:34 2010 +0200
+++ b/hooks/integrity.py Wed May 05 18:10:33 2010 +0200
@@ -160,28 +160,30 @@
class _CheckConstraintsOp(hook.LateOperation):
- """check a new relation satisfy its constraints
- """
+ """ check a new relation satisfy its constraints """
+
def precommit_event(self):
- eidfrom, rtype, eidto = self.rdef
- # first check related entities have not been deleted in the same
- # transaction
- if self.session.deleted_in_transaction(eidfrom):
- return
- if self.session.deleted_in_transaction(eidto):
- return
- for constraint in self.constraints:
- # XXX
- # * lock RQLConstraint as well?
- # * use a constraint id to use per constraint lock and avoid
- # unnecessary commit serialization ?
- if isinstance(constraint, RQLUniqueConstraint):
- _acquire_unique_cstr_lock(self.session)
- try:
- constraint.repo_check(self.session, eidfrom, rtype, eidto)
- except NotImplementedError:
- self.critical('can\'t check constraint %s, not supported',
- constraint)
+ session = self.session
+ for values in session.transaction_data.pop('check_constraints_op'):
+ eidfrom, rtype, eidto, constraints = values
+ # first check related entities have not been deleted in the same
+ # transaction
+ if session.deleted_in_transaction(eidfrom):
+ return
+ if session.deleted_in_transaction(eidto):
+ return
+ for constraint in constraints:
+ # XXX
+ # * lock RQLConstraint as well?
+ # * use a constraint id to use per constraint lock and avoid
+ # unnecessary commit serialization ?
+ if isinstance(constraint, RQLUniqueConstraint):
+ _acquire_unique_cstr_lock(session)
+ try:
+ constraint.repo_check(session, eidfrom, rtype, eidto)
+ except NotImplementedError:
+ self.critical('can\'t check constraint %s, not supported',
+ constraint)
def commit_event(self):
pass
@@ -201,8 +203,9 @@
constraints = self._cw.schema_rproperty(self.rtype, self.eidfrom, self.eidto,
'constraints')
if constraints:
- _CheckConstraintsOp(self._cw, constraints=constraints,
- rdef=(self.eidfrom, self.rtype, self.eidto))
+ hook.set_operation(self._cw, 'check_constraints_op',
+ (self.eidfrom, self.rtype, self.eidto, tuple(constraints)),
+ _CheckConstraintsOp)
class CheckAttributeConstraintHook(IntegrityHook):
@@ -221,8 +224,9 @@
constraints = [c for c in eschema.rdef(attr).constraints
if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
if constraints:
- _CheckConstraintsOp(self._cw, constraints=constraints,
- rdef=(self.entity.eid, attr, None))
+ hook.set_operation(self._cw, 'check_constraints_op',
+ (self.entity.eid, attr, None, tuple(constraints)),
+ _CheckConstraintsOp)
class CheckUniqueHook(IntegrityHook):
--- a/hooks/metadata.py Wed May 05 18:08:34 2010 +0200
+++ b/hooks/metadata.py Wed May 05 18:10:33 2010 +0200
@@ -69,11 +69,13 @@
def precommit_event(self):
session = self.session
- if session.deleted_in_transaction(self.entity.eid):
- # entity have been created and deleted in the same transaction
- return
- if not self.entity.created_by:
- session.add_relation(self.entity.eid, 'created_by', session.user.eid)
+ for eid in session.transaction_data.pop('set_creator_op'):
+ if session.deleted_in_transaction(eid):
+ # entity have been created and deleted in the same transaction
+ continue
+ entity = session.entity_from_eid(eid)
+ if not entity.created_by:
+ session.add_relation(eid, 'created_by', session.user.eid)
class SetIsHook(MetaDataHook):
@@ -108,14 +110,14 @@
def __call__(self):
if not self._cw.is_internal_session:
self._cw.add_relation(self.entity.eid, 'owned_by', self._cw.user.eid)
- _SetCreatorOp(self._cw, entity=self.entity)
-
+ hook.set_operation(self._cw, 'set_creator_op', self.entity.eid, _SetCreatorOp)
class _SyncOwnersOp(hook.Operation):
def precommit_event(self):
- self.session.execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
- 'NOT EXISTS(X owned_by U, X eid %(x)s)',
- {'c': self.compositeeid, 'x': self.composedeid})
+ for compositeeid, composedeid in self.session.transaction_data.pop('sync_owners_op'):
+ self.session.execute('SET X owned_by U WHERE C owned_by U, C eid %(c)s,'
+ 'NOT EXISTS(X owned_by U, X eid %(x)s)',
+ {'c': compositeeid, 'x': composedeid})
class SyncCompositeOwner(MetaDataHook):
@@ -132,9 +134,9 @@
eidfrom, eidto = self.eidfrom, self.eidto
composite = self._cw.schema_rproperty(self.rtype, eidfrom, eidto, 'composite')
if composite == 'subject':
- _SyncOwnersOp(self._cw, compositeeid=eidfrom, composedeid=eidto)
+ hook.set_operation(self._cw, 'sync_owners_op', (eidfrom, eidto), _SyncOwnersOp)
elif composite == 'object':
- _SyncOwnersOp(self._cw, compositeeid=eidto, composedeid=eidfrom)
+ hook.set_operation(self._cw, 'sync_owners_op', (eidto, eidfrom), _SyncOwnersOp)
class FixUserOwnershipHook(MetaDataHook):
--- a/hooks/security.py Wed May 05 18:08:34 2010 +0200
+++ b/hooks/security.py Wed May 05 18:10:33 2010 +0200
@@ -53,8 +53,12 @@
class _CheckEntityPermissionOp(hook.LateOperation):
def precommit_event(self):
#print 'CheckEntityPermissionOp', self.session.user, self.entity, self.action
- self.entity.check_perm(self.action)
- check_entity_attributes(self.session, self.entity, self.editedattrs)
+ session = self.session
+ for values in session.transaction_data.pop('check_entity_perm_op'):
+ entity = session.entity_from_eid(values[0])
+ action = values[1]
+ entity.check_perm(action)
+ check_entity_attributes(session, entity, values[2:])
def commit_event(self):
pass
@@ -89,9 +93,9 @@
events = ('after_add_entity',)
def __call__(self):
- _CheckEntityPermissionOp(self._cw, entity=self.entity,
- editedattrs=tuple(self.entity.edited_attributes),
- action='add')
+ hook.set_operation(self._cw, 'check_entity_perm_op',
+ (self.entity.eid, 'add') + tuple(self.entity.edited_attributes),
+ _CheckEntityPermissionOp)
class AfterUpdateEntitySecurityHook(SecurityHook):
@@ -108,9 +112,9 @@
# save back editedattrs in case the entity is reedited later in the
# same transaction, which will lead to edited_attributes being
# overwritten
- _CheckEntityPermissionOp(self._cw, entity=self.entity,
- editedattrs=tuple(self.entity.edited_attributes),
- action='update')
+ hook.set_operation(self._cw, 'check_entity_perm_op',
+ (self.entity.eid, 'update') + tuple(self.entity.edited_attributes),
+ _CheckEntityPermissionOp)
class BeforeDelEntitySecurityHook(SecurityHook):
--- a/req.py Wed May 05 18:08:34 2010 +0200
+++ b/req.py Wed May 05 18:10:33 2010 +0200
@@ -106,10 +106,7 @@
return rset
def empty_rset(self):
- """return a result set for the given eid without doing actual query
- (we have the eid, we can suppose it exists and user has access to the
- entity)
- """
+ """ return a guaranteed empty result """
rset = ResultSet([], 'Any X WHERE X eid -1')
rset.req = self
return rset
--- a/server/serverconfig.py Wed May 05 18:08:34 2010 +0200
+++ b/server/serverconfig.py Wed May 05 18:10:33 2010 +0200
@@ -125,20 +125,20 @@
{'type' : 'time',
'default': '30min',
'help': 'session expiration time, default to 30 minutes',
- 'group': 'main', 'level': 1,
+ 'group': 'main', 'level': 3,
}),
('connections-pool-size',
{'type' : 'int',
'default': 4,
'help': 'size of the connections pools. Each source supporting multiple \
connections will have this number of opened connections.',
- 'group': 'main', 'level': 1,
+ 'group': 'main', 'level': 3,
}),
('rql-cache-size',
{'type' : 'int',
'default': 300,
'help': 'size of the parsed rql cache size.',
- 'group': 'main', 'level': 1,
+ 'group': 'main', 'level': 3,
}),
('undo-support',
{'type' : 'string', 'default': '',
@@ -146,20 +146,20 @@
[C]reate [U]pdate [D]elete entities / [A]dd [R]emove relation. Leave it empty \
for no undo support, set it to CUDAR for full undo support, or to DR for \
support undoing of deletion only.',
- 'group': 'main', 'level': 1,
+ 'group': 'main', 'level': 3,
}),
('keep-transaction-lifetime',
{'type' : 'int', 'default': 7,
'help': 'number of days during which transaction records should be \
kept (hence undoable).',
- 'group': 'main', 'level': 1,
+ 'group': 'main', 'level': 3,
}),
('multi-sources-etypes',
{'type' : 'csv', 'default': (),
'help': 'defines which entity types from this repository are used \
by some other instances. You should set this properly so those instances to \
detect updates / deletions.',
- 'group': 'main', 'level': 1,
+ 'group': 'main', 'level': 3,
}),
('delay-full-text-indexation',
@@ -168,7 +168,7 @@
' to be done when entity are added/modified by users, activate this '
'option and setup a job using cubicweb-ctl db-rebuild-fti on your '
'system (using cron for instance).',
- 'group': 'main', 'level': 1,
+ 'group': 'main', 'level': 3,
}),
# email configuration
@@ -181,7 +181,7 @@
modes are "default-dest-addrs" (emails specified in the configuration \
variable with the same name), "users" (every users which has activated \
account with an email set), "none" (no notification).',
- 'group': 'email', 'level': 1,
+ 'group': 'email', 'level': 2,
}),
('default-dest-addrs',
{'type' : 'csv',
@@ -189,7 +189,7 @@
'help': 'comma separated list of email addresses that will be used \
as default recipient when an email is sent and the notification has no \
specific recipient rules.',
- 'group': 'email', 'level': 1,
+ 'group': 'email', 'level': 2,
}),
('supervising-addrs',
{'type' : 'csv',
@@ -205,7 +205,7 @@
'help': 'Pyro server host, if not detectable correctly through \
gethostname(). It may contains port information using <host>:<port> notation, \
and if not set, it will be choosen randomly',
- 'group': 'pyro', 'level': 2,
+ 'group': 'pyro', 'level': 3,
}),
) + CubicWebConfiguration.options)
--- a/server/sources/ldapuser.py Wed May 05 18:08:34 2010 +0200
+++ b/server/sources/ldapuser.py Wed May 05 18:10:33 2010 +0200
@@ -44,6 +44,7 @@
from ldap.filter import filter_format, escape_filter_chars
from ldapurl import LDAPUrl
+from logilab.common.configuration import time_validator
from cubicweb import AuthenticationError, UnknownEid, RepositoryError
from cubicweb.server.utils import cartesian_product
from cubicweb.server.sources import (AbstractSource, TrFunc, GlobTrFunc,
@@ -85,13 +86,13 @@
'default': 'simple',
'choices': ('simple', 'cram_md5', 'digest_md5', 'gssapi'),
'help': 'authentication mode used to authenticate user to the ldap.',
- 'group': 'ldap-source', 'level': 1,
+ 'group': 'ldap-source', 'level': 3,
}),
('auth-realm',
{'type' : 'string',
'default': None,
'help': 'realm to use when using gssapi/kerberos authentication.',
- 'group': 'ldap-source', 'level': 1,
+ 'group': 'ldap-source', 'level': 3,
}),
('data-cnx-dn',
@@ -152,13 +153,13 @@
'default': '1d',
'help': 'interval between synchronization with the ldap \
directory (default to once a day).',
- 'group': 'ldap-source', 'level': 2,
+ 'group': 'ldap-source', 'level': 3,
}),
('cache-life-time',
{'type' : 'time',
'default': '2h',
'help': 'life time of query cache in minutes (default to two hours).',
- 'group': 'ldap-source', 'level': 2,
+ 'group': 'ldap-source', 'level': 3,
}),
)
@@ -186,9 +187,11 @@
for o in self.user_classes]
self._conn = None
self._cache = {}
- ttlm = int(source_config.get('cache-life-type', 2*60))
+ ttlm = time_validator(None, None,
+ source_config.get('cache-life-time', 2*60))
self._query_cache = TimedCache(ttlm)
- self._interval = int(source_config.get('synchronization-interval',
+ self._interval = time_validator(None, None,
+ source_config.get('synchronization-interval',
24*60*60))
def reset_caches(self):
--- a/server/test/unittest_repository.py Wed May 05 18:08:34 2010 +0200
+++ b/server/test/unittest_repository.py Wed May 05 18:10:33 2010 +0200
@@ -295,11 +295,19 @@
cnx = connect(self.repo.config.appid, u'admin', password='gingkow',
initlog=False) # don't reset logging configuration
try:
+ cnx.load_appobjects(subpath=('entities',))
# check we can get the schema
schema = cnx.get_schema()
+ self.failUnless(cnx.vreg)
+ self.failUnless('etypes'in cnx.vreg)
self.assertEquals(schema.__hashmode__, None)
cu = cnx.cursor()
rset = cu.execute('Any U,G WHERE U in_group G')
+ user = iter(rset.entities()).next()
+ self.failUnless(user._cw)
+ self.failUnless(user._cw.vreg)
+ from cubicweb.entities import authobjs
+ self.assertIsInstance(user._cw.user, authobjs.CWUser)
cnx.close()
done.append(True)
finally:
--- a/web/data/cubicweb.schema.css Wed May 05 18:08:34 2010 +0200
+++ b/web/data/cubicweb.schema.css Wed May 05 18:10:33 2010 +0200
@@ -15,21 +15,22 @@
}
-div.relationDefinition {
- float: left;
+div.relationDefinition {
+ float: left;
position: relative;
width: 60%;
padding: 0;
}
-div.acl{
+div.acl{
position: relative;
/* right: 20%;*/
width: 25%;
padding:0px 0px 0px 2em;
}
-div.acl table tr,td{
+div.acl table td,
+div.acl table tr {
padding: 2px 2px 2px 2px;
}
@@ -37,8 +38,8 @@
width : 100%;
}
-div.entityAttributes{
- margin: 3em 0 5em;
+div.entityAttributes{
+ margin: 3em 0 5em;
font: normal 9pt Arial;
}
@@ -58,12 +59,12 @@
div.body{
padding : 0.2em;
- padding-bottom : 0.4em;
+ padding-bottom : 0.4em;
overflow: auto;
}
div.body table td{
- padding:0.4em;
+ padding:0.4em;
}
div.box{
@@ -73,27 +74,27 @@
div.vl{
float:left;
- position:relative;
- margin-top:1em;
- border-top:1px solid black;
- line-height : 1px;
- width: 1em;
+ position:relative;
+ margin-top:1em;
+ border-top:1px solid black;
+ line-height : 1px;
+ width: 1em;
height : 0px}
div.hl{
float:left;
- position:relative;
- margin-top:1em;
- border-left:1px solid black;
- width: 1px;
+ position:relative;
+ margin-top:1em;
+ border-left:1px solid black;
+ width: 1px;
height : 10px
}
div.rels{
- float:left;
- position:relative;
- margin-top:1em;
- border-left:1px solid black;
+ float:left;
+ position:relative;
+ margin-top:1em;
+ border-left:1px solid black;
margin-left:-2px;}
div.firstrel, div.rel, div.lastrel{
@@ -104,7 +105,7 @@
}
/* FIXME set to 9em or an image*/
-div.rel, div.lastrel{
+div.rel, div.lastrel{
margin-top:0.7em}
div.vars{
@@ -113,24 +114,24 @@
div.firstvar, div.var, div.lastvar{
line-height:1em;
- border:1px solid black;
+ border:1px solid black;
padding:0.2em}
div.firstvar{
margin-top:1em;}
div.var{
- margin-top:0.5em;
+ margin-top:0.5em;
}
div.lastvar{
border:none;
}
-div.firstvar a,
+div.firstvar a,
div.var a,
div.rel a,
-div.firstrel a{
+div.firstrel a{
padding:0px ! important;
- margin : 0px ! important;
+ margin : 0px ! important;
}
--- a/web/schemaviewer.py Wed May 05 18:08:34 2010 +0200
+++ b/web/schemaviewer.py Wed May 05 18:10:33 2010 +0200
@@ -48,13 +48,13 @@
# no self.req managements
- def may_read(self, rdef, action):
+ def may_read(self, rdef, action='read'):
"""Return true if request user may read the given schema.
Always return True when no request is provided.
"""
if self.req is None:
return True
- return sch.may_have_permission('read', self.req)
+ return rdef.may_have_permission(action, self.req)
def format_eschema(self, eschema):
text = eschema.type
--- a/web/views/autoform.py Wed May 05 18:08:34 2010 +0200
+++ b/web/views/autoform.py Wed May 05 18:10:33 2010 +0200
@@ -16,16 +16,13 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
"""
-The automatic entity form
--------------------------
-
.. autodocstring:: cubicweb.web.views.autoform::AutomaticEntityForm
Configuration through uicfg
```````````````````````````
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 +50,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 +101,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 +622,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'
--- a/web/views/forms.py Wed May 05 18:08:34 2010 +0200
+++ b/web/views/forms.py Wed May 05 18:10:33 2010 +0200
@@ -25,8 +25,8 @@
using a form renderer. No display is actually done here, though you'll find
some attributes of form that are used to control the rendering process.
-Besides the automagic form we'll see later, they are barely two form
-classes in |cubicweb|:
+Besides the automagic form we'll see later, there are roughly two main
+form classes in |cubicweb|:
.. autoclass:: cubicweb.web.views.forms.FieldsForm
.. autoclass:: cubicweb.web.views.forms.EntityFieldsForm
--- a/web/views/sparql.py Wed May 05 18:08:34 2010 +0200
+++ b/web/views/sparql.py Wed May 05 18:10:33 2010 +0200
@@ -64,11 +64,13 @@
self.w(self._cw._('we are not yet ready to handle this query'))
except xy.UnsupportedVocabulary, ex:
self.w(self._cw._('unknown vocabulary:') + u' ' + unicode('ex'))
- if vid == 'sparqlxml':
- url = self._cw.build_url('view', rql=qinfo.finalize(), vid=vid)
- raise Redirect(url)
- rset = self._cw.execute(qinfo.finalize())
- self.wview(vid, rset, 'null')
+ else:
+ if vid == 'sparqlxml':
+ url = self._cw.build_url('view', rql=qinfo.finalize(), vid=vid)
+ raise Redirect(url)
+ print qinfo.finalize()
+ rset = self._cw.execute(*qinfo.finalize())
+ self.wview(vid, rset, 'null')
## sparql resultset views #####################################################
--- a/web/webconfig.py Wed May 05 18:08:34 2010 +0200
+++ b/web/webconfig.py Wed May 05 18:10:33 2010 +0200
@@ -98,7 +98,7 @@
{'type' : 'string',
'default': None,
'help': 'web instance query log file',
- 'group': 'main', 'level': 2,
+ 'group': 'main', 'level': 3,
}),
# web configuration
('https-url',
@@ -112,20 +112,20 @@
'differentiate between http vs https access. For instance: \n'\
'RewriteRule ^/demo/(.*) http://127.0.0.1:8080/https/$1 [L,P]\n'\
'where the cubicweb web server is listening on port 8080.',
- 'group': 'main', 'level': 2,
+ 'group': 'main', 'level': 3,
}),
('auth-mode',
{'type' : 'choice',
'choices' : ('cookie', 'http'),
'default': 'cookie',
'help': 'authentication mode (cookie / http)',
- 'group': 'web', 'level': 1,
+ 'group': 'web', 'level': 3,
}),
('realm',
{'type' : 'string',
'default': 'cubicweb',
'help': 'realm to use on HTTP authentication mode',
- 'group': 'web', 'level': 2,
+ 'group': 'web', 'level': 3,
}),
('http-session-time',
{'type' : 'time',
@@ -144,7 +144,7 @@
'So even if http-session-time is 0 and the user don\'t close his '
'browser, he will have to reauthenticate after this time of '
'inactivity. Default to 24h.',
- 'group': 'web', 'level': 2,
+ 'group': 'web', 'level': 3,
}),
('cleanup-anonymous-session-time',
{'type' : 'time',
@@ -152,14 +152,14 @@
'help': 'Same as cleanup-session-time but specific to anonymous '
'sessions. You can have a much smaller timeout here since it will be '
'transparent to the user. Default to 5min.',
- 'group': 'web', 'level': 2,
+ 'group': 'web', 'level': 3,
}),
('force-html-content-type',
{'type' : 'yn',
'default': False,
'help': 'force text/html content type for your html pages instead of cubicweb user-agent based'\
'deduction of an appropriate content type',
- 'group': 'web', 'level': 2,
+ 'group': 'web', 'level': 3,
}),
('embed-allowed',
{'type' : 'regexp',
@@ -167,7 +167,7 @@
'help': 'regular expression matching URLs that may be embeded. \
leave it blank if you don\'t want the embedding feature, or set it to ".*" \
if you want to allow everything',
- 'group': 'web', 'level': 1,
+ 'group': 'web', 'level': 3,
}),
('submit-mail',
{'type' : 'string',
@@ -197,14 +197,14 @@
'default': join(CubicWebConfiguration.shared_dir(), 'data', 'porkys.ttf'),
'help': 'True type font to use for captcha image generation (you \
must have the python imaging library installed to use captcha)',
- 'group': 'web', 'level': 2,
+ 'group': 'web', 'level': 3,
}),
('captcha-font-size',
{'type' : 'int',
'default': 25,
'help': 'Font size to use for captcha image generation (you must \
have the python imaging library installed to use captcha)',
- 'group': 'web', 'level': 2,
+ 'group': 'web', 'level': 3,
}),
))
--- a/xy.py Wed May 05 18:08:34 2010 +0200
+++ b/xy.py Wed May 05 18:10:33 2010 +0200
@@ -22,8 +22,11 @@
from yams import xy
xy.register_prefix('http://purl.org/dc/elements/1.1/', 'dc')
-xy.register_prefix('http://xmlns.com/foaf/0.1/', 'foaf')
-xy.register_prefix('http://usefulinc.com/ns/doap#', 'doap')
+xy.register_prefix('http://xmlns.com/foaf/0.1/', 'foaf')
+xy.register_prefix('http://usefulinc.com/ns/doap#', 'doap')
+xy.register_prefix('http://rdfs.org/sioc/ns#', 'sioc')
+xy.register_prefix('http://www.w3.org/2002/07/owl#', 'owl')
+xy.register_prefix('http://purl.org/dc/terms/', 'dcterms')
xy.add_equivalence('creation_date', 'dc:date')
xy.add_equivalence('created_by', 'dc:creator')