# HG changeset patch # User Aurelien Campeas # Date 1247743813 -7200 # Node ID caad2367d940c2b9aa974f6312a74a5992b68013 # Parent ea1a44e4ad62958e8d52a1b089d4c113b417c0ed# Parent 5d980ba57632940494ec48d7da04b04fcd0b6c1d backport stable branch diff -r ea1a44e4ad62 -r caad2367d940 common/uilib.py --- a/common/uilib.py Wed Jul 15 09:45:13 2009 +0200 +++ b/common/uilib.py Thu Jul 16 13:30:13 2009 +0200 @@ -15,7 +15,7 @@ from urllib import quote as urlquote from StringIO import StringIO -from logilab.mtconverter import html_escape, html_unescape +from logilab.mtconverter import xml_escape, html_unescape from cubicweb.utils import ustrftime @@ -66,7 +66,7 @@ except ImportError: def rest_publish(entity, data): """default behaviour if docutils was not found""" - return html_escape(data) + return xml_escape(data) TAG_PROG = re.compile(r'', re.U) def remove_html_tags(text): @@ -108,7 +108,7 @@ if len(text_nohtml) <= length: return text # else if un-tagged text is too long, cut it - return html_escape(text_nohtml[:length] + u'...') + return xml_escape(text_nohtml[:length] + u'...') fallback_safe_cut = safe_cut @@ -220,15 +220,15 @@ attrs['class'] = attrs.pop('klass') except KeyError: pass - value += u' ' + u' '.join(u'%s="%s"' % (attr, html_escape(unicode(value))) + value += u' ' + u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value))) for attr, value in sorted(attrs.items()) if value is not None) if content: if escapecontent: - content = html_escape(unicode(content)) + content = xml_escape(unicode(content)) value += u'>%s' % (content, tag) else: - value += u'/>' + value += u'>' % tag return value def tooltipize(text, tooltip, url=None): @@ -406,9 +406,9 @@ strings.append(body) strings.append(u'') if title: - strings.append(u'

%s

'% html_escape(title)) + strings.append(u'

%s

'% xml_escape(title)) try: - strings.append(u'

%s

' % html_escape(str(exception)).replace("\n","
")) + strings.append(u'

%s

' % xml_escape(str(exception)).replace("\n","
")) except UnicodeError: pass strings.append(u'
') @@ -416,9 +416,9 @@ strings.append(u'File %s, line ' u'%s, function ' u'%s:
'%( - html_escape(stackentry[0]), stackentry[1], html_escape(stackentry[2]))) + xml_escape(stackentry[0]), stackentry[1], xml_escape(stackentry[2]))) if stackentry[3]: - string = html_escape(stackentry[3]).decode('utf-8', 'replace') + string = xml_escape(stackentry[3]).decode('utf-8', 'replace') strings.append(u'  %s
\n' % (string)) # add locals info for each entry try: @@ -426,7 +426,7 @@ html_info = [] chars = 0 for name, value in local_context.iteritems(): - value = html_escape(repr(value)) + value = xml_escape(repr(value)) info = u'%s=%s, ' % (name, value) line_length = len(name) + len(value) chars += line_length @@ -491,5 +491,5 @@ def newfunc(*args, **kwargs): ret = function(*args, **kwargs) assert isinstance(ret, basestring) - return html_escape(ret) + return xml_escape(ret) return newfunc diff -r ea1a44e4ad62 -r caad2367d940 cwconfig.py --- a/cwconfig.py Wed Jul 15 09:45:13 2009 +0200 +++ b/cwconfig.py Thu Jul 16 13:30:13 2009 +0200 @@ -571,7 +571,7 @@ }), ('sender-addr', {'type' : 'string', - 'default': 'devel@logilab.fr', + 'default': 'cubicweb@mydomain.com', 'help': 'email address used as HELO address for outgoing emails from \ the repository', 'group': 'email', 'inputlevel': 1, diff -r ea1a44e4ad62 -r caad2367d940 doc/book/en/development/datamodel/define-workflows.rst --- a/doc/book/en/development/datamodel/define-workflows.rst Wed Jul 15 09:45:13 2009 +0200 +++ b/doc/book/en/development/datamodel/define-workflows.rst Thu Jul 16 13:30:13 2009 +0200 @@ -118,7 +118,7 @@ * `%(seid)s`, the object's current state eid -.. image:: images/lax-book.03-transitions-view.en.png +.. image:: ../../images/lax-book.03-transitions-view.en.png You can notice that in the action box of a BlogEntry, the state is now listed as well as the possible transitions defined by the workflow. diff -r ea1a44e4ad62 -r caad2367d940 entity.py --- a/entity.py Wed Jul 15 09:45:13 2009 +0200 +++ b/entity.py Thu Jul 16 13:30:13 2009 +0200 @@ -13,7 +13,7 @@ from logilab.common.compat import all from logilab.common.decorators import cached from logilab.common.deprecation import obsolete -from logilab.mtconverter import TransformData, TransformError, html_escape +from logilab.mtconverter import TransformData, TransformError, xml_escape from rql.utils import rqlvar_maker @@ -463,7 +463,7 @@ return u'' value = printable_value(self.req, attrtype, value, props, displaytime) if format == 'text/html': - value = html_escape(value) + value = xml_escape(value) return value def mtc_transform(self, data, format, target_format, encoding, @@ -666,6 +666,7 @@ self.critical("can't get value for attribute %s of entity with eid %s", name, self.eid) if self.e_schema.destination(name) == 'String': + # XXX (syt) imo emtpy string is better self[name] = value = self.req._('unaccessible') else: self[name] = value = None diff -r ea1a44e4ad62 -r caad2367d940 ext/html4zope.py --- a/ext/html4zope.py Wed Jul 15 09:45:13 2009 +0200 +++ b/ext/html4zope.py Thu Jul 16 13:30:13 2009 +0200 @@ -24,7 +24,7 @@ __docformat__ = 'reStructuredText' -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from docutils import nodes from docutils.writers.html4css1 import Writer as CSS1Writer @@ -154,7 +154,7 @@ error = u'System Message: %s%s/%s%s (%s %s)%s

\n' % ( a_start, node['type'], node['level'], a_end, self.encode(node['source']), line, backref_text) - self.body.append(u'
ReST / HTML errors:%s
' % html_escape(error)) + self.body.append(u'
ReST / HTML errors:%s
' % xml_escape(error)) def depart_system_message(self, node): pass diff -r ea1a44e4ad62 -r caad2367d940 ext/rest.py --- a/ext/rest.py Wed Jul 15 09:45:13 2009 +0200 +++ b/ext/rest.py Thu Jul 16 13:30:13 2009 +0200 @@ -29,7 +29,7 @@ from docutils.parsers.rst import Parser, states, directives from docutils.parsers.rst.roles import register_canonical_role, set_classes -from logilab.mtconverter import html_escape +from logilab.mtconverter import ESC_UCAR_TABLE, ESC_CAR_TABLE, xml_escape from cubicweb.ext.html4zope import Writer @@ -207,8 +207,12 @@ req = context.req if isinstance(data, unicode): encoding = 'unicode' + # remove unprintable characters unauthorized in xml + data = data.translate(ESC_UCAR_TABLE) else: encoding = req.encoding + # remove unprintable characters unauthorized in xml + data = data.translate(ESC_CAR_TABLE) settings = {'input_encoding': encoding, 'output_encoding': 'unicode', 'warning_stream': StringIO(), 'context': context, # dunno what's the max, severe is 4, and we never want a crash @@ -232,5 +236,5 @@ LOGGER.exception('error while publishing ReST text') if not isinstance(data, unicode): data = unicode(data, encoding, 'replace') - return html_escape(req._('error while publishing ReST text') + return xml_escape(req._('error while publishing ReST text') + '\n\n' + data) diff -r ea1a44e4ad62 -r caad2367d940 goa/appobjects/components.py --- a/goa/appobjects/components.py Wed Jul 15 09:45:13 2009 +0200 +++ b/goa/appobjects/components.py Thu Jul 16 13:30:13 2009 +0200 @@ -7,7 +7,7 @@ """ __docformat__ = "restructuredtext en" -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb import typed_eid from cubicweb.selectors import one_line_rset, match_search_state, accept @@ -74,7 +74,7 @@ label = display_name(req, etype, 'plural') view = self.vreg.select('views', 'list', req, req.etype_rset(etype)) url = view.url() - etypelink = u' %s' % (html_escape(url), label) + etypelink = u' %s' % (xml_escape(url), label) yield (label, etypelink, self.add_entity_link(eschema, req)) ManageView.entity_types = entity_types_no_count diff -r ea1a44e4ad62 -r caad2367d940 goa/appobjects/dbmgmt.py --- a/goa/appobjects/dbmgmt.py Wed Jul 15 09:45:13 2009 +0200 +++ b/goa/appobjects/dbmgmt.py Thu Jul 16 13:30:13 2009 +0200 @@ -12,7 +12,7 @@ from pickle import loads, dumps from logilab.common.decorators import cached -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb.selectors import none_rset, match_user_groups from cubicweb.common.view import StartupView @@ -54,7 +54,7 @@ break values.append('__session=%s' % cookie['__session'].value) self.w(u"

pass this flag to the client: --cookie='%s'

" - % html_escape('; '.join(values))) + % xml_escape('; '.join(values))) @@ -148,7 +148,7 @@ % cpath) self.w(u'
click here to ' 'delete all datastore content so process can be ' - 'reinitialized
' % html_escape(self.req.base_url())) + 'reinitialized
' % xml_escape(self.req.base_url())) Put(status) @property @@ -159,11 +159,11 @@ repo=self.config.repository()) def msg(self, msg): - self.w(u'
%s
' % html_escape(msg)) + self.w(u'
%s
' % xml_escape(msg)) def redirect(self, msg): raise Redirect(self.req.build_url('', msg)) def continue_link(self): - self.w(u'continue
' % html_escape(self.req.url())) + self.w(u'continue
' % xml_escape(self.req.url())) class ContentClear(StartupView): diff -r ea1a44e4ad62 -r caad2367d940 i18n/en.po --- a/i18n/en.po Wed Jul 15 09:45:13 2009 +0200 +++ b/i18n/en.po Thu Jul 16 13:30:13 2009 +0200 @@ -160,6 +160,9 @@ "can also display a complete schema with meta-data." msgstr "" +msgid "" +msgstr "" + msgid "?*" msgstr "0..1 0..n" @@ -1147,6 +1150,9 @@ msgid "click on the box to cancel the deletion" msgstr "" +msgid "click to edit this field" +msgstr "" + msgid "comment" msgstr "" @@ -1721,12 +1727,6 @@ msgid "facets_cwfinal-facet_description" msgstr "" -msgid "facets_cwmeta-facet" -msgstr "" - -msgid "facets_cwmeta-facet_description" -msgstr "" - msgid "facets_etype-facet" msgstr "\"entity type\" facet" @@ -2259,9 +2259,6 @@ msgid "not selected" msgstr "" -msgid "not specified" -msgstr "" - msgid "not the initial state for this entity" msgstr "" @@ -2946,6 +2943,12 @@ msgid "view workflow" msgstr "" +msgid "view_index" +msgstr "index" + +msgid "view_manage" +msgstr "site management" + msgid "views" msgstr "" diff -r ea1a44e4ad62 -r caad2367d940 i18n/fr.po --- a/i18n/fr.po Wed Jul 15 09:45:13 2009 +0200 +++ b/i18n/fr.po Thu Jul 16 13:30:13 2009 +0200 @@ -164,8 +164,11 @@ "
This schema of the data model excludes the meta-data, but you " "can also display a complete schema with meta-data.
" msgstr "" -"
Ce schéma du modèle de données exclue les méta-données, mais vous " -"pouvez afficher un schéma complet.
" +"
Ce schéma du modèle de données exclue les méta-données, mais " +"vous pouvez afficher un schéma complet.
" + +msgid "" +msgstr "" msgid "?*" msgstr "0..1 0..n" @@ -633,9 +636,10 @@ "invalidate the cache (typically in hooks). Also, checkout the AppRsetObject." "get_cache() method." msgstr "" -"une simple entité de cache, caractérisées par un nom et une date de validité. L'application " -"est responsable de la mise à jour de la date quand il est nécessaire d'invalider le cache (typiquement dans les crochets). " -"Voir aussi la méthode get_cache() sur la classe AppRsetObject." +"une simple entité de cache, caractérisées par un nom et une date de " +"validité. L'application est responsable de la mise à jour de la date quand " +"il est nécessaire d'invalider le cache (typiquement dans les crochets). Voir " +"aussi la méthode get_cache() sur la classe AppRsetObject." msgid "about this site" msgstr "à propos de ce site" @@ -1039,7 +1043,8 @@ msgstr "signets" msgid "bookmarks are used to have user's specific internal links" -msgstr "les signets sont utilisés pour gérer des liens internes par utilisateur" +msgstr "" +"les signets sont utilisés pour gérer des liens internes par utilisateur" msgid "boxes" msgstr "boîtes" @@ -1178,7 +1183,10 @@ msgstr "cliquez ici pour voir l'entité créée" msgid "click on the box to cancel the deletion" -msgstr "cliquer dans la zone d'édition pour annuler la suppression" +msgstr "cliquez dans la zone d'édition pour annuler la suppression" + +msgid "click to edit this field" +msgstr "cliquez pour éditer ce champ" msgid "comment" msgstr "commentaire" @@ -1800,12 +1808,6 @@ msgid "facets_cwfinal-facet_description" msgstr "" -msgid "facets_cwmeta-facet" -msgstr "" - -msgid "facets_cwmeta-facet_description" -msgstr "" - msgid "facets_etype-facet" msgstr "facette \"est de type\"" @@ -2360,9 +2362,6 @@ msgid "not selected" msgstr "non sélectionné" -msgid "not specified" -msgstr "non spécifié" - msgid "not the initial state for this entity" msgstr "n'est pas l'état initial pour cette entité" @@ -3069,6 +3068,12 @@ msgid "view workflow" msgstr "voir les états possibles" +msgid "view_index" +msgstr "accueil" + +msgid "view_manage" +msgstr "gestion du site" + msgid "views" msgstr "vues" @@ -3252,6 +3257,9 @@ #~ msgid "no associated epermissions" #~ msgstr "aucune permission spécifique n'est définie" +#~ msgid "not specified" +#~ msgstr "non spécifié" + #~ msgid "owned by" #~ msgstr "appartient à" diff -r ea1a44e4ad62 -r caad2367d940 rset.py --- a/rset.py Wed Jul 15 09:45:13 2009 +0200 +++ b/rset.py Thu Jul 16 13:30:13 2009 +0200 @@ -538,10 +538,12 @@ for i, term in enumerate(rqlst.selection): if i == index: continue - try: - # XXX rewritten const - var = term.variable - except AttributeError: + # XXX rewritten const + # use iget_nodes for (hack) case where we have things like MAX(V) + for vref in term.iget_nodes(nodes.VariableRef): + var = vref.variable + break + else: continue #varname = var.name for ref in var.references(): diff -r ea1a44e4ad62 -r caad2367d940 rtags.py --- a/rtags.py Wed Jul 15 09:45:13 2009 +0200 +++ b/rtags.py Thu Jul 16 13:30:13 2009 +0200 @@ -101,6 +101,7 @@ '%r is not an allowed tag (should be in %s)' % ( tag, self._allowed_values) self._tagdefs[(rtype, tagged, stype, otype)] = tag + return tag # rtag runtime api ######################################################## @@ -123,15 +124,19 @@ class RelationTagsSet(RelationTags): - """This class associates a set of tags to each key.""" + """This class associates a set of tags to each key. + """ + tag_container_cls = set def tag_relation(self, key, tag): stype, rtype, otype, tagged = [str(k) for k in key] - rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype), set()) + rtags = self._tagdefs.setdefault((rtype, tagged, stype, otype), + self.tag_container_cls()) rtags.add(tag) + return rtags def get(self, stype, rtype, otype, tagged): - rtags = set() + rtags = self.tag_container_cls() for key in self._get_keys(stype, rtype, otype, tagged): try: rtags.update(self._tagdefs[key]) @@ -140,6 +145,31 @@ return rtags +class RelationTagsDict(RelationTagsSet): + """This class associates a set of tags to each key.""" + tag_container_cls = dict + + def tag_relation(self, key, tag): + stype, rtype, otype, tagged = [str(k) for k in key] + try: + rtags = self._tagdefs[(rtype, tagged, stype, otype)] + rtags.update(tag) + return rtags + except KeyError: + self._tagdefs[(rtype, tagged, stype, otype)] = tag + return tag + + def setdefault(self, key, tagkey, tagvalue): + stype, rtype, otype, tagged = [str(k) for k in key] + try: + rtags = self._tagdefs[(rtype, tagged, stype, otype)] + rtags.setdefault(tagkey, tagvalue) + return rtags + except KeyError: + self._tagdefs[(rtype, tagged, stype, otype)] = {tagkey: tagvalue} + return self._tagdefs[(rtype, tagged, stype, otype)] + + class RelationTagsBool(RelationTags): _allowed_values = frozenset((True, False)) diff -r ea1a44e4ad62 -r caad2367d940 selectors.py --- a/selectors.py Wed Jul 15 09:45:13 2009 +0200 +++ b/selectors.py Thu Jul 16 13:30:13 2009 +0200 @@ -79,7 +79,7 @@ ret = selector(cls, *args, **kwargs) if TRACED_OIDS == 'all' or oid in TRACED_OIDS: #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls) - print 'selector %s returned %s for %s' % (selname, ret, vobj) + print '%s -> %s for %s' % (selname, ret, vobj) return ret traced.__name__ = selector.__name__ return traced diff -r ea1a44e4ad62 -r caad2367d940 server/__init__.py --- a/server/__init__.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/__init__.py Thu Jul 16 13:30:13 2009 +0200 @@ -50,8 +50,7 @@ driver = source['db-driver'] sqlcnx = repo.system_source.get_connection() sqlcursor = sqlcnx.cursor() - def execute(sql, args=None): - repo.system_source.doexec(sqlcursor, sql, args) + execute = sqlcursor.execute if drop: dropsql = sqldropschema(schema, driver) try: diff -r ea1a44e4ad62 -r caad2367d940 server/hooksmanager.py --- a/server/hooksmanager.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/hooksmanager.py Thu Jul 16 13:30:13 2009 +0200 @@ -216,7 +216,14 @@ cls.warning('%s hook has been disabled', cls) return done = set() + assert isinstance(cls.events, (tuple, list)), \ + '%s: events is expected to be a tuple, not %s' % ( + cls, type(cls.events)) for event in cls.events: + if event == 'server_startup': + assert not cls.accepts or cls.accepts == ('Any',), \ + '%s doesnt make sense on server_startup' % cls.accepts + cls.accepts = ('Any',) for ertype in cls.accepts: if (event, ertype) in done: continue diff -r ea1a44e4ad62 -r caad2367d940 server/rqlannotation.py --- a/server/rqlannotation.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/rqlannotation.py Thu Jul 16 13:30:13 2009 +0200 @@ -22,7 +22,7 @@ has_text_query = False need_distinct = rqlst.distinct for rel in rqlst.iget_nodes(Relation): - if getrschema(rel.r_type).symetric: + if getrschema(rel.r_type).symetric and not rel.neged(strict=True): for vref in rel.iget_nodes(VariableRef): stinfo = vref.variable.stinfo if not stinfo['constnode'] and stinfo['selected']: diff -r ea1a44e4ad62 -r caad2367d940 server/session.py --- a/server/session.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/session.py Thu Jul 16 13:30:13 2009 +0200 @@ -88,9 +88,7 @@ """return a sql cursor on the system database""" if not sql.split(None, 1)[0].upper() == 'SELECT': self.mode = 'write' - cursor = self.pool['system'] - self.pool.source('system').doexec(cursor, sql, args) - return cursor + return self.pool.source('system').doexec(self, sql, args) def set_language(self, language): """i18n configuration for translation""" @@ -137,24 +135,27 @@ raise Exception('try to set pool on a closed session') if self.pool is None: # get pool first to avoid race-condition - self._threaddata.pool = self.repo._get_pool() + self._threaddata.pool = pool = self.repo._get_pool() try: - self._threaddata.pool.pool_set() + pool.pool_set() except: self._threaddata.pool = None - self.repo._free_pool(self.pool) + self.repo._free_pool(pool) raise self._threads_in_transaction.add(threading.currentThread()) return self._threaddata.pool def reset_pool(self): - """the session has no longer using its pool, at least for some time""" + """the session is no longer using its pool, at least for some time""" # pool may be none if no operation has been done since last commit # or rollback if self.pool is not None and self.mode == 'read': # even in read mode, we must release the current transaction pool = self.pool - self._threads_in_transaction.remove(threading.currentThread()) + try: + self._threads_in_transaction.remove(threading.currentThread()) + except KeyError: + pass pool.pool_reset() self._threaddata.pool = None # free pool once everything is done to avoid race-condition diff -r ea1a44e4ad62 -r caad2367d940 server/sources/extlite.py --- a/server/sources/extlite.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/sources/extlite.py Thu Jul 16 13:30:13 2009 +0200 @@ -174,9 +174,7 @@ if server.DEBUG: print self.uri, 'SOURCE RQL', union.as_string() args = self.sqladapter.merge_args(args, query_args) - cursor = session.pool[self.uri] - self.doexec(cursor, sql, args) - res = self.sqladapter.process_result(cursor) + res = self.sqladapter.process_result(self.doexec(session, sql, args)) if server.DEBUG: print '------>', res return res @@ -190,7 +188,7 @@ """ attrs = self.sqladapter.preprocess_entity(entity) sql = self.sqladapter.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def add_entity(self, session, entity): """add a new entity to the source""" @@ -207,7 +205,7 @@ attrs = self.sqladapter.preprocess_entity(entity) sql = self.sqladapter.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs, [SQL_PREFIX + 'eid']) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def update_entity(self, session, entity): """update an entity in the source""" @@ -222,7 +220,7 @@ """ attrs = {SQL_PREFIX + 'eid': eid} sql = self.sqladapter.sqlgen.delete(SQL_PREFIX + etype, attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def local_add_relation(self, session, subject, rtype, object): """add a relation to the source @@ -233,7 +231,7 @@ """ attrs = {'eid_from': subject, 'eid_to': object} sql = self.sqladapter.sqlgen.insert('%s_relation' % rtype, attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def add_relation(self, session, subject, rtype, object): """add a relation to the source""" @@ -252,21 +250,25 @@ else: attrs = {'eid_from': subject, 'eid_to': object} sql = self.sqladapter.sqlgen.delete('%s_relation' % rtype, attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) - def doexec(self, cursor, query, args=None): + def doexec(self, session, query, args=None): """Execute a query. it's a function just so that it shows up in profiling """ - #t1 = time() if server.DEBUG: print 'exec', query, args - #import sys - #sys.stdout.flush() - # str(query) to avoid error if it's an unicode string + cursor = session.pool[self.uri] try: + # str(query) to avoid error if it's an unicode string cursor.execute(str(query), args) except Exception, ex: self.critical("sql: %r\n args: %s\ndbms message: %r", query, args, ex.args[0]) + try: + session.pool.connection(self.uri).rollback() + self.critical('transaction has been rollbacked') + except: + pass raise + return cursor diff -r ea1a44e4ad62 -r caad2367d940 server/sources/native.py --- a/server/sources/native.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/sources/native.py Thu Jul 16 13:30:13 2009 +0200 @@ -31,6 +31,7 @@ from cubicweb.server.sources.rql2sql import SQLGenerator +ATTR_MAP = {} NONSYSTEM_ETYPES = set() NONSYSTEM_RELATIONS = set() @@ -90,6 +91,7 @@ class NativeSQLSource(SQLAdapterMixIn, AbstractSource): """adapter for source using the native cubicweb schema (see below) """ + sqlgen_class = SQLGenerator # need default value on class since migration doesn't call init method has_deleted_entitites_table = True @@ -141,8 +143,8 @@ AbstractSource.__init__(self, repo, appschema, source_config, *args, **kwargs) # sql generator - self._rql_sqlgen = SQLGenerator(appschema, self.dbhelper, - self.encoding) + self._rql_sqlgen = self.sqlgen_class(appschema, self.dbhelper, + self.encoding, ATTR_MAP.copy()) # full text index helper self.indexer = get_indexer(self.dbdriver, self.encoding) # advanced functionality helper @@ -185,9 +187,7 @@ def sqlexec(self, session, sql, args=None): """execute the query and return its result""" - cursor = session.pool[self.uri] - self.doexec(cursor, sql, args) - return self.process_result(cursor) + return self.process_result(self.doexec(session, sql, args)) def init_creating(self): pool = self.repo._get_pool() @@ -211,6 +211,9 @@ pool.pool_reset() self.repo._free_pool(pool) + def map_attribute(self, etype, attr, cb): + self._rql_sqlgen.attr_map['%s.%s' % (etype, attr)] = cb + # ISource interface ####################################################### def compile_rql(self, rql): @@ -305,17 +308,15 @@ sql, query_args = self._rql_sqlgen.generate(union, args, varmap) self._cache[cachekey] = sql, query_args args = self.merge_args(args, query_args) - cursor = session.pool[self.uri] assert isinstance(sql, basestring), repr(sql) try: - self.doexec(cursor, sql, args) + cursor = self.doexec(session, sql, args) except (self.dbapi_module.OperationalError, self.dbapi_module.InterfaceError): # FIXME: better detection of deconnection pb self.info("request failed '%s' ... retry with a new cursor", sql) session.pool.reconnect(self) - cursor = session.pool[self.uri] - self.doexec(cursor, sql, args) + cursor = self.doexec(session, sql, args) res = self.process_result(cursor) if server.DEBUG: print '------>', res @@ -337,8 +338,7 @@ # generate sql queries if we are able to do so sql, query_args = self._rql_sqlgen.generate(union, args, varmap) query = 'INSERT INTO %s %s' % (table, sql.encode(self.encoding)) - self.doexec(session.pool[self.uri], query, - self.merge_args(args, query_args)) + self.doexec(session, query, self.merge_args(args, query_args)) else: super(NativeSQLSource, self).flying_insert(table, session, union, args, varmap) @@ -358,15 +358,14 @@ cell = self.binary(cell.getvalue()) kwargs[str(index)] = cell kwargs_list.append(kwargs) - self.doexecmany(session.pool[self.uri], query, kwargs_list) + self.doexecmany(session, query, kwargs_list) def clean_temp_data(self, session, temptables): """remove temporary data, usually associated to temporary tables""" if temptables: - cursor = session.pool[self.uri] for table in temptables: try: - self.doexec(cursor,'DROP TABLE %s' % table) + self.doexec(session,'DROP TABLE %s' % table) except: pass try: @@ -378,25 +377,25 @@ """add a new entity to the source""" attrs = self.preprocess_entity(entity) sql = self.sqlgen.insert(SQL_PREFIX + str(entity.e_schema), attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def update_entity(self, session, entity): """replace an entity in the source""" attrs = self.preprocess_entity(entity) sql = self.sqlgen.update(SQL_PREFIX + str(entity.e_schema), attrs, [SQL_PREFIX + 'eid']) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def delete_entity(self, session, etype, eid): """delete an entity from the source""" attrs = {SQL_PREFIX + 'eid': eid} sql = self.sqlgen.delete(SQL_PREFIX + etype, attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def add_relation(self, session, subject, rtype, object): """add a relation to the source""" attrs = {'eid_from': subject, 'eid_to': object} sql = self.sqlgen.insert('%s_relation' % rtype, attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) def delete_relation(self, session, subject, rtype, object): """delete a relation from the source""" @@ -410,39 +409,47 @@ else: attrs = {'eid_from': subject, 'eid_to': object} sql = self.sqlgen.delete('%s_relation' % rtype, attrs) - self.doexec(session.pool[self.uri], sql, attrs) + self.doexec(session, sql, attrs) - def doexec(self, cursor, query, args=None): + def doexec(self, session, query, args=None): """Execute a query. it's a function just so that it shows up in profiling """ - #t1 = time() if server.DEBUG: print 'exec', query, args - #import sys - #sys.stdout.flush() - # str(query) to avoid error if it's an unicode string + cursor = session.pool[self.uri] try: + # str(query) to avoid error if it's an unicode string cursor.execute(str(query), args) except Exception, ex: self.critical("sql: %r\n args: %s\ndbms message: %r", query, args, ex.args[0]) + try: + session.pool.connection(self.uri).rollback() + self.critical('transaction has been rollbacked') + except: + pass raise + return cursor - def doexecmany(self, cursor, query, args): + def doexecmany(self, session, query, args): """Execute a query. it's a function just so that it shows up in profiling """ - #t1 = time() if server.DEBUG: print 'execmany', query, 'with', len(args), 'arguments' - #import sys - #sys.stdout.flush() - # str(query) to avoid error if it's an unicode string + cursor = session.pool[self.uri] try: + # str(query) to avoid error if it's an unicode string cursor.executemany(str(query), args) - except: - self.critical("sql many: %r\n args: %s", query, args) + except Exception, ex: + self.critical("sql many: %r\n args: %s\ndbms message: %r", + query, args, ex.args[0]) + try: + session.pool.connection(self.uri).rollback() + self.critical('transaction has been rollbacked') + except: + pass raise # short cut to method requiring advanced db helper usage ################## @@ -498,14 +505,13 @@ # running with an ldap source, and table will be deleted manually any way # on commit sql = self.dbhelper.sql_temporary_table(table, schema, False) - self.doexec(session.pool[self.uri], sql) + self.doexec(session, sql) def create_eid(self, session): self._eid_creation_lock.acquire() try: - cursor = session.pool[self.uri] for sql in self.dbhelper.sqls_increment_sequence('entities_id_seq'): - self.doexec(cursor, sql) + cursor = self.doexec(session, sql) return cursor.fetchone()[0] finally: self._eid_creation_lock.release() diff -r ea1a44e4ad62 -r caad2367d940 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/sources/rql2sql.py Thu Jul 16 13:30:13 2009 +0200 @@ -303,7 +303,7 @@ protected by a lock """ - def __init__(self, schema, dbms_helper, dbencoding='UTF-8'): + def __init__(self, schema, dbms_helper, dbencoding='UTF-8', attrmap=None): self.schema = schema self.dbms_helper = dbms_helper self.dbencoding = dbencoding @@ -313,6 +313,9 @@ if not self.dbms_helper.union_parentheses_support: self.union_sql = self.noparen_union_sql self._lock = threading.Lock() + if attrmap is None: + attrmap = {} + self.attr_map = attrmap def generate(self, union, args=None, varmap=None): """return SQL queries and a variable dictionnary from a RQL syntax tree @@ -853,27 +856,30 @@ relation.r_type) return '%s%s' % (lhssql, relation.children[1].accept(self, contextrels)) - def _visit_attribute_relation(self, relation): + def _visit_attribute_relation(self, rel): """generate SQL for an attribute relation""" - lhs, rhs = relation.get_parts() + lhs, rhs = rel.get_parts() rhssql = rhs.accept(self) table = self._var_table(lhs.variable) if table is None: - assert relation.r_type == 'eid' + assert rel.r_type == 'eid' lhssql = lhs.accept(self) else: try: - lhssql = self._varmap['%s.%s' % (lhs.name, relation.r_type)] + lhssql = self._varmap['%s.%s' % (lhs.name, rel.r_type)] except KeyError: - if relation.r_type == 'eid': + mapkey = '%s.%s' % (self._state.solution[lhs.name], rel.r_type) + if mapkey in self.attr_map: + lhssql = self.attr_map[mapkey](self, lhs.variable, rel) + elif rel.r_type == 'eid': lhssql = lhs.variable._q_sql else: - lhssql = '%s.%s%s' % (table, SQL_PREFIX, relation.r_type) + lhssql = '%s.%s%s' % (table, SQL_PREFIX, rel.r_type) try: - if relation._q_needcast == 'TODAY': + if rel._q_needcast == 'TODAY': sql = 'DATE(%s)%s' % (lhssql, rhssql) # XXX which cast function should be used - #elif relation._q_needcast == 'NOW': + #elif rel._q_needcast == 'NOW': # sql = 'TIMESTAMP(%s)%s' % (lhssql, rhssql) else: sql = '%s%s' % (lhssql, rhssql) @@ -884,15 +890,15 @@ else: return sql - def _visit_has_text_relation(self, relation): + def _visit_has_text_relation(self, rel): """generate SQL for a has_text relation""" - lhs, rhs = relation.get_parts() + lhs, rhs = rel.get_parts() const = rhs.children[0] - alias = self._fti_table(relation) + alias = self._fti_table(rel) jointo = lhs.accept(self) restriction = '' lhsvar = lhs.variable - me_is_principal = lhsvar.stinfo.get('principal') is relation + me_is_principal = lhsvar.stinfo.get('principal') is rel if me_is_principal: if not lhsvar.stinfo['typerels']: # the variable is using the fti table, no join needed @@ -908,8 +914,8 @@ else: etypes = ','.join("'%s'" % etype for etype in lhsvar.stinfo['possibletypes']) restriction = " AND %s.type IN (%s)" % (ealias, etypes) - if isinstance(relation.parent, Not): - self._state.done.add(relation.parent) + if isinstance(rel.parent, Not): + self._state.done.add(rel.parent) not_ = True else: not_ = False @@ -1117,6 +1123,9 @@ if isinstance(linkedvar, ColumnAlias): raise BadRQLQuery('variable %s should be selected by the subquery' % variable.name) + mapkey = '%s.%s' % (self._state.solution[linkedvar.name], rel.r_type) + if mapkey in self.attr_map: + return self.attr_map[mapkey](self, linkedvar, rel) try: sql = self._varmap['%s.%s' % (linkedvar.name, rel.r_type)] except KeyError: diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/server/test/data/schema.py Thu Jul 16 13:30:13 2009 +0200 @@ -0,0 +1,207 @@ +""" + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +from cubicweb.schema import format_constraint + +class Affaire(WorkflowableEntityType): + permissions = { + 'read': ('managers', + ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')), + 'add': ('managers', ERQLExpression('X concerne S, S owned_by U')), + 'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')), + 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')), + } + + ref = String(fulltextindexed=True, indexed=True, + constraints=[SizeConstraint(16)]) + sujet = String(fulltextindexed=True, + constraints=[SizeConstraint(256)]) + descr_format = String(meta=True, internationalizable=True, + default='text/rest', constraints=[format_constraint]) + descr = String(fulltextindexed=True, + description=_('more detailed description')) + + duration = Int() + invoiced = Int() + + depends_on = SubjectRelation('Affaire') + require_permission = SubjectRelation('CWPermission') + concerne = SubjectRelation(('Societe', 'Note')) + todo_by = SubjectRelation('Personne') + documented_by = SubjectRelation('Card') + + +class Societe(EntityType): + permissions = { + 'read': ('managers', 'users', 'guests'), + 'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')), + 'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')), + 'add': ('managers', 'users',) + } + + nom = String(maxsize=64, fulltextindexed=True) + web = String(maxsize=128) + type = String(maxsize=128) # attribute in common with Note + tel = Int() + fax = Int() + rncs = String(maxsize=128) + ad1 = String(maxsize=128) + ad2 = String(maxsize=128) + ad3 = String(maxsize=128) + cp = String(maxsize=12) + ville= String(maxsize=32) + + +class Division(Societe): + __specializes_schema__ = True + +class SubDivision(Division): + __specializes_schema__ = True + travaille_subdivision = ObjectRelation('Personne') + +_euser = import_schema('base').CWUser +_euser.__relations__[0].fulltextindexed = True + +class Note(EntityType): + date = String(maxsize=10) + type = String(maxsize=6) + para = String(maxsize=512) + + migrated_from = SubjectRelation('Note') + attachment = SubjectRelation(('File', 'Image')) + inline1 = SubjectRelation('Affaire', inlined=True) + todo_by = SubjectRelation('CWUser') + +class Personne(EntityType): + nom = String(fulltextindexed=True, required=True, maxsize=64) + prenom = String(fulltextindexed=True, maxsize=64) + sexe = String(maxsize=1, default='M') + promo = String(vocabulary=('bon','pasbon')) + titre = String(fulltextindexed=True, maxsize=128) + adel = String(maxsize=128) + ass = String(maxsize=128) + web = String(maxsize=128) + tel = Int() + fax = Int() + datenaiss = Datetime() + test = Boolean() + description = String() + firstname = String(fulltextindexed=True, maxsize=64) + + travaille = SubjectRelation('Societe') + concerne = SubjectRelation('Affaire') + connait = SubjectRelation('Personne') + inline2 = SubjectRelation('Affaire', inlined=True) + comments = ObjectRelation('Comment') + + +class fiche(RelationType): + inlined = True + subject = 'Personne' + object = 'Card' + cardinality = '??' + +class multisource_inlined_rel(RelationType): + inlined = True + cardinality = '?*' + subject = ('Card', 'Note') + object = ('Affaire', 'Note') + +class ecrit_par(RelationType): + inlined = True + +class connait(RelationType): + symetric = True + +class concerne(RelationType): + permissions = { + 'read': ('managers', 'users', 'guests'), + 'add': ('managers', RRQLExpression('U has_update_permission S')), + 'delete': ('managers', RRQLExpression('O owned_by U')), + } + +class travaille(RelationType): + permissions = { + 'read': ('managers', 'users', 'guests'), + 'add': ('managers', RRQLExpression('U has_update_permission S')), + 'delete': ('managers', RRQLExpression('O owned_by U')), + } + +class para(AttributeRelationType): + permissions = { + 'read': ('managers', 'users', 'guests'), + 'add': ('managers', ERQLExpression('X in_state S, S name "todo"')), + 'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')), + } + +class test(AttributeRelationType): + permissions = {'read': ('managers', 'users', 'guests'), + 'delete': ('managers',), + 'add': ('managers',)} + + +class in_state(RelationDefinition): + subject = 'Note' + object = 'State' + cardinality = '1*' + constraints=[RQLConstraint('S is ET, O state_of ET')] + +class wf_info_for(RelationDefinition): + subject = 'TrInfo' + object = 'Note' + cardinality = '1*' + +class multisource_rel(RelationDefinition): + subject = ('Card', 'Note') + object = 'Note' + +class multisource_crossed_rel(RelationDefinition): + subject = ('Card', 'Note') + object = 'Note' + + +class see_also(RelationDefinition): + subject = ('Bookmark', 'Note') + object = ('Bookmark', 'Note') + +class evaluee(RelationDefinition): + subject = ('Personne', 'CWUser', 'Societe') + object = ('Note') + +class ecrit_par_1(RelationDefinition): + name = 'ecrit_par' + subject = 'Note' + object ='Personne' + constraints = [RQLConstraint('E concerns P, X version_of P')] + +class ecrit_par_2(RelationDefinition): + name = 'ecrit_par' + subject = 'Note' + object ='CWUser' + +class see_also(RelationDefinition): + subject = object = 'Folder' + + +class copain(RelationDefinition): + subject = object = 'CWUser' + +class tags(RelationDefinition): + subject = 'Tag' + object = ('CWUser', 'CWGroup', 'State', 'Note', 'Card', 'Affaire') + +class filed_under(RelationDefinition): + subject = ('Note', 'Affaire') + object = 'Folder' + +class require_permission(RelationDefinition): + subject = ('Card', 'Note') + object = 'CWPermission' + +class require_state(RelationDefinition): + subject = 'CWPermission' + object = 'State' diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema/Affaire.py --- a/server/test/data/schema/Affaire.py Wed Jul 15 09:45:13 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -""" - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" -from cubicweb.schema import format_constraint - -class Affaire(WorkflowableEntityType): - permissions = { - 'read': ('managers', - ERQLExpression('X owned_by U'), ERQLExpression('X concerne S?, S owned_by U')), - 'add': ('managers', ERQLExpression('X concerne S, S owned_by U')), - 'update': ('managers', 'owners', ERQLExpression('X in_state S, S name in ("pitetre", "en cours")')), - 'delete': ('managers', 'owners', ERQLExpression('X concerne S, S owned_by U')), - } - - ref = String(fulltextindexed=True, indexed=True, - constraints=[SizeConstraint(16)]) - sujet = String(fulltextindexed=True, - constraints=[SizeConstraint(256)]) - descr_format = String(meta=True, internationalizable=True, - default='text/rest', constraints=[format_constraint]) - descr = String(fulltextindexed=True, - description=_('more detailed description')) - - duration = Int() - invoiced = Int() - - depends_on = SubjectRelation('Affaire') - require_permission = SubjectRelation('CWPermission') - -class concerne(RelationType): - permissions = { - 'read': ('managers', 'users', 'guests'), - 'add': ('managers', RRQLExpression('U has_update_permission S')), - 'delete': ('managers', RRQLExpression('O owned_by U')), - } - - diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema/Note.sql --- a/server/test/data/schema/Note.sql Wed Jul 15 09:45:13 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -date varchar(10) -type char(6) -para varchar(512) diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema/Personne.sql --- a/server/test/data/schema/Personne.sql Wed Jul 15 09:45:13 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -nom ivarchar(64) NOT NULL -prenom ivarchar(64) -sexe char(1) DEFAULT 'M' -promo choice('bon','pasbon') -titre ivarchar(128) -adel varchar(128) -ass varchar(128) -web varchar(128) -tel integer -fax integer -datenaiss datetime -test boolean -description text -firstname ivarchar(64) diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema/Societe.py --- a/server/test/data/schema/Societe.py Wed Jul 15 09:45:13 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -""" - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" -class Societe(EntityType): - permissions = { - 'read': ('managers', 'users', 'guests'), - 'update': ('managers', 'owners', ERQLExpression('U login L, X nom L')), - 'delete': ('managers', 'owners', ERQLExpression('U login L, X nom L')), - 'add': ('managers', 'users',) - } - - nom = String(maxsize=64, fulltextindexed=True) - web = String(maxsize=128) - type = String(maxsize=128) # attribute in common with Note - tel = Int() - fax = Int() - rncs = String(maxsize=128) - ad1 = String(maxsize=128) - ad2 = String(maxsize=128) - ad3 = String(maxsize=128) - cp = String(maxsize=12) - ville= String(maxsize=32) - - -class travaille(RelationType): - permissions = { - 'read': ('managers', 'users', 'guests'), - 'add': ('managers', RRQLExpression('U has_update_permission S')), - 'delete': ('managers', RRQLExpression('O owned_by U')), - } - - -class Division(Societe): - __specializes_schema__ = True - -class SubDivision(Division): - __specializes_schema__ = True - travaille_subdivision = ObjectRelation('Personne') diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema/custom.py --- a/server/test/data/schema/custom.py Wed Jul 15 09:45:13 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -""" - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" - - -class test(AttributeRelationType): - permissions = {'read': ('managers', 'users', 'guests'), - 'delete': ('managers',), - 'add': ('managers',)} - -class fiche(RelationType): - inlined = True - subject = 'Personne' - object = 'Card' - cardinality = '??' - -class multisource_rel(RelationDefinition): - subject = ('Card', 'Note') - object = 'Note' - -class multisource_crossed_rel(RelationDefinition): - subject = ('Card', 'Note') - object = 'Note' - -class multisource_inlined_rel(RelationType): - inlined = True - cardinality = '?*' - subject = ('Card', 'Note') - object = ('Affaire', 'Note') - - -class see_also(RelationDefinition): - subject = ('Bookmark', 'Note') - object = ('Bookmark', 'Note') - -_euser = import_schema('base').CWUser -_euser.__relations__[0].fulltextindexed = True diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema/note.py --- a/server/test/data/schema/note.py Wed Jul 15 09:45:13 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -""" - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" - -class para(AttributeRelationType): - permissions = { - 'read': ('managers', 'users', 'guests'), - 'add': ('managers', ERQLExpression('X in_state S, S name "todo"')), - 'delete': ('managers', ERQLExpression('X in_state S, S name "todo"')), - } - -class in_state(RelationDefinition): - subject = 'Note' - object = 'State' - cardinality = '1*' - constraints=[RQLConstraint('S is ET, O state_of ET')] - -class wf_info_for(RelationDefinition): - subject = 'TrInfo' - object = 'Note' - cardinality = '1*' diff -r ea1a44e4ad62 -r caad2367d940 server/test/data/schema/relations.rel --- a/server/test/data/schema/relations.rel Wed Jul 15 09:45:13 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -Personne travaille Societe -Personne evaluee Note -CWUser evaluee Note -Societe evaluee Note -Personne concerne Affaire -Affaire concerne Societe -Affaire concerne Note - -Note ecrit_par Personne inline CONSTRAINT E concerns P, X version_of P -Note ecrit_par CWUser inline CONSTRAINT -Personne connait Personne symetric - -# not inlined intentionaly -Comment comments Personne - -Note inline1 Affaire inline -Personne inline2 Affaire inline - -Note todo_by CWUser -Affaire todo_by Personne - -Folder see_also Folder - - -Affaire documented_by Card - -CWUser copain CWUser - -Tag tags CWUser -Tag tags CWGroup -Tag tags State -Tag tags Note -Tag tags Card -Tag tags Affaire - -Note filed_under Folder -Affaire filed_under Folder - -Card require_permission CWPermission -Note require_permission CWPermission -Personne require_permission CWPermission - -CWPermission require_state State - -Note migrated_from Note - -Note attachment File -Note attachment Image diff -r ea1a44e4ad62 -r caad2367d940 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Wed Jul 15 09:45:13 2009 +0200 +++ b/server/test/unittest_rql2sql.py Thu Jul 16 13:30:13 2009 +0200 @@ -1436,6 +1436,22 @@ '''SELECT COUNT(1) WHERE EXISTS(SELECT 1 FROM owned_by_relation AS rel_owned_by0, cw_Affaire AS P WHERE rel_owned_by0.eid_from=P.cw_eid AND rel_owned_by0.eid_to=1 UNION SELECT 1 FROM owned_by_relation AS rel_owned_by1, cw_Note AS P WHERE rel_owned_by1.eid_from=P.cw_eid AND rel_owned_by1.eid_to=1)''') + def test_attr_map(self): + def generate_ref(gen, linkedvar, rel): + linkedvar.accept(gen) + return 'VERSION_DATA(%s)' % linkedvar._q_sql + self.o.attr_map['Affaire.ref'] = generate_ref + try: + self._check('Any R WHERE X ref R', + '''SELECT VERSION_DATA(X.cw_eid) +FROM cw_Affaire AS X''') + self._check('Any X WHERE X ref 1', + '''SELECT X.cw_eid +FROM cw_Affaire AS X +WHERE VERSION_DATA(X.cw_eid)=1''') + finally: + self.o.attr_map.clear() + class SqliteSQLGeneratorTC(PostgresSQLGeneratorTC): diff -r ea1a44e4ad62 -r caad2367d940 skeleton/__pkginfo__.py.tmpl --- a/skeleton/__pkginfo__.py.tmpl Wed Jul 15 09:45:13 2009 +0200 +++ b/skeleton/__pkginfo__.py.tmpl Thu Jul 16 13:30:13 2009 +0200 @@ -34,21 +34,16 @@ and not fname.endswith('~') and not isdir(join(dirpath, fname))] -try: - data_files = [ - # common files - [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']], - ] - # check for possible extended cube layout - for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration'): - if isdir(dirname): - data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)]) - # Note: here, you'll need to add subdirectories if you want - # them to be included in the debian package -except OSError: - if exists(dirname(__file__)): - raise - # we are in an installed directory +data_files = [ + # common files + [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']], + ] +# check for possible extended cube layout +for dirname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data', 'i18n', 'migration'): + if isdir(dirname): + data_files.append([join(THIS_CUBE_DIR, dirname), listdir(dirname)]) +# Note: here, you'll need to add subdirectories if you want +# them to be included in the debian package cube_eid = None # <=== FIXME if you need direct bug-subscription diff -r ea1a44e4ad62 -r caad2367d940 test/unittest_rtags.py --- a/test/unittest_rtags.py Wed Jul 15 09:45:13 2009 +0200 +++ b/test/unittest_rtags.py Thu Jul 16 13:30:13 2009 +0200 @@ -6,7 +6,7 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ from logilab.common.testlib import TestCase, unittest_main -from cubicweb.rtags import RelationTags, RelationTagsSet +from cubicweb.rtags import RelationTags, RelationTagsSet, RelationTagsDict class RelationTagsTC(TestCase): @@ -53,12 +53,30 @@ rtags.tag_subject_of(('*', 'travaille', '*'), 'secondary') self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'), set(('primary', 'secondary'))) - self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'), - set(('primary', 'secondary'))) self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'), set(('secondary',))) self.assertEquals(rtags.get('Note', 'tags', "*", 'subject'), set()) + def test_rtagdict_expansion(self): + rtags = RelationTagsDict() + rtags.tag_subject_of(('Societe', 'travaille', '*'), + {'key1': 'val1', 'key2': 'val1'}) + rtags.tag_subject_of(('*', 'travaille', '*'), + {'key1': 'val0', 'key3': 'val0'}) + rtags.tag_subject_of(('Societe', 'travaille', '*'), + {'key2': 'val2'}) + self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'), + {'key1': 'val1', 'key2': 'val2', 'key3': 'val0'}) + self.assertEquals(rtags.get('Note', 'travaille', '*', 'subject'), + {'key1': 'val0', 'key3': 'val0'}) + self.assertEquals(rtags.get('Note', 'tags', "*", 'subject'), + {}) + + rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key1', 'val4') + rtags.setdefault(('Societe', 'travaille', '*', 'subject'), 'key4', 'val4') + self.assertEquals(rtags.get('Societe', 'travaille', '*', 'subject'), + {'key1': 'val1', 'key2': 'val2', 'key3': 'val0', 'key4': 'val4'}) + if __name__ == '__main__': unittest_main() diff -r ea1a44e4ad62 -r caad2367d940 utils.py --- a/utils.py Wed Jul 15 09:45:13 2009 +0200 +++ b/utils.py Thu Jul 16 13:30:13 2009 +0200 @@ -326,4 +326,18 @@ """ # XXX deprecated, no more necessary +def get_schema_property(eschema, rschema, role, property): + # XXX use entity.e_schema.role_rproperty(role, rschema, property, tschemas[0]) once yams > 0.23.0 is out + if role == 'subject': + targetschema = rschema.objects(eschema)[0] + return rschema.rproperty(eschema, targetschema, property) + targetschema = rschema.subjects(eschema)[0] + return rschema.rproperty(targetschema, eschema, property) +def compute_cardinality(eschema, rschema, role): + if role == 'subject': + targetschema = rschema.objects(eschema)[0] + return rschema.rproperty(eschema, targetschema, 'cardinality')[0] + targetschema = rschema.subjects(eschema)[0] + return rschema.rproperty(targetschema, eschema, 'cardinality')[1] + diff -r ea1a44e4ad62 -r caad2367d940 view.py --- a/view.py Wed Jul 15 09:45:13 2009 +0200 +++ b/view.py Thu Jul 16 13:30:13 2009 +0200 @@ -12,7 +12,7 @@ from cStringIO import StringIO from logilab.common.deprecation import obsolete -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb import NotAnEntity from cubicweb.selectors import yes, non_final_entity, nonempty_rset, none_rset @@ -219,7 +219,7 @@ def wdata(self, data): """simple helper that escapes `data` and writes into `self.w`""" - self.w(html_escape(data)) + self.w(xml_escape(data)) def html_headers(self): """return a list of html headers (eg something to be inserted between @@ -440,10 +440,10 @@ def cb(*args): _cb(*args) cbname = self.req.register_onetime_callback(cb, *args) - return self.build_js(cbname, html_escape(msg or '')) + return self.build_js(cbname, xml_escape(msg or '')) def build_update_js_call(self, cbname, msg): - rql = html_escape(self.rset.printable_rql()) + rql = xml_escape(self.rset.printable_rql()) return "javascript:userCallbackThenUpdateUI('%s', '%s', '%s', '%s', '%s', '%s')" % ( cbname, self.id, rql, msg, self.__registry__, self.div_id()) diff -r ea1a44e4ad62 -r caad2367d940 vregistry.py --- a/vregistry.py Wed Jul 15 09:45:13 2009 +0200 +++ b/vregistry.py Thu Jul 16 13:30:13 2009 +0200 @@ -1,10 +1,10 @@ """ -* the vregistry handle various type of objects interacting - together. The vregistry handle registration of dynamically loaded - objects and provide a convenient api access to those objects +* the vregistry handles various types of objects interacting + together. The vregistry handles registration of dynamically loaded + objects and provides a convenient api to access those objects according to a context -* to interact with the vregistry, object should inherit from the +* to interact with the vregistry, objects should inherit from the VObject abstract class * the selection procedure has been generalized by delegating to a diff -r ea1a44e4ad62 -r caad2367d940 web/box.py --- a/web/box.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/box.py Thu Jul 16 13:30:13 2009 +0200 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb import Unauthorized, role as get_role, target as get_target from cubicweb.selectors import (one_line_rset, primary_view, @@ -74,7 +74,7 @@ .format_actions method """ if escape: - title = html_escape(title) + title = xml_escape(title) return self.box_action(self._action(title, path, **kwargs)) def _action(self, title, path, **kwargs): diff -r ea1a44e4ad62 -r caad2367d940 web/component.py --- a/web/component.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/component.py Thu Jul 16 13:30:13 2009 +0200 @@ -9,7 +9,7 @@ _ = unicode from logilab.common.deprecation import class_renamed -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb import role from cubicweb.utils import merge_dicts @@ -117,7 +117,7 @@ def page_link(self, path, params, start, stop, content): url = self.build_url(path, **merge_dicts(params, {self.start_param : start, self.stop_param : stop,})) - url = html_escape(url) + url = xml_escape(url) if start == self.starting_from: return self.selected_page_link_templ % (url, content, content) return self.page_link_templ % (url, content, content) @@ -130,7 +130,7 @@ stop = start + self.page_size - 1 url = self.build_url(**merge_dicts(params, {self.start_param : start, self.stop_param : stop,})) - url = html_escape(url) + url = xml_escape(url) return self.previous_page_link_templ % (url, title, content) def next_link(self, params, content='>>', title=_('next_results')): @@ -140,7 +140,7 @@ stop = start + self.page_size - 1 url = self.build_url(**merge_dicts(params, {self.start_param : start, self.stop_param : stop,})) - url = html_escape(url) + url = xml_escape(url) return self.next_page_link_templ % (url, title, content) diff -r ea1a44e4ad62 -r caad2367d940 web/controller.py --- a/web/controller.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/controller.py Thu Jul 16 13:30:13 2009 +0200 @@ -184,11 +184,9 @@ elif '__redirectpath' in self.req.form: # if redirect path was explicitly specified in the form, use it path = self.req.form['__redirectpath'] - if self._edited_entity: - msg = newparams.get('__message', '') - msg += ' (%s)' % ( - self._edited_entity.absolute_url(), - self.req._('click here to see created entity')) + if self._edited_entity and path != self._edited_entity.rest_path(): + # XXX may be here on modification? if yes the message should be + # modified where __createdpath is detected (cw.web.request) newparams['__createdpath'] = self._edited_entity.rest_path() elif self._after_deletion_path: # else it should have been set during form processing diff -r ea1a44e4ad62 -r caad2367d940 web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Wed Jul 15 09:45:13 2009 +0200 +++ b/web/data/cubicweb.ajax.js Thu Jul 16 13:30:13 2009 +0200 @@ -59,8 +59,8 @@ if (typeof buildWidgets != 'undefined') { buildWidgets(node); } - if (typeof roundedCornersOnLoad != 'undefined') { - roundedCornersOnLoad(); + if (typeof roundedCorners != 'undefined') { + roundedCorners(node); } loadDynamicFragments(node); jQuery(CubicWeb).trigger('ajax-loaded'); @@ -231,6 +231,7 @@ // make sure the component is visible removeElementClass(node, "hidden"); swapDOM(node, domnode); + postAjaxLoad(domnode); } }); d.addCallback(resetCursor); diff -r ea1a44e4ad62 -r caad2367d940 web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Wed Jul 15 09:45:13 2009 +0200 +++ b/web/data/cubicweb.edition.js Thu Jul 16 13:30:13 2009 +0200 @@ -348,11 +348,11 @@ // Success if (result[0]) { if (onsuccess) { - return onsuccess(result[1], formid); + onsuccess(result[1], formid); } else { document.location.href = result[1]; - return ; } + return; } unfreezeFormButtons(formid); // Failures @@ -362,7 +362,7 @@ if ( !isArrayLike(descr) || descr.length != 2 ) { log('got strange error :', descr); updateMessage(descr); - return ; + return; } _displayValidationerrors(formid, descr[0], descr[1]); updateMessage(_("please correct errors below")); @@ -370,7 +370,7 @@ if (onfailure){ onfailure(formid); } - return false; + return; } @@ -422,7 +422,7 @@ }); } -$(document).ready(setFormsTarget); +jQuery(document).ready(setFormsTarget); /* @@ -454,9 +454,9 @@ * @param eid : the eid of the entity being edited * @param reload: boolean to reload page if true (when changing URL dependant data) */ -function inlineValidateAttributeForm(formid, rtype, eid, divid, reload, default_value) { +function inlineValidateAttributeForm(rtype, eid, divid, reload, default_value) { try { - var form = getNode(formid); + var form = getNode(divid+'-form'); if (typeof FCKeditorAPI != "undefined") { for ( var name in FCKeditorAPI.__Instances ) { var oEditor = FCKeditorAPI.__Instances[name] ; @@ -473,7 +473,7 @@ return false; } d.addCallback(function (result, req) { - handleFormValidationResponse(formid, noop, noop, result); + handleFormValidationResponse(divid+'-form', noop, noop, result); if (reload) { document.location.href = result[1]; } else { @@ -486,7 +486,7 @@ // hide global error messages jQuery('div.errorMessage').remove(); jQuery('#appMsg').hide(); - cancelInlineEdit(eid, rtype, divid); + hideInlineEdit(eid, rtype, divid); } } return false; @@ -494,30 +494,29 @@ return false; } -function inlineValidateRelationForm(formid, rtype, role, eid, divid, vid, default_value) { +function inlineValidateRelationForm(rtype, role, eid, divid, reload, vid, + default_value, lzone) { try { - var form = getNode(formid); + var form = getNode(divid+'-form'); var relname = rtype + ':' + eid; var newtarget = jQuery('[name=' + relname + ']').val(); var zipped = formContents(form); - var d = asyncRemoteExec('edit_relation', 'apply', zipped[0], zipped[1], rtype, role, - eid, vid, default_value); + var d = asyncRemoteExec('validate_form', 'apply', zipped[0], zipped[1]); } catch (ex) { log('got exception', ex); return false; } d.addCallback(function (result, req) { - handleFormValidationResponse(formid, noop, noop, result); - var fieldview = getNode(divid); - fieldview.innerHTML = result[2]; - // switch inline form off only if no error - if (result[0]) { - // hide global error messages - jQuery('div.errorMessage').remove(); - jQuery('#appMsg').hide(); - var inputname = 'edit' + role[0] + '-' + relname; - jQuery('input[name=' + inputname + ']').val(newtarget); - cancelInlineEdit(eid, rtype, divid); + handleFormValidationResponse(divid+'-form', noop, noop, result); + if (reload) { + document.location.href = result[1]; + } else { + if (result[0]) { + var d = asyncRemoteExec('reledit_form', eid, rtype, role, lzone); + d.addCallback(function (result) { + jQuery('#'+divid+'-reledit').replaceWith(result); + }); + } } return false; }); @@ -528,12 +527,12 @@ /**** inline edition ****/ function showInlineEditionForm(eid, rtype, divid) { jQuery('#' + divid).hide(); - jQuery('#' + divid + '-form').show(); + jQuery('#' + divid+'-form').show(); } -function cancelInlineEdit(eid, rtype, divid) { +function hideInlineEdit(eid, rtype, divid) { jQuery('#' + divid).show(); - jQuery('#' + divid + '-form').hide(); + jQuery('#' + divid+'-form').hide(); } CubicWeb.provide('edition.js'); diff -r ea1a44e4ad62 -r caad2367d940 web/data/cubicweb.formfilter.js --- a/web/data/cubicweb.formfilter.js Wed Jul 15 09:45:13 2009 +0200 +++ b/web/data/cubicweb.formfilter.js Thu Jul 16 13:30:13 2009 +0200 @@ -126,27 +126,35 @@ }); facet.find('div.facetCheckBox').click(function () { var $this = jQuery(this); + if ($this.hasClass('facetValueDisabled')){ + return + } if ($this.hasClass('facetValueSelected')) { $this.removeClass('facetValueSelected'); $this.find('img').each(function (i){ if (this.getAttribute('cubicweb:unselimg')){ this.setAttribute('src', UNSELECTED_BORDER_IMG); + this.setAttribute('alt', (_('not selected'))); } else{ this.setAttribute('src', UNSELECTED_IMG); + this.setAttribute('alt', (_('not selected'))); } }); var index = parseInt($this.attr('cubicweb:idx')); - var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) { - var nindex = parseInt(n.getAttribute('cubicweb:idx')); - return nindex > index; - }).length; - index += shift; - var parent = this.parentNode; - var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')'); - if ( ! ($insertAfter.length == 1 && index == 0) ) { - // only rearrange element if necessary - $insertAfter.after(this); + // we dont need to move the element when cubicweb:idx == 0 + if (index > 0){ + var shift = jQuery.grep(facet.find('.facetValueSelected'), function (n) { + var nindex = parseInt(n.getAttribute('cubicweb:idx')); + return nindex > index; + }).length; + index += shift; + var parent = this.parentNode; + var $insertAfter = jQuery(parent).find('.facetCheckBox:nth('+index+')'); + if ( ! ($insertAfter.length == 1 && shift == 0) ) { + // only rearrange element if necessary + $insertAfter.after(this); + } } } else { var lastSelected = facet.find('.facetValueSelected:last'); @@ -157,7 +165,8 @@ jQuery(parent).prepend(this); } jQuery(this).addClass('facetValueSelected'); - jQuery(this).find('img').attr('src', SELECTED_IMG); + var $img = jQuery(this).find('img'); + $img.attr('src', SELECTED_IMG).attr('alt', (_('selected'))); } buildRQL.apply(null, evalJSON(form.attr('cubicweb:facetargs'))); facet.find('.facetBody').animate({scrollTop: 0}, ''); diff -r ea1a44e4ad62 -r caad2367d940 web/data/cubicweb.htmlhelpers.js --- a/web/data/cubicweb.htmlhelpers.js Wed Jul 15 09:45:13 2009 +0200 +++ b/web/data/cubicweb.htmlhelpers.js Thu Jul 16 13:30:13 2009 +0200 @@ -1,4 +1,5 @@ CubicWeb.require('python.js'); +CubicWeb.require('jquery.corner.js'); /* returns the document's baseURI. (baseuri() uses document.baseURI if * available and inspects the tag manually otherwise.) @@ -15,6 +16,7 @@ return ''; } +// XXX this is used exactly ONCE in web/views/massmailing.py function insertText(text, areaId) { var textarea = jQuery('#' + areaId); if (document.selection) { // IE @@ -39,6 +41,7 @@ } /* taken from dojo toolkit */ +// XXX this looks unused function setCaretPos(element, start, end){ if(!end){ end = element.value.length; } // NOTE: Strange - should be able to put caret at start of text? // Mozilla @@ -71,30 +74,30 @@ } } + +// XXX this looks unused function setProgressMessage(label) { var body = document.getElementsByTagName('body')[0]; body.appendChild(DIV({id: 'progress'}, label)); jQuery('#progress').show(); } +// XXX this looks unused function resetProgressMessage() { var body = document.getElementsByTagName('body')[0]; jQuery('#progress').hide(); } -/* set body's cursor to 'progress' - */ +/* set body's cursor to 'progress' */ function setProgressCursor() { var body = document.getElementsByTagName('body')[0]; body.style.cursor = 'progress'; } -/* - * reset body's cursor to default (mouse cursor). The main +/* reset body's cursor to default (mouse cursor). The main * purpose of this function is to be used as a callback in the - * deferreds' callbacks chain. - */ + * deferreds' callbacks chain. */ function resetCursor(result) { var body = document.getElementsByTagName('body')[0]; body.style.cursor = 'default'; @@ -137,7 +140,7 @@ */ function firstSelected(selectNode) { var selection = filter(attrgetter('selected'), selectNode.options); - return (selection.length>0) ? getNodeAttribute(selection[0], 'value'):null; + return (selection.length > 0) ? getNodeAttribute(selection[0], 'value'):null; } /* toggle visibility of an element by its id @@ -148,22 +151,12 @@ /* toggles visibility of login popup div */ +// XXX used exactly ONCE function popupLoginBox() { toggleVisibility('popupLoginBox'); jQuery('#__login:visible').focus(); } -/* - * return true (resp. false) if (resp. doesn't) matches - */ -function elementMatches(properties, element) { - for (prop in properties) { - if (getNodeAttribute(element, prop) != properties[prop]) { - return false; - } - } - return true; -} /* returns the list of elements in the document matching the tag name * and the properties provided @@ -174,9 +167,13 @@ * list() function) */ function getElementsMatching(tagName, properties, /* optional */ parent) { - var filterfunc = partial(elementMatches, properties); parent = parent || document; - return filter(filterfunc, parent.getElementsByTagName(tagName)); + return filter(function elementMatches(element) { + for (prop in properties) { + if (getNodeAttribute(element, prop) != properties[prop]) { + return false;}} + return true;}, + parent.getElementsByTagName(tagName)); } /* @@ -196,9 +193,8 @@ forEach(filter(filterfunc, elements), function(cb) {cb.checked=checked;}); } -/* - * centers an HTML element on the screen - */ +/* centers an HTML element on the screen */ +// XXX looks unused function centerElement(obj){ var vpDim = getViewportDimensions(); var elemDim = getElementDimensions(obj); @@ -227,15 +223,9 @@ function isTextNode(domNode) { return domNode.nodeType == 3; } function isElementNode(domNode) { return domNode.nodeType == 1; } +// XXX this looks unused function changeLinkText(link, newText) { jQuery(link).text(newText); -// for (var i=0; i' % ( - key, html_escape(val))) + key, xml_escape(val))) def _may_be_removed(rel, schema, mainvar): @@ -586,11 +587,11 @@ self.items.append(item) def _render(self): - title = html_escape(self.facet.title) - facetid = html_escape(self.facet.id) + title = xml_escape(self.facet.title) + facetid = xml_escape(self.facet.id) self.w(u'
\n' % facetid) self.w(u'
%s
\n' % - (html_escape(facetid), title)) + (xml_escape(facetid), title)) if self.facet.support_and(): _ = self.facet.req._ self.w(u'''''') def test_richtextfield_2(self): self.req.use_fckeditor = lambda: True - self._test_richtextfield('') def test_filefield(self): @@ -172,14 +172,14 @@ data=Binary('new widgets system')) form = FFForm(self.req, redirect_path='perdu.com', entity=file) self.assertTextEquals(self._render_entity_field('data', form), - ''' + ''' show advanced fields
- + detach attached file ''' % {'eid': file.eid}) @@ -196,17 +196,17 @@ data=Binary('new widgets system')) form = EFFForm(self.req, redirect_path='perdu.com', entity=file) self.assertTextEquals(self._render_entity_field('data', form), - ''' + ''' show advanced fields
- + detach attached file

You can either submit a new file using the browse button above, or choose to remove already uploaded file by checking the "detach attached file" check-box, or edit file content online with the widget below.

-''' % {'eid': file.eid}) +''' % {'eid': file.eid}) def test_passwordfield(self): @@ -214,9 +214,9 @@ upassword = StringField(widget=PasswordInput) form = PFForm(self.req, redirect_path='perdu.com', entity=self.entity) self.assertTextEquals(self._render_entity_field('upassword', form), - ''' + '''
- +   confirm password''' % {'eid': self.entity.eid}) diff -r ea1a44e4ad62 -r caad2367d940 web/test/unittest_views_baseviews.py --- a/web/test/unittest_views_baseviews.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/test/unittest_views_baseviews.py Thu Jul 16 13:30:13 2009 +0200 @@ -102,6 +102,7 @@ # self.assertAlmostEquals(value, rset.rows[0][3].seconds) def test_sortvalue_with_display_col(self): + self.skip('XXX there is no column_labels on rset') e, rset, view = self._prepare_entity() labels = rset.column_labels() table = TableWidget(view) diff -r ea1a44e4ad62 -r caad2367d940 web/uicfg.py --- a/web/uicfg.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/uicfg.py Thu Jul 16 13:30:13 2009 +0200 @@ -68,7 +68,8 @@ __docformat__ = "restructuredtext en" from cubicweb import neg_role -from cubicweb.rtags import RelationTags, RelationTagsBool, RelationTagsSet +from cubicweb.rtags import (RelationTags, RelationTagsBool, + RelationTagsSet, RelationTagsDict) from cubicweb.web import formwidgets @@ -118,14 +119,13 @@ primaryview_section.tag_attribute(('CWRType', attr), 'hidden') -class DisplayCtrlRelationTags(RelationTags): +class DisplayCtrlRelationTags(RelationTagsDict): def __init__(self, *args, **kwargs): super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs) self._counter = 0 def tag_relation(self, key, tag): - assert isinstance(tag, dict) - super(DisplayCtrlRelationTags, self).tag_relation(key, tag) + tag = super(DisplayCtrlRelationTags, self).tag_relation(key, tag) self._counter += 1 tag.setdefault('order', self._counter) @@ -152,11 +152,8 @@ else: sschema = '*' label = '%s_%s' % (rschema, role) - displayinfo = rtag.get(sschema, rschema, oschema, role) - if displayinfo is None: - displayinfo = {} - rtag.tag_relation((sschema, rschema, oschema, role), displayinfo) - displayinfo.setdefault('label', label) + rtag.setdefault((sschema, rschema, oschema, role), 'label', label) + rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag._counter) primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl', init_primaryview_display_ctrl) @@ -243,7 +240,7 @@ autoform_field = RelationTags('autoform_field') # relations'field explicit kwargs (given to field's __init__) -autoform_field_kwargs = RelationTags() +autoform_field_kwargs = RelationTagsDict() autoform_field_kwargs.tag_attribute(('RQLExpression', 'expression'), {'widget': formwidgets.TextInput}) autoform_field_kwargs.tag_attribute(('Bookmark', 'path'), diff -r ea1a44e4ad62 -r caad2367d940 web/views/basecomponents.py --- a/web/views/basecomponents.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/basecomponents.py Thu Jul 16 13:30:13 2009 +0200 @@ -11,11 +11,12 @@ __docformat__ = "restructuredtext en" _ = unicode +from logilab.mtconverter import xml_escape from rql import parse from cubicweb.selectors import yes, two_etypes_rset, match_form_params from cubicweb.schema import display_name -from cubicweb.common.uilib import html_escape, toggle_action +from cubicweb.common.uilib import toggle_action from cubicweb.web import component from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink) @@ -47,7 +48,7 @@ ''' % (not self.propval('visible') and 'hidden' or '', - self.build_url('view'), html_escape(rql), req._('full text or RQL query'), req.next_tabindex(), + self.build_url('view'), xml_escape(rql), req._('full text or RQL query'), req.next_tabindex(), req.next_tabindex())) if self.req.search_state[0] != 'normal': self.w(u'' @@ -202,7 +203,7 @@ url = self.build_url(rql=newrql, __restrrql=restrrql, __restrtype=etype, __restrtypes=','.join(restrtypes)) html.append(u'%s' % ( - html_escape(url), elabel)) + xml_escape(url), elabel)) rqlst.recover() if on_etype: url = self.build_url(rql=restrrql) diff -r ea1a44e4ad62 -r caad2367d940 web/views/basecontrollers.py --- a/web/views/basecontrollers.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/basecontrollers.py Thu Jul 16 13:30:13 2009 +0200 @@ -15,7 +15,7 @@ import simplejson from logilab.common.decorators import cached -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb import NoSelectableObject, ValidationError, ObjectNotFound, typed_eid from cubicweb.utils import strptime @@ -394,27 +394,16 @@ rset = self.req.execute('Any X,N WHERE X eid %%(x)s, X %s N' % rtype, {'x': eid}, 'x') entity = rset.get_entity(0, 0) - value = entity.printable_value(rtype) - return (success, args, value or default) + value = entity.printable_value(rtype) or default + return (success, args, value) else: return (success, args, None) @jsonize - def js_edit_relation(self, action, names, values, - rtype, role, eid, vid, default): - success, args = self.validate_form(action, names, values) - if success: - entity = self.req.eid_rset(eid).get_entity(0, 0) - rset = entity.related(rtype, role) - if rset: - output = self.view(vid, rset) - if vid == 'textoutofcontext': - output = html_escape(output) - else: - output = default - return (success, args, output) - else: - return (success, args, None) + def js_reledit_form(self, eid, rtype, role, lzone): + entity = self.req.eid_rset(eid).get_entity(0, 0) + return entity.view('reledit', rtype=rtype, role=role, + landing_zone=lzone) @jsonize def js_i18n(self, msgids): diff -r ea1a44e4ad62 -r caad2367d940 web/views/baseforms.py --- a/web/views/baseforms.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/baseforms.py Thu Jul 16 13:30:13 2009 +0200 @@ -12,7 +12,7 @@ from simplejson import dumps -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from logilab.common.decorators import cached from cubicweb.selectors import (specified_etype_implements, accepts_etype_compat, @@ -148,7 +148,7 @@ output = [] for name, value, iid in self._hiddens: if isinstance(value, basestring): - value = html_escape(value) + value = xml_escape(value) if iid: output.append(u'' % (iid, name, value)) @@ -249,14 +249,14 @@ w(u'[x]' % (_('cancel this insert'), row[2])) w(u'%s' - % (row[1], row[4], html_escape(row[5]))) + % (row[1], row[4], xml_escape(row[5]))) w(u'') w(u'') w(u'' % eid) w(u'') w(u'%s' % _('add relation')) w(u'
""" % (hidden and 'hidden' or '', divid, selectid, - html_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname, + xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname, '\n'.join(options)) def _get_select_options(self, entity, rschema, target): @@ -126,13 +126,13 @@ for eview, reid in form.form_field_vocabulary(field, limit): if reid is None: options.append('' - % html_escape(eview)) + % xml_escape(eview)) else: optionid = relation_id(eid, rtype, target, reid) if optionid not in pending_inserts: # prefix option's id with letters to make valid XHTML wise options.append('' % - (optionid, reid, html_escape(eview))) + (optionid, reid, xml_escape(eview))) return options def _get_search_options(self, entity, rschema, target, targettypes): @@ -145,7 +145,7 @@ __mode=mode) options.append((eschema.display_name(self.req), '' % ( - html_escape(url), _('Search for'), eschema.display_name(self.req)))) + xml_escape(url), _('Search for'), eschema.display_name(self.req)))) return [o for l, o in sorted(options)] def _get_basket_options(self, entity, rschema, target, targettypes): @@ -156,7 +156,7 @@ target, targettypes): optionid = relation_id(entity.eid, rtype, target, basketeid) options.append('' % ( - optionid, basketeid, _('link to each item in'), html_escape(basketname))) + optionid, basketeid, _('link to each item in'), xml_escape(basketname))) return options def _get_basket_links(self, ueid, target, targettypes): diff -r ea1a44e4ad62 -r caad2367d940 web/views/emailaddress.py --- a/web/views/emailaddress.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/emailaddress.py Thu Jul 16 13:30:13 2009 +0200 @@ -7,8 +7,9 @@ """ __docformat__ = "restructuredtext en" -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape +from cubicweb.schema import display_name from cubicweb.selectors import implements from cubicweb.common import Unauthorized from cubicweb.web.views import baseviews, primary @@ -79,9 +80,9 @@ if entity.reverse_primary_email: self.w(u'') if entity.alias: - self.w(u'%s <' % html_escape(entity.alias)) - self.w('%s' % (html_escape(entity.absolute_url()), - html_escape(entity.display_address()))) + self.w(u'%s <' % xml_escape(entity.alias)) + self.w('%s' % (xml_escape(entity.absolute_url()), + xml_escape(entity.display_address()))) if entity.alias: self.w(u'>\n') if entity.reverse_primary_email: @@ -108,8 +109,8 @@ mailto = "mailto:%s <%s>" % (alias, entity.display_address()) else: mailto = "mailto:%s" % entity.display_address() - self.w(u'%s' % (html_escape(mailto), - html_escape(entity.display_address()))) + self.w(u'%s' % (xml_escape(mailto), + xml_escape(entity.display_address()))) if entity.reverse_primary_email: self.w(u'') diff -r ea1a44e4ad62 -r caad2367d940 web/views/facets.py --- a/web/views/facets.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/facets.py Thu Jul 16 13:30:13 2009 +0200 @@ -9,14 +9,15 @@ from simplejson import dumps -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb.vregistry import objectify_selector from cubicweb.selectors import (non_final_entity, two_lines_rset, match_context_prop, yes, relation_possible) from cubicweb.web.box import BoxTemplate from cubicweb.web.facet import (AbstractFacet, FacetStringWidget, RelationFacet, - prepare_facets_rqlst, filter_hiddens) + prepare_facets_rqlst, filter_hiddens, _cleanup_rqlst, + _prepare_vocabulary_rqlst) @objectify_selector def contextview_selector(cls, req, rset=None, row=None, col=None, view=None, @@ -41,7 +42,7 @@ needs_css = 'cubicweb.facets.css' needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js') - bkLinkBox_template = u'
%s
' + bk_linkbox_template = u'
%s
' def facetargs(self): """this method returns the list of extra arguments that should @@ -82,10 +83,11 @@ widgets.append(wdg) if not widgets: return - self.displayBookmarkLink(rset) + if self.bk_linkbox_template: + self.display_bookmark_link(rset) w = self.w w(u'
' % ( - divid, html_escape(dumps([divid, vid, paginate, self.facetargs()])))) + divid, xml_escape(dumps([divid, vid, paginate, self.facetargs()])))) w(u'
') hiddens = {'facets': ','.join(wdg.facet.id for wdg in widgets), 'baserql': baserql} @@ -101,7 +103,7 @@ import cubicweb cubicweb.info('after facets with rql: %s' % repr(rqlst)) - def displayBookmarkLink(self, rset): + def display_bookmark_link(self, rset): eschema = self.schema.eschema('Bookmark') if eschema.has_perm(self.req, 'add'): bk_path = 'view?rql=%s' % rset.printable_rql() @@ -110,10 +112,10 @@ bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto) bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto) bk_link = u'%s' % ( - html_escape(bk_base_url), - html_escape(bk_add_url), + xml_escape(bk_base_url), + xml_escape(bk_add_url), self.req._('bookmark this search')) - self.w(self.bkLinkBox_template % bk_link) + self.w(self.bk_linkbox_template % bk_link) def get_facets(self, rset, mainvar): return self.vreg.possible_vobjects('facets', self.req, rset=rset, @@ -162,6 +164,21 @@ return self.rqlst.add_type_restriction(self.filtered_variable, value) + def possible_values(self): + """return a list of possible values (as string since it's used to + compare to a form value in javascript) for this facet + """ + rqlst = self.rqlst + rqlst.save_state() + try: + _cleanup_rqlst(rqlst, self.filtered_variable) + etype_var = _prepare_vocabulary_rqlst(rqlst, self.filtered_variable, self.rtype, self.role) + attrvar = rqlst.make_variable() + rqlst.add_selected(attrvar) + rqlst.add_relation(etype_var, 'name', attrvar) + return [etype for _, etype in self.rqlexec(rqlst.as_string())] + finally: + rqlst.recover() class HasTextFacet(AbstractFacet): __select__ = relation_possible('has_text', 'subject') & match_context_prop() diff -r ea1a44e4ad62 -r caad2367d940 web/views/formrenderers.py --- a/web/views/formrenderers.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/formrenderers.py Thu Jul 16 13:30:13 2009 +0200 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" from logilab.common import dictattr -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from simplejson import dumps @@ -144,17 +144,17 @@ else: action = form.action tag = ('' def display_field(self, form, field): @@ -298,7 +298,7 @@ entity = form.edited_entity values = form.form_previous_values qeid = eid_param('eid', entity.eid) - cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % html_escape(dumps(entity.eid)) + cbsetstate = "setCheckboxesState2('eid', %s, 'checked')" % xml_escape(dumps(entity.eid)) w(u'' % (entity.row % 2 and u'even' or u'odd')) # XXX turn this into a widget used on the eid field w(u'%s' % checkbox('eid', entity.eid, checked=qeid in values)) @@ -411,7 +411,7 @@ w(u'[x]' % (_('cancel this insert'), row[2])) w(u'%s' - % (row[1], row[4], html_escape(row[5]))) + % (row[1], row[4], xml_escape(row[5]))) w(u'') w(u'') w(u'' % eid) @@ -419,7 +419,7 @@ w(u'%s' % _('add relation')) w(u'' % divid) filter_hiddens(self.w, facets=','.join(wdg.facet.id for wdg in fwidgets), baserql=baserql) @@ -178,7 +178,7 @@ box = MenuWidget('', 'tableActionsBox', _class='', islist=False) label = '%s' % ( self.req.datadir_url + 'liveclipboard-icon.png', - html_escape(self.req._('action(s) on this selection'))) + xml_escape(self.req._('action(s) on this selection'))) menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox', ident='%sActions' % divid) box.append(menu) diff -r ea1a44e4ad62 -r caad2367d940 web/views/tabs.py --- a/web/views/tabs.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/tabs.py Thu Jul 16 13:30:13 2009 +0200 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb import NoSelectableObject, role from cubicweb.selectors import partial_has_related_entities @@ -47,7 +47,7 @@ elif rset: urlparams['rql'] = rset.printable_rql() w(u'
' % ( - vid, html_escape(self.build_url('json', **urlparams)))) + vid, xml_escape(self.build_url('json', **urlparams)))) if show_spinbox: w(u'%s' % (vid, self.req._('loading'))) diff -r ea1a44e4ad62 -r caad2367d940 web/views/timeline.py --- a/web/views/timeline.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/timeline.py Thu Jul 16 13:30:13 2009 +0200 @@ -11,7 +11,7 @@ import simplejson -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb.interfaces import ICalendarable from cubicweb.selectors import implements @@ -68,7 +68,7 @@ if start is None and stop is None: return None event_data = {'start': start.strftime(self.date_fmt), - 'title': html_escape(entity.dc_title()), + 'title': xml_escape(entity.dc_title()), 'description': entity.dc_description(format='text/html'), 'link': entity.absolute_url(), } @@ -95,7 +95,7 @@ additional = u'' self.w(u'
' % - (self.widget_class, html_escape(loadurl), + (self.widget_class, xml_escape(loadurl), additional)) self.w(u'
') diff -r ea1a44e4ad62 -r caad2367d940 web/views/timetable.py --- a/web/views/timetable.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/timetable.py Thu Jul 16 13:30:13 2009 +0200 @@ -6,7 +6,7 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb.interfaces import ITimetableViews from cubicweb.selectors import implements @@ -190,7 +190,7 @@ if value: task_descr, first_row = value if first_row: - url = html_escape(task_descr.task.absolute_url(vid="edition")) + url = xml_escape(task_descr.task.absolute_url(vid="edition")) self.w(u' 
' % ( task_descr.lines, task_descr.color, filled_klasses[kj], url)) task_descr.task.view('tooltip', w=self.w) diff -r ea1a44e4ad62 -r caad2367d940 web/views/treeview.py --- a/web/views/treeview.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/treeview.py Thu Jul 16 13:30:13 2009 +0200 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" from logilab.common.decorators import monkeypatch -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb.utils import make_uid from cubicweb.interfaces import ITree @@ -39,7 +39,7 @@ self.w(u'') if initial_load and not self.req.form.get('fname'): self.req.add_css('jquery.treeview.css') - self.req.add_js(('cubicweb.ajax.js', 'jquery.treeview.js')) + self.req.add_js(('cubicweb.ajax.js', 'cubicweb.widgets.js', 'jquery.treeview.js')) self.req.html_headers.add_onload(u""" jQuery("#tree-%s").treeview({toggle: toggleTree, prerendered: true});""" % treeid) @@ -113,7 +113,7 @@ w(u'
  • ' % u' '.join(liclasses)) else: rql = entity.children_rql() % {'x': entity.eid} - url = html_escape(self.build_url('json', rql=rql, vid=parentvid, + url = xml_escape(self.build_url('json', rql=rql, vid=parentvid, pageid=self.req.pageid, treeid=treeid, fname='view', diff -r ea1a44e4ad62 -r caad2367d940 web/views/workflow.py --- a/web/views/workflow.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/workflow.py Thu Jul 16 13:30:13 2009 +0200 @@ -11,7 +11,7 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from logilab.common.graph import escape, GraphGenerator, DotBackend from cubicweb import Unauthorized, view @@ -118,7 +118,7 @@ __select__ = implements('State') def cell_call(self, row, col): - self.w(html_escape(self.view('textincontext', self.rset, + self.w(xml_escape(self.view('textincontext', self.rset, row=row, col=col))) @@ -145,8 +145,8 @@ self.w(u'

    %s

    ' % (self.req._('workflow for %s') % display_name(self.req, entity.name))) self.w(u'%s' % ( - html_escape(entity.absolute_url(vid='ewfgraph')), - html_escape(self.req._('graphical workflow for %s') % entity.name))) + xml_escape(entity.absolute_url(vid='ewfgraph')), + xml_escape(self.req._('graphical workflow for %s') % entity.name))) class WorkflowDotPropsHandler(object): diff -r ea1a44e4ad62 -r caad2367d940 web/views/xbel.py --- a/web/views/xbel.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/views/xbel.py Thu Jul 16 13:30:13 2009 +0200 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from cubicweb.selectors import implements from cubicweb.view import EntityView @@ -42,8 +42,8 @@ def cell_call(self, row, col): entity = self.complete_entity(row, col) - self.w(u'' % html_escape(self.url(entity))) - self.w(u' %s' % html_escape(entity.dc_title())) + self.w(u'' % xml_escape(self.url(entity))) + self.w(u' %s' % xml_escape(entity.dc_title())) self.w(u'') def url(self, entity): diff -r ea1a44e4ad62 -r caad2367d940 web/widgets.py --- a/web/widgets.py Wed Jul 15 09:45:13 2009 +0200 +++ b/web/widgets.py Thu Jul 16 13:30:13 2009 +0200 @@ -12,7 +12,7 @@ from datetime import datetime -from logilab.mtconverter import html_escape +from logilab.mtconverter import xml_escape from yams.constraints import SizeConstraint, StaticVocabularyConstraint @@ -247,9 +247,9 @@ value = self.current_value(entity) dvalue = self.current_display_value(entity) if isinstance(value, basestring): - value = html_escape(value) + value = xml_escape(value) if isinstance(dvalue, basestring): - dvalue = html_escape(dvalue) + dvalue = xml_escape(dvalue) return u'%s' % ( self.hidden_input(entity, value), self.input_type, self.rname, dvalue, self.format_attrs()) @@ -323,9 +323,9 @@ value = self.current_value(entity) dvalue = self.current_display_value(entity) if isinstance(value, basestring): - value = html_escape(value) + value = xml_escape(value) if isinstance(dvalue, basestring): - dvalue = html_escape(dvalue) + dvalue = xml_escape(dvalue) iid = self.attrs.pop('id') if self.required(entity): cssclass = u' required' @@ -337,7 +337,7 @@ 'iid': iid, 'hidden': self.hidden_input(entity, value), 'wdgtype': self.wdgtype, - 'url': html_escape(dataurl), + 'url': xml_escape(dataurl), 'tabindex': self.attrs.pop('tabindex'), 'value': dvalue, 'attrs': self.format_attrs(), @@ -398,7 +398,7 @@ editor = self._edit_render_textarea(entity, with_format) value = self.current_value(entity) if isinstance(value, basestring): - value = html_escape(value) + value = xml_escape(value) return u'%s%s' % (self.hidden_input(entity, value), editor) def _edit_render_textarea(self, entity, with_format): @@ -406,7 +406,7 @@ self.attrs.setdefault('rows', 20) dvalue = self.current_display_value(entity) if isinstance(dvalue, basestring): - dvalue = html_escape(dvalue) + dvalue = xml_escape(dvalue) if entity.use_fckeditor(self.name): entity.req.fckeditor_config() if with_format: @@ -472,9 +472,9 @@ or entity.e_schema.has_metadata(self.name, 'encoding')): divid = '%s-%s-advanced' % (self.name, entity.eid) wdgs.append(u'%s' % - (html_escape(toggle_action(divid)), + (xml_escape(toggle_action(divid)), req._('show advanced fields'), - html_escape(req.build_url('data/puce_down.png')), + xml_escape(req.build_url('data/puce_down.png')), req._('show advanced fields'))) wdgs.append(u'