# HG changeset patch # User Sylvain Thénault # Date 1308329613 -7200 # Node ID 29961a416faadfd2e31390565cdddc1b6b5ffc5b # Parent d58a9d96aad895a59d9b7409701d4c8ecb49799f# Parent d5725a89dac9f6663be87390e28d4c60a9d4ace4 backport stable diff -r d58a9d96aad8 -r 29961a416faa devtools/__init__.py --- a/devtools/__init__.py Fri Jun 17 18:50:13 2011 +0200 +++ b/devtools/__init__.py Fri Jun 17 18:53:33 2011 +0200 @@ -528,11 +528,19 @@ return get_db_helper('postgres') @property - @cached def dbcnx(self): - from cubicweb.server.serverctl import _db_sys_cnx - return _db_sys_cnx(self.system_source, 'CREATE DATABASE and / or USER', - interactive=False) + try: + return self._cnx + except AttributeError: + from cubicweb.server.serverctl import _db_sys_cnx + try: + self._cnx = _db_sys_cnx( + self.system_source, 'CREATE DATABASE and / or USER', + interactive=False) + return self._cnx + except Exception: + self._cnx = None + raise @property @cached @@ -570,17 +578,19 @@ cnx.close() init_repository(self.config, interactive=False) except: - self.dbcnx.rollback() + if self.dbcnx is not None: + self.dbcnx.rollback() print >> sys.stderr, 'building', self.dbname, 'failed' #self._drop(self.dbname) raise def helper_clear_cache(self): - self.dbcnx.commit() - self.dbcnx.close() - clear_cache(self, 'dbcnx') + if self.dbcnx is not None: + self.dbcnx.commit() + self.dbcnx.close() + del self._cnx + clear_cache(self, 'cursor') clear_cache(self, 'helper') - clear_cache(self, 'cursor') def __del__(self): self.helper_clear_cache() diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/admin/config.rst --- a/doc/book/en/admin/config.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/admin/config.rst Fri Jun 17 18:53:33 2011 +0200 @@ -209,6 +209,8 @@ Pyro configuration ------------------ +Pyro name server +~~~~~~~~~~~~~~~~ If you want to use Pyro to access your instance remotely, or to have multi-source or distributed configuration, it is required to have a Pyro name server running on your network. By default it is detected by a broadcast request, but you can @@ -216,9 +218,13 @@ To do so, you need to : +* be sure to have installed it (see :ref:`InstallDependencies`) + * launch the pyro name server with `pyro-nsd start` before starting cubicweb * under debian, edit the file :file:`/etc/default/pyro-nsd` so that the name server pyro will be launched automatically when the machine fire up +Note that you can use the pyro server without a running pyro nameserver. +Refer to `pyro-ns-host` server configuration option for details. diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/admin/index.rst --- a/doc/book/en/admin/index.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/admin/index.rst Fri Jun 17 18:53:33 2011 +0200 @@ -14,6 +14,8 @@ :numbered: setup + setup-windows + config cubicweb-ctl create-instance instance-config diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/annexes/faq.rst --- a/doc/book/en/annexes/faq.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/annexes/faq.rst Fri Jun 17 18:53:33 2011 +0200 @@ -185,12 +185,6 @@ recommended also for other attribute types). By default it expects to generate HTML, so it deals with rich text formating, xml escaping... -How do I translate an msg id defined (and translated) in another cube ? ------------------------------------------------------------------------ - -You should put these translations in the `i18n/static-messages.pot` -file of your own cube. - How to update a database after a schema modification ? ------------------------------------------------------ diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/annexes/rql/debugging.rst --- a/doc/book/en/annexes/rql/debugging.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/annexes/rql/debugging.rst Fri Jun 17 18:53:33 2011 +0200 @@ -8,26 +8,15 @@ Available levels ~~~~~~~~~~~~~~~~ -:DBG_NONE: - no debug information (current mode) - -:DBG_RQL: - rql execution information - -:DBG_SQL: - executed sql +Server debugging flags. They may be combined using binary operators. -:DBG_REPO: - repository events - -:DBG_MS: - multi-sources - -:DBG_MORE: - more verbosity - -:DBG_ALL: - all level enabled +.. autodata:: cubicweb.server.DBG_NONE +.. autodata:: cubicweb.server.DBG_RQL +.. autodata:: cubicweb.server.DBG_SQL +.. autodata:: cubicweb.server.DBG_REPO +.. autodata:: cubicweb.server.DBG_MS +.. autodata:: cubicweb.server.DBG_MORE +.. autodata:: cubicweb.server.DBG_ALL Enable verbose output @@ -40,6 +29,8 @@ from cubicweb import server server.set_debug(server.DBG_RQL|server.DBG_SQL|server.DBG_ALL) +.. autofunction:: cubicweb.server.set_debug + Detect largest RQL queries ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -50,7 +41,5 @@ API ~~~ -.. autofunction:: cubicweb.server.set_debug - .. autoclass:: cubicweb.server.debugged diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/annexes/rql/intro.rst --- a/doc/book/en/annexes/rql/intro.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/annexes/rql/intro.rst Fri Jun 17 18:53:33 2011 +0200 @@ -159,3 +159,4 @@ .. _Datalog: http://en.wikipedia.org/wiki/Datalog .. _intensional: http://en.wikipedia.org/wiki/Intensional_definition .. _extensional: http://en.wikipedia.org/wiki/Extension_(predicate_logic) + diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/annexes/rql/language.rst --- a/doc/book/en/annexes/rql/language.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/annexes/rql/language.rst Fri Jun 17 18:53:33 2011 +0200 @@ -152,6 +152,10 @@ - Aggregate Functions: COUNT, MIN, MAX, AVG, SUM, GROUP_CONCAT +.. note:: + Aggregate functions will return None if there is no result row. + + Having ``````` diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devrepo/vreg.rst --- a/doc/book/en/devrepo/vreg.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/devrepo/vreg.rst Fri Jun 17 18:53:33 2011 +0200 @@ -79,7 +79,7 @@ .. autoclass:: cubicweb.selectors.has_add_permission .. autoclass:: cubicweb.selectors.has_mimetype .. autoclass:: cubicweb.selectors.is_in_state -.. autoclass:: cubicweb.selectors.on_fire_transition +.. autofunction:: cubicweb.selectors.on_fire_transition .. autoclass:: cubicweb.selectors.implements diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devweb/controllers.rst --- a/doc/book/en/devweb/controllers.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/devweb/controllers.rst Fri Jun 17 18:53:33 2011 +0200 @@ -16,11 +16,11 @@ `Browsing`: * the View controller is associated with most browsing actions within a - CubicWeb application: it always instantiates a :ref:`the_main_template` and - lets the ResultSet/Views dispatch system build up the whole content; it - handles :exc:`ObjectNotFound` and :exc:`NoSelectableObject` errors that may - bubble up to its entry point, in an end-user-friendly way (but other - programming errors will slip through) + CubicWeb application: it always instantiates a + :ref:`the_main_template_layout` and lets the ResultSet/Views dispatch system + build up the whole content; it handles :exc:`ObjectNotFound` and + :exc:`NoSelectableObject` errors that may bubble up to its entry point, in an + end-user-friendly way (but other programming errors will slip through) * the JSon controller (same module) provides services for Ajax calls, typically using JSON as a serialization format for input, and diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devweb/httpcaching.rst --- a/doc/book/en/devweb/httpcaching.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/devweb/httpcaching.rst Fri Jun 17 18:53:33 2011 +0200 @@ -1,3 +1,21 @@ HTTP cache management ---------------------- -XXX feedme \ No newline at end of file +===================== + +.. automodule:: cubicweb.web.httpcache + +Cache policies +-------------- +.. autoclass:: cubicweb.web.httpcache.NoHTTPCacheManager +.. autoclass:: cubicweb.web.httpcache.MaxAgeHTTPCacheManager +.. autoclass:: cubicweb.web.httpcache.EtagHTTPCacheManager +.. autoclass:: cubicweb.web.httpcache.EntityHTTPCacheManager + +Exception +--------- +.. autoexception:: cubicweb.web.httpcache.NoEtag + +Helper functions +---------------- +.. autofunction:: cubicweb.web.httpcache.set_http_cache_headers + +.. NOT YET AVAILABLE IN STABLE autofunction:: cubicweb.web.httpcache.lastmodified diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devweb/index.rst --- a/doc/book/en/devweb/index.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/devweb/index.rst Fri Jun 17 18:53:33 2011 +0200 @@ -17,5 +17,6 @@ edition/index facets internationalization -.. property + property httpcaching + resource diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devweb/resource.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/devweb/resource.rst Fri Jun 17 18:53:33 2011 +0200 @@ -0,0 +1,16 @@ +Locate resources +---------------- + +.. automethod:: cubicweb.web.webconfig.WebConfiguration.locate_resource +.. automethod:: cubicweb.web.webconfig.WebConfiguration.locate_doc_file +.. automethod:: cubicweb.web.webconfig.WebConfiguration.locate_all_files + +Static files handling +--------------------- + +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_directory +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_exists +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_open +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_add +.. automethod:: cubicweb.web.webconfig.WebConfiguration.static_file_del + diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devweb/views/idownloadable.rst --- a/doc/book/en/devweb/views/idownloadable.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/devweb/views/idownloadable.rst Fri Jun 17 18:53:33 2011 +0200 @@ -1,5 +1,23 @@ -The 'download' view -------------------- +The 'download' views +==================== + +.. automodule:: cubicweb.web.views.idownloadable + +Components +---------- + +.. autoclass:: cubicweb.web.views.idownloadable.DownloadBox -(:mod:`cubicweb.web.views.idownloadable`) +Download views +-------------- +.. autoclass:: cubicweb.web.views.idownloadable.DownloadView +.. autoclass:: cubicweb.web.views.idownloadable.DownloadLinkView +.. autoclass:: cubicweb.web.views.idownloadable.IDownloadablePrimaryView +.. autoclass:: cubicweb.web.views.idownloadable.IDownloadableLineView + +Embedded views +-------------- + +.. autoclass:: cubicweb.web.views.idownloadable.ImageView +.. autoclass:: cubicweb.web.views.idownloadable.EHTMLView diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devweb/views/index.rst --- a/doc/book/en/devweb/views/index.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/devweb/views/index.rst Fri Jun 17 18:53:33 2011 +0200 @@ -25,7 +25,7 @@ urlpublish breadcrumbs -.. wdoc + idownloadable + wdoc .. embedding -.. idownloadable diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/devweb/views/wdoc.rst --- a/doc/book/en/devweb/views/wdoc.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/devweb/views/wdoc.rst Fri Jun 17 18:53:33 2011 +0200 @@ -1,9 +1,17 @@ .. -*- coding: utf-8 -*- Online documentation system ---------------------------- +=========================== + +.. automodule:: cubicweb.web.views.wdoc -(:mod:`cubicweb.web.views.wdoc`) +Help views +---------- +.. autoclass:: cubicweb.web.views.wdoc.InlineHelpView +.. autoclass:: cubicweb.web.views.wdoc.ChangeLogView -XXX describe the on-line documentation system - +Actions +------- +.. autoclass:: cubicweb.web.views.wdoc.HelpAction +.. autoclass:: cubicweb.web.views.wdoc.ChangeLogAction +.. autoclass:: cubicweb.web.views.wdoc.AboutAction diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/tutorials/advanced/part05_ui-advanced.rst --- a/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/tutorials/advanced/part05_ui-advanced.rst Fri Jun 17 18:53:33 2011 +0200 @@ -3,6 +3,8 @@ We'll now see how to benefit from features introduced in 3.9 and 3.10 releases of CubicWeb +.. _uiprops: + Step 1: tired of the default look? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff -r d58a9d96aad8 -r 29961a416faa doc/book/en/tutorials/tools/windmill.rst --- a/doc/book/en/tutorials/tools/windmill.rst Fri Jun 17 18:50:13 2011 +0200 +++ b/doc/book/en/tutorials/tools/windmill.rst Fri Jun 17 18:53:33 2011 +0200 @@ -23,23 +23,21 @@ environment, take a look to the `virtualenv `_ project as well):: - pip install windmill - curl -O http://github.com/windmill/windmill/tarball/master + $ pip install windmill + $ curl -O http://github.com/windmill/windmill/tarball/master However, the Windmill project doesn't release frequently. Our recommandation is -to used the last snapshot of the Git repository: - -.. sourcecode:: bash +to used the last snapshot of the Git repository:: - git clone git://github.com/windmill/windmill.git HEAD - cd windmill - python setup.py develop + $ git clone git://github.com/windmill/windmill.git HEAD + $ cd windmill + $ python setup.py develop Install instructions are `available `_. Be sure to have the windmill module in your PYTHONPATH afterwards:: - python -c "import windmill" + $ python -c "import windmill" X dummy ------- @@ -60,9 +58,9 @@ *From: http://www.x.org/wiki/XorgTesting* -Then, you can run the X server with the following command : +Then, you can run the X server with the following command :: - /usr/bin/X11/Xvfb :1 -ac -screen 0 1280x1024x8 -fbdir /tmp + $ /usr/bin/X11/Xvfb :1 -ac -screen 0 1280x1024x8 -fbdir /tmp Windmill usage @@ -82,13 +80,15 @@ If you are using firefox as client, consider the "firebug" option. -If you have a running instance, you can refine the test by the *loadtest* windmill option: +If you have a running instance, you can refine the test by the *loadtest* windmill option:: - windmill -m firebug loadtest= + $ windmill -m firebug loadtest= -Or use the internal windmill shell to explore available commands: +Or use the internal windmill shell to explore available commands:: - windmill -m firebug shell + $ windmill -m firebug shell + +And enter python commands: .. sourcecode:: python @@ -125,7 +125,7 @@ To run your test series:: - % pytest test/test_windmill.py + $ pytest test/test_windmill.py By default, CubicWeb will use **firefox** as the default browser and will try to run test instance server on localhost. In the general case, You've no need @@ -144,6 +144,8 @@ Examples: +.. sourcecode:: python + browser = 'firefox' test_dir = osp.join(__file__, 'windmill') edit_test = False @@ -162,7 +164,7 @@ For instance, CubicWeb framework windmill tests can be manually run by:: - % pytest web/test/test_windmill.py + $ pytest web/test/test_windmill.py Edit your tests --------------- @@ -172,7 +174,7 @@ But if you are using `pytest` as test runner, use the `-i` option directly. The test series will be loaded and you can run assertions step-by-step:: - % pytest -i test/test_windmill.py + $ pytest -i test/test_windmill.py In this case, the `firebug` extension will be loaded automatically for you. diff -r d58a9d96aad8 -r 29961a416faa hooks/syncsources.py --- a/hooks/syncsources.py Fri Jun 17 18:50:13 2011 +0200 +++ b/hooks/syncsources.py Fri Jun 17 18:53:33 2011 +0200 @@ -136,7 +136,7 @@ if not session.deleted_in_transaction(schemacfg.eid): source.add_schema_config(schemacfg, checkonly=checkonly) elif session.deleted_in_transaction(schemacfg.eid): - source.delete_schema_config(schemacfg, checkonly=checkonly) + source.del_schema_config(schemacfg, checkonly=checkonly) else: source.update_schema_config(schemacfg, checkonly=checkonly) @@ -164,5 +164,4 @@ def __call__(self): SourceMappingChangedOp.get_instance(self._cw).add_data( (self._cw.entity_from_eid(self.eidfrom), - self._cw.entity_from_eid(self.eidto)) ) - + self._cw.entity_from_eid(self.eidto).repo_source) ) diff -r d58a9d96aad8 -r 29961a416faa rqlrewrite.py --- a/rqlrewrite.py Fri Jun 17 18:50:13 2011 +0200 +++ b/rqlrewrite.py Fri Jun 17 18:53:33 2011 +0200 @@ -20,12 +20,16 @@ This is used for instance for read security checking in the repository. """ +from __future__ import with_statement __docformat__ = "restructuredtext en" from rql import nodes as n, stmts, TypeResolverException from rql.utils import common_parent + from yams import BadSchemaDefinition + +from logilab.common import tempattr from logilab.common.graph import has_path from cubicweb import Unauthorized, typed_eid @@ -156,7 +160,6 @@ self.exists_snippet = {} self.pending_keys = [] self.existingvars = existingvars - self._insert_scope = None # we have to annotate the rqlst before inserting snippets, even though # we'll have to redo it latter self.annotate(select) @@ -193,6 +196,7 @@ self.varmap = varmap self.revvarmap = {} self.varinfos = [] + self._insert_scope = None for i, (selectvar, snippetvar) in enumerate(varmap): assert snippetvar in 'SOX' self.revvarmap[snippetvar] = (selectvar, i) @@ -229,7 +233,7 @@ except Unsupported: continue inserted = True - if new is not None: + if new is not None and self._insert_scope is None: self.exists_snippet[rqlexpr] = new parent = parent or new else: @@ -263,11 +267,12 @@ insert_scope = common_parent(scope, insert_scope) else: insert_scope = self._insert_scope - if any(vi.get('stinfo', {}).get('optrelations') for vi in self.varinfos): + if self._insert_scope is None and any(vi.get('stinfo', {}).get('optrelations') + for vi in self.varinfos): assert parent is None self._insert_scope = self.snippet_subquery(varmap, new) self.insert_pending() - self._insert_scope = None + #self._insert_scope = None return if not isinstance(new, (n.Exists, n.Not)): new = n.Exists(new) @@ -283,15 +288,14 @@ except Unsupported: # some solutions have been lost, can't apply this rql expr if parent is None: - self.select.remove_node(new, undefine=True) + self.current_statement().remove_node(new, undefine=True) else: parent.parent.replace(or_, or_.children[0]) self._cleanup_inserted(new) raise else: - self._insert_scope = new - self.insert_pending() - self._insert_scope = None + with tempattr(self, '_insert_scope', new): + self.insert_pending() return new self.insert_pending() @@ -303,6 +307,7 @@ recomputed, we have to insert snippet defined for of entity types taken by X """ + stmt = self.current_statement() while self.pending_keys: key, action = self.pending_keys.pop() try: @@ -313,12 +318,12 @@ except KeyError: # variable isn't used anywhere else, we can't insert security raise Unauthorized() - ptypes = self.select.defined_vars[varname].stinfo['possibletypes'] + ptypes = stmt.defined_vars[varname].stinfo['possibletypes'] if len(ptypes) > 1: # XXX dunno how to handle this self.session.error( 'cant check security of %s, ambigous type for %s in %s', - self.select, varname, key[0]) # key[0] == the rql expression + stmt, varname, key[0]) # key[0] == the rql expression raise Unauthorized() etype = iter(ptypes).next() eschema = self.schema.eschema(etype) @@ -499,17 +504,18 @@ self.rewritten[key] = term.name def _get_varname_or_term(self, vname): + stmt = self.current_statement() if vname == 'U': + stmt = self.select if self.u_varname is None: - select = self.select - self.u_varname = select.allocate_varname() + self.u_varname = stmt.allocate_varname() # generate an identifier for the substitution - argname = select.allocate_varname() + argname = stmt.allocate_varname() while argname in self.kwargs: - argname = select.allocate_varname() + argname = stmt.allocate_varname() # insert "U eid %(u)s" - select.add_constant_restriction( - select.get_variable(self.u_varname), + stmt.add_constant_restriction( + stmt.get_variable(self.u_varname), 'eid', unicode(argname), 'Substitute') self.kwargs[argname] = self.session.user.eid return self.u_varname @@ -517,7 +523,7 @@ try: return self.rewritten[key] except KeyError: - self.rewritten[key] = newvname = self.select.allocate_varname() + self.rewritten[key] = newvname = stmt.allocate_varname() return newvname # visitor methods ########################################################## @@ -625,14 +631,20 @@ def visit_variableref(self, node): """get the sql name for a variable reference""" + stmt = self.current_statement() if node.name in self.revvarmap: selectvar, index = self.revvarmap[node.name] vi = self.varinfos[index] if vi.get('const') is not None: return n.Constant(vi['const'], 'Int') # XXX gae - return n.VariableRef(self.select.get_variable(selectvar)) + return n.VariableRef(stmt.get_variable(selectvar)) vname_or_term = self._get_varname_or_term(node.name) if isinstance(vname_or_term, basestring): - return n.VariableRef(self.select.get_variable(vname_or_term)) + return n.VariableRef(stmt.get_variable(vname_or_term)) # shared term - return vname_or_term.copy(self.select) + return vname_or_term.copy(stmt) + + def current_statement(self): + if self._insert_scope is None: + return self.select + return self._insert_scope.stmt diff -r d58a9d96aad8 -r 29961a416faa selectors.py --- a/selectors.py Fri Jun 17 18:50:13 2011 +0200 +++ b/selectors.py Fri Jun 17 18:53:33 2011 +0200 @@ -1252,15 +1252,18 @@ def on_fire_transition(etype, tr_name, from_state_name=None): """Return 1 when entity of the type `etype` is going through transition of - the name `tr_name`. If `from_state_name` is specified, this selector will - also check the incoming state. + the name `tr_name`. + + If `from_state_name` is specified, this selector will also check the + incoming state. You should use this selector on 'after_add_entity' hook, since it's actually looking for addition of `TrInfo` entities. Hence in the hook, `self.entity` will reference the matching `TrInfo` entity, allowing to get all the transition details (including the entity to which is applied the transition - but also its original state, transition, destination state, user...). See - :class:`cubicweb.entities.wfobjs.TrInfo` for more information. + but also its original state, transition, destination state, user...). + + See :class:`cubicweb.entities.wfobjs.TrInfo` for more information. """ def match_etype_and_transition(trinfo): # take care trinfo.transition is None when calling change_state diff -r d58a9d96aad8 -r 29961a416faa server/__init__.py --- a/server/__init__.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/__init__.py Fri Jun 17 18:53:33 2011 +0200 @@ -18,7 +18,7 @@ """Server subcube of cubicweb : defines objects used only on the server (repository) side -This module contains functions to initialize a new repository. +The server module contains functions to initialize a new repository. """ from __future__ import with_statement @@ -39,14 +39,23 @@ # server-side debugging ######################################################### # server debugging flags. They may be combined using binary operators. -DBG_NONE = 0 # no debug information -DBG_RQL = 1 # rql execution information -DBG_SQL = 2 # executed sql -DBG_REPO = 4 # repository events -DBG_MS = 8 # multi-sources -DBG_MORE = 16 # more verbosity -DBG_ALL = 1 + 2 + 4 + 8 + 16 -# current debug mode + +#:no debug information +DBG_NONE = 0 #: no debug information +#: rql execution information +DBG_RQL = 1 +#: executed sql +DBG_SQL = 2 +#: repository events +DBG_REPO = 4 +#: multi-sources +DBG_MS = 8 +#: more verbosity +DBG_MORE = 16 +#: all level enabled +DBG_ALL = DBG_RQL + DBG_SQL + DBG_REPO + DBG_MS + DBG_MORE + +#: current debug mode DEBUG = 0 def set_debug(debugmode): diff -r d58a9d96aad8 -r 29961a416faa server/mssteps.py --- a/server/mssteps.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/mssteps.py Fri Jun 17 18:53:33 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -22,6 +22,7 @@ * each step has is own members (this is not necessarily bad, but a bit messy for now) """ +from __future__ import with_statement __docformat__ = "restructuredtext en" @@ -32,25 +33,30 @@ AGGR_TRANSFORMS = {'COUNT':'SUM', 'MIN':'MIN', 'MAX':'MAX', 'SUM': 'SUM'} -def remove_clauses(union, keepgroup): - clauses = [] - for select in union.children: - if keepgroup: - having, orderby = select.having, select.orderby - select.having, select.orderby = (), () - clauses.append( (having, orderby) ) - else: - groupby, having, orderby = select.groupby, select.having, select.orderby - select.groupby, select.having, select.orderby = (), (), () - clauses.append( (groupby, having, orderby) ) - return clauses +class remove_and_restore_clauses(object): + def __init__(self, union, keepgroup): + self.union = union + self.keepgroup = keepgroup + self.clauses = None -def restore_clauses(union, keepgroup, clauses): - for i, select in enumerate(union.children): - if keepgroup: - select.having, select.orderby = clauses[i] - else: - select.groupby, select.having, select.orderby = clauses[i] + def __enter__(self): + self.clauses = clauses = [] + for select in self.union.children: + if self.keepgroup: + having, orderby = select.having, select.orderby + select.having, select.orderby = (), () + clauses.append( (having, orderby) ) + else: + groupby, having, orderby = select.groupby, select.having, select.orderby + select.groupby, select.having, select.orderby = (), (), () + clauses.append( (groupby, having, orderby) ) + + def __exit__(self, exctype, exc, traceback): + for i, select in enumerate(self.union.children): + if self.keepgroup: + select.having, select.orderby = self.clauses[i] + else: + select.groupby, select.having, select.orderby = self.clauses[i] class FetchStep(OneFetchStep): @@ -94,29 +100,24 @@ plan = self.plan plan.create_temp_table(self.table) union = self.union - # XXX 2.5 use "with" - clauses = remove_clauses(union, self.keepgroup) - for source in self.sources: - source.flying_insert(self.table, plan.session, union, plan.args, - self.inputmap) - restore_clauses(union, self.keepgroup, clauses) + with remove_and_restore_clauses(union, self.keepgroup): + for source in self.sources: + source.flying_insert(self.table, plan.session, union, plan.args, + self.inputmap) def mytest_repr(self): """return a representation of this step suitable for test""" - clauses = remove_clauses(self.union, self.keepgroup) - try: - inputmap = varmap_test_repr(self.inputmap, self.plan.tablesinorder) - outputmap = varmap_test_repr(self.outputmap, self.plan.tablesinorder) - except AttributeError: - inputmap = self.inputmap - outputmap = self.outputmap - try: + with remove_and_restore_clauses(self.union, self.keepgroup): + try: + inputmap = varmap_test_repr(self.inputmap, self.plan.tablesinorder) + outputmap = varmap_test_repr(self.outputmap, self.plan.tablesinorder) + except AttributeError: + inputmap = self.inputmap + outputmap = self.outputmap return (self.__class__.__name__, - sorted((r.as_string(kwargs=self.plan.args), r.solutions) - for r in self.union.children), - sorted(self.sources), inputmap, outputmap) - finally: - restore_clauses(self.union, self.keepgroup, clauses) + sorted((r.as_string(kwargs=self.plan.args), r.solutions) + for r in self.union.children), + sorted(self.sources), inputmap, outputmap) class AggrStep(LimitOffsetMixIn, Step): diff -r d58a9d96aad8 -r 29961a416faa server/rqlannotation.py --- a/server/rqlannotation.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/rqlannotation.py Fri Jun 17 18:53:33 2011 +0200 @@ -271,9 +271,17 @@ return has_text_query def is_ambiguous(self, var): - # ignore has_text relation - if len([rel for rel in var.stinfo['relations'] - if rel.scope is var.scope and rel.r_type == 'has_text']) == 1: + # ignore has_text relation when we know it will be used as principal. + # This is expected by the rql2sql generator which will use the `entities` + # table to filter out by type if necessary, This optimisation is very + # interesting in multi-sources cases, as it may avoid a costly query + # on sources to get all entities of a given type to achieve this, while + # we have all the necessary information. + root = var.stmt.root # Union node + # rel.scope -> Select or Exists node, so add .parent to get Union from + # Select node + rels = [rel for rel in var.stinfo['relations'] if rel.scope.parent is root] + if len(rels) == 1 and rels[0].r_type == 'has_text': return False try: data = var.stmt._deamb_data diff -r d58a9d96aad8 -r 29961a416faa server/session.py diff -r d58a9d96aad8 -r 29961a416faa server/sources/native.py --- a/server/sources/native.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/sources/native.py Fri Jun 17 18:53:33 2011 +0200 @@ -165,6 +165,15 @@ class UndoException(Exception): """something went wrong during undoing""" + def __unicode__(self): + """Called by the unicode builtin; should return a Unicode object + + Type of UndoException message must be `unicode` by design in CubicWeb. + + .. warning:: + This method is not available in python2.5""" + assert isinstance(self.message, unicode) + return self.message def _undo_check_relation_target(tentity, rdef, role): """check linked entity has not been redirected for this relation""" diff -r d58a9d96aad8 -r 29961a416faa server/sources/pyrorql.py --- a/server/sources/pyrorql.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/sources/pyrorql.py Fri Jun 17 18:53:33 2011 +0200 @@ -234,6 +234,7 @@ etype, dexturi, dextid = cnx.describe(extid) if dexturi == 'system' or not ( dexturi in self.repo.sources_by_uri or self._skip_externals): + assert etype in self.support_entities, etype eid = self.repo.extid2eid(self, str(extid), etype, session) if eid > 0: return eid, True diff -r d58a9d96aad8 -r 29961a416faa server/test/unittest_querier.py --- a/server/test/unittest_querier.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/test/unittest_querier.py Fri Jun 17 18:53:33 2011 +0200 @@ -1460,5 +1460,14 @@ rset = self.execute('Any X,Y WHERE X nom XD, Y nom XD, X eid Z, Y eid > Z') self.assertEqual(rset.rows, [[peid1, peid2]]) + def test_nonregr_has_text_ambiguity_1(self): + peid = self.execute("INSERT CWUser X: X login 'bidule', X upassword 'bidule', X in_group G WHERE G name 'users'")[0][0] + aeid = self.execute("INSERT Affaire X: X ref 'bidule'")[0][0] + self.commit() + rset = self.execute('Any X WHERE X is CWUser, X has_text "bidule"') + self.assertEqual(rset.rows, [[peid]]) + rset = self.execute('Any X WHERE X is CWUser, X has_text "bidule", X in_state S, S name SN') + self.assertEqual(rset.rows, [[peid]]) + if __name__ == '__main__': unittest_main() diff -r d58a9d96aad8 -r 29961a416faa server/test/unittest_rqlannotation.py --- a/server/test/unittest_rqlannotation.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/test/unittest_rqlannotation.py Fri Jun 17 18:53:33 2011 +0200 @@ -18,13 +18,16 @@ # with CubicWeb. If not, see . """unit tests for modules cubicweb.server.rqlannotation""" -from cubicweb.devtools import init_test_database +from cubicweb.devtools import TestServerConfiguration, get_test_db_handler from cubicweb.devtools.repotest import BaseQuerierTC def setUpModule(*args): + handler = get_test_db_handler(TestServerConfiguration( + 'data2', apphome=SQLGenAnnotatorTC.datadir)) + handler.build_db_cache() global repo, cnx - repo, cnx = init_test_database(apphome=SQLGenAnnotatorTC.datadir) + repo, cnx = handler.get_repo_and_cnx() def tearDownModule(*args): global repo, cnx @@ -330,6 +333,13 @@ self.assertEqual(rqlst.defined_vars['N']._q_invariant, False) self.assertEqual(rqlst.defined_vars['F']._q_invariant, True) + def test_nonregr_ambiguity_2(self): + rqlst = self._prepare('Any S,SN WHERE X has_text "tot", X in_state S, S name SN, X is CWUser') + # X use has_text but should not be invariant as ambiguous, and has_text + # may not be its principal + self.assertEqual(rqlst.defined_vars['X']._q_invariant, False) + self.assertEqual(rqlst.defined_vars['S']._q_invariant, False) + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r d58a9d96aad8 -r 29961a416faa server/test/unittest_undo.py --- a/server/test/unittest_undo.py Fri Jun 17 18:50:13 2011 +0200 +++ b/server/test/unittest_undo.py Fri Jun 17 18:53:33 2011 +0200 @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # @@ -21,9 +22,11 @@ from cubicweb.devtools.testlib import CubicWebTC from cubicweb.transaction import * +from cubicweb.server.sources.native import UndoException + + class UndoableTransactionTC(CubicWebTC): - def setup_database(self): req = self.request() self.session.undo_actions = set('CUDAR') @@ -285,6 +288,15 @@ # test implicit 'replacement' of an inlined relation + +class UndoExceptionInUnicode(CubicWebTC): + + # problem occurs in string manipulation for python < 2.6 + def test___unicode__method(self): + u = UndoException(u"voilà") + self.assertIsInstance(unicode(u), unicode) + + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r d58a9d96aad8 -r 29961a416faa test/unittest_rqlrewrite.py --- a/test/unittest_rqlrewrite.py Fri Jun 17 18:50:13 2011 +0200 +++ b/test/unittest_rqlrewrite.py Fri Jun 17 18:53:33 2011 +0200 @@ -82,8 +82,9 @@ for vref in node.iget_nodes(nodes.VariableRef): vrefmap.setdefault(vref.name, set()).add(vref) for var in node.defined_vars.itervalues(): - assert not (var.stinfo['references'] ^ vrefmap[var.name]) - assert (var.stinfo['references']) + assert var.stinfo['references'] + assert not (var.stinfo['references'] ^ vrefmap[var.name]), (node.as_string(), var.stinfo['references'], vrefmap[var.name]) + class RQLRewriteTC(TestCase): """a faire: @@ -95,10 +96,10 @@ """ def test_base_var(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' + constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Card C') - rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), u"Any C WHERE C is Card, B eid %(D)s, " "EXISTS(C in_state A, B in_group E, F require_state A, " @@ -130,27 +131,31 @@ "E in_state D, D name 'subscribed'), D is State, E is CWUser)") def test_simplified_rqlst(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' + constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Any 2') # this is the simplified rql st for Any X WHERE X eid 12 - rewrite(rqlst, {('2', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('2', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), u"Any 2 WHERE B eid %(C)s, " "EXISTS(2 in_state A, B in_group D, E require_state A, " "E name 'read', E require_group D, A is State, D is CWGroup, E is CWPermission)") - def test_optional_var_base(self): - card_constraint = ('X in_state S, U in_group G, P require_state S,' + def test_optional_var_base_1(self): + constraint = ('X in_state S, U in_group G, P require_state S,' 'P name "read", P require_group G') rqlst = parse('Any A,C WHERE A documented_by C?') - rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), "Any A,C WHERE A documented_by C?, A is Affaire " "WITH C BEING " "(Any C WHERE EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', " "G require_group F), D eid %(A)s, C is Card)") + + def test_optional_var_base_2(self): + constraint = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') - rewrite(rqlst, {('C', 'X'): (card_constraint,)}, {}) + rewrite(rqlst, {('C', 'X'): (constraint,)}, {}) self.failUnlessEqual(rqlst.as_string(), "Any A,C,T WHERE A documented_by C?, A is Affaire " "WITH C,T BEING " @@ -158,6 +163,19 @@ "G require_state B, G name 'read', G require_group F), " "D eid %(A)s, C is Card)") + def test_optional_var_base_3(self): + constraint1 = ('X in_state S, U in_group G, P require_state S,' + 'P name "read", P require_group G') + constraint2 = 'X in_state S, S name "public"' + rqlst = parse('Any A,C,T WHERE A documented_by C?, C title T') + rewrite(rqlst, {('C', 'X'): (constraint1, constraint2)}, {}) + self.failUnlessEqual(rqlst.as_string(), + "Any A,C,T WHERE A documented_by C?, A is Affaire " + "WITH C,T BEING (Any C,T WHERE C title T, " + "EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F), " + "D eid %(A)s, C is Card, " + "EXISTS(C in_state E, E name 'public'))") + def test_optional_var_inlined(self): c1 = ('X require_permission P') c2 = ('X inlined_card O, O require_permission P') diff -r d58a9d96aad8 -r 29961a416faa web/component.py --- a/web/component.py Fri Jun 17 18:50:13 2011 +0200 +++ b/web/component.py Fri Jun 17 18:53:33 2011 +0200 @@ -440,7 +440,6 @@ params.pop('view', None) params.pop('entity', None) form = params.pop('formparams', {}) - form['pageid'] = self._cw.pageid if entity.has_eid(): eid = entity.eid else: diff -r d58a9d96aad8 -r 29961a416faa web/data/cubicweb.calendar.js --- a/web/data/cubicweb.calendar.js Fri Jun 17 18:50:13 2011 +0200 +++ b/web/data/cubicweb.calendar.js Fri Jun 17 18:53:33 2011 +0200 @@ -15,9 +15,9 @@ /** * .. class:: Calendar * - * Calendar (graphical) widget + * Calendar (graphical) widget * - * public methods are : + * public methods are : * * __init__ : * :attr:`containerId`: the DOM node's ID where the calendar will be displayed @@ -74,7 +74,7 @@ /** * .. function:: Calendar._uppercaseFirst(s) * - * utility function (the only use for now is inside the calendar) + * utility function (the only use for now is inside the calendar) */ this._uppercaseFirst = function(s) { return s.charAt(0).toUpperCase(); @@ -83,7 +83,7 @@ /** * .. function:: Calendar._domForRows(rows) * - * accepts the cells data and builds the corresponding TR nodes + * accepts the cells data and builds the corresponding TR nodes * * * `rows`, a list of list of couples (daynum, cssprops) */ @@ -98,7 +98,7 @@ /** * .. function:: Calendar._headdisplay(row) * - * builds the calendar headers + * builds the calendar headers */ this._headdisplay = function(row) { if (_CAL_HEADER) { @@ -224,13 +224,17 @@ this.hide); // connect(inputId, 'onfocus', this, 'hide'); }; -// keep track of each calendar created +/** + * .. data:: Calendar.REGISTRY + * + * keep track of each calendar created + */ Calendar.REGISTRY = {}; /** * .. function:: toggleCalendar(containerId, inputId, year, month) * - * popup / hide calendar associated to `containerId` + * popup / hide calendar associated to `containerId` */ function toggleCalendar(containerId, inputId, year, month) { var cal = Calendar.REGISTRY[containerId]; @@ -251,7 +255,7 @@ /** * .. function:: toggleNextMonth(containerId) * - * ask for next month to calendar displayed in `containerId` + * ask for next month to calendar displayed in `containerId` */ function toggleNextMonth(containerId) { var cal = Calendar.REGISTRY[containerId]; @@ -261,7 +265,7 @@ /** * .. function:: togglePreviousMonth(containerId) * - * ask for previous month to calendar displayed in `containerId` + * ask for previous month to calendar displayed in `containerId` */ function togglePreviousMonth(containerId) { var cal = Calendar.REGISTRY[containerId]; @@ -271,7 +275,7 @@ /** * .. function:: dateSelected(cell, containerId) * - * Callback called when the user clicked on a cell in the popup calendar + * callback called when the user clicked on a cell in the popup calendar */ function dateSelected(cell, containerId) { var cal = Calendar.REGISTRY[containerId]; diff -r d58a9d96aad8 -r 29961a416faa web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Fri Jun 17 18:50:13 2011 +0200 +++ b/web/data/cubicweb.edition.js Fri Jun 17 18:53:33 2011 +0200 @@ -583,6 +583,7 @@ * around the corresponding input fields. */ function validateForm(formid, action, onsuccess, onfailure) { + freezeFormButtons(formid); try { var zipped = cw.utils.formContents(formid); var args = ajaxFuncArgs('validate_form', null, action, zipped[0], zipped[1]); diff -r d58a9d96aad8 -r 29961a416faa web/data/cubicweb.timeline-bundle.js --- a/web/data/cubicweb.timeline-bundle.js Fri Jun 17 18:50:13 2011 +0200 +++ b/web/data/cubicweb.timeline-bundle.js Fri Jun 17 18:53:33 2011 +0200 @@ -1,10 +1,15 @@ +/** + * This file contains timeline utilities + * :organization: Logilab + */ + var SimileAjax_urlPrefix = baseuri() + 'data/'; var Timeline_urlPrefix = baseuri() + 'data/'; /* * Simile Ajax API * - * Include this file in your HTML file as follows: + * Include this file in your HTML file as follows:: * * * diff -r d58a9d96aad8 -r 29961a416faa web/request.py --- a/web/request.py Fri Jun 17 18:50:13 2011 +0200 +++ b/web/request.py Fri Jun 17 18:53:33 2011 +0200 @@ -624,8 +624,10 @@ extraparams.setdefault('fname', 'view') url = self.build_url('json', **extraparams) cbname = build_cb_uid(url[:50]) + # think to propagate pageid. XXX see https://www.cubicweb.org/ticket/1753121 jscode = 'function %s() { $("#%s").%s; }' % ( - cbname, nodeid, js.loadxhtml(url, None, 'get', replacemode)) + cbname, nodeid, js.loadxhtml(url, {'pageid': self.pageid}, + 'get', replacemode)) self.html_headers.add_post_inline_script(jscode) return "javascript: %s()" % cbname diff -r d58a9d96aad8 -r 29961a416faa web/test/unittest_web.py --- a/web/test/unittest_web.py Fri Jun 17 18:50:13 2011 +0200 +++ b/web/test/unittest_web.py Fri Jun 17 18:53:33 2011 +0200 @@ -38,7 +38,7 @@ self.failUnless(url.endswith('()')) cbname = url.split()[1][:-2] self.assertMultiLineEqual( - 'function %s() { $("#foo").loadxhtml("http://testing.fr/cubicweb/json?%s",null,"get","replace"); }' % (cbname, qs), + 'function %s() { $("#foo").loadxhtml("http://testing.fr/cubicweb/json?%s",{"pageid": "%s"},"get","replace"); }' % (cbname, qs, req.pageid), req.html_headers.post_inlined_scripts[0]) if __name__ == '__main__': diff -r d58a9d96aad8 -r 29961a416faa web/views/basecontrollers.py --- a/web/views/basecontrollers.py Fri Jun 17 18:50:13 2011 +0200 +++ b/web/views/basecontrollers.py Fri Jun 17 18:53:33 2011 +0200 @@ -604,7 +604,7 @@ if not errors: self.redirect() return self._cw._('some errors occurred:') + self._cw.view( - 'pyvalist', pyvalue=errors) + 'pyvallist', pyvalue=errors) def redirect(self): req = self._cw diff -r d58a9d96aad8 -r 29961a416faa web/views/cwsources.py --- a/web/views/cwsources.py Fri Jun 17 18:50:13 2011 +0200 +++ b/web/views/cwsources.py Fri Jun 17 18:53:33 2011 +0200 @@ -27,7 +27,7 @@ from cubicweb.selectors import is_instance, score_entity, match_user_groups from cubicweb.view import EntityView, StartupView from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name -from cubicweb.web import uicfg +from cubicweb.web import uicfg, formwidgets as wdgs from cubicweb.web.views import tabs, actions @@ -35,6 +35,12 @@ _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_schema', '*'), False) _abaa.tag_object_of(('CWSourceSchemaConfig', 'cw_for_source', '*'), False) +_afs = uicfg.autoform_section +_afs.tag_attribute(('CWSource', 'synchronizing'), 'main', 'hidden') +_afs.tag_object_of(('*', 'cw_for_source', 'CWSource'), 'main', 'hidden') +_affk = uicfg.autoform_field_kwargs +_affk.tag_attribute(('CWSource', 'parser'), {'widget': wdgs.TextInput}) + # source primary views ######################################################### _pvs = uicfg.primaryview_section diff -r d58a9d96aad8 -r 29961a416faa web/views/idownloadable.py --- a/web/views/idownloadable.py Fri Jun 17 18:50:13 2011 +0200 +++ b/web/views/idownloadable.py Fri Jun 17 18:53:33 2011 +0200 @@ -15,8 +15,10 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""Specific views for entities adapting to IDownloadable""" - +""" +Specific views for entities adapting to IDownloadable +===================================================== +""" __docformat__ = "restructuredtext en" _ = unicode @@ -50,6 +52,7 @@ class DownloadBox(component.EntityCtxComponent): + """add download box""" __regid__ = 'download_box' # no download box for images __select__ = (component.EntityCtxComponent.__select__ & adaptable('IDownloadable') & ~has_mimetype('image/')) @@ -71,7 +74,9 @@ class DownloadView(EntityView): - """this view is replacing the deprecated 'download' controller and allow + """download view + + this view is replacing the deprecated 'download' controller and allow downloading of entities providing the necessary interface """ __regid__ = 'download' @@ -199,6 +204,7 @@ class ImageView(AbstractEmbeddedView): + """image embedded view""" __regid__ = 'image' __select__ = has_mimetype('image/') @@ -207,6 +213,7 @@ class EHTMLView(AbstractEmbeddedView): + """html embedded view""" __regid__ = 'ehtml' __select__ = has_mimetype('text/html') diff -r d58a9d96aad8 -r 29961a416faa web/views/rdf.py --- a/web/views/rdf.py Fri Jun 17 18:50:13 2011 +0200 +++ b/web/views/rdf.py Fri Jun 17 18:53:33 2011 +0200 @@ -59,6 +59,9 @@ self.entity2graph(graph, entity) self.w(graph.serialize().decode('utf-8')) + def entity_call(self, entity): + self.call() + def entity2graph(self, graph, entity): cwuri = URIRef(entity.cwuri) add = graph.add