# HG changeset patch # User Sylvain Thénault # Date 1252507719 -7200 # Node ID 0e549b299f0b357837ea620c561aa843f46de17a # Parent ecead3f2cff9a4752f913d257a14f79efa89ab7a# Parent 5c07c3b2b3323c690f9e04ee8eebdc6e92ab21ff merge diff -r 5c07c3b2b332 -r 0e549b299f0b __pkginfo__.py --- a/__pkginfo__.py Wed Sep 09 16:11:07 2009 +0200 +++ b/__pkginfo__.py Wed Sep 09 16:48:39 2009 +0200 @@ -7,7 +7,7 @@ distname = "cubicweb" modname = "cubicweb" -numversion = (3, 4, 6) +numversion = (3, 4, 7) version = '.'.join(str(num) for num in numversion) license = 'LGPL v2' diff -r 5c07c3b2b332 -r 0e549b299f0b common/mail.py --- a/common/mail.py Wed Sep 09 16:11:07 2009 +0200 +++ b/common/mail.py Wed Sep 09 16:48:39 2009 +0200 @@ -21,6 +21,7 @@ return 'XXX' from cubicweb.view import EntityView +from cubicweb.entity import Entity def header(ustring): return Header(ustring.encode('UTF-8'), 'UTF-8') @@ -141,14 +142,6 @@ msgid_timestamp = True - def user_login(self): - try: - # if req is actually a session (we are on the server side), and we - # have to prevent nested internal session - return self.req.actual_session().user.login - except AttributeError: - return self.req.user.login - def recipients(self): finder = self.vreg['components'].select('recipients_finder', self.req, rset=self.rset, @@ -161,7 +154,7 @@ subject = self.req._(self.message) etype = entity.dc_type() eid = entity.eid - login = self.user_login() + login = self.user_data['login'] return self.req._('%(subject)s %(etype)s #%(eid)s (%(login)s)') % locals() def context(self, **kwargs): @@ -169,7 +162,7 @@ for key, val in kwargs.iteritems(): if val and isinstance(val, unicode) and val.strip(): kwargs[key] = self.req._(val) - kwargs.update({'user': self.user_login(), + kwargs.update({'user': self.user_data['login'], 'eid': entity.eid, 'etype': entity.dc_type(), 'url': entity.absolute_url(), @@ -183,18 +176,12 @@ return construct_message_id(self.config.appid, eid, self.msgid_timestamp) def render_emails(self, **kwargs): - """generate and send an email message for this view""" + """generate and send emails for this view (one per recipient)""" self._kwargs = kwargs recipients = self.recipients() if not recipients: self.info('skipping %s notification, no recipients', self.id) return - if not isinstance(recipients[0], tuple): - from warnings import warn - warn('recipients should now return a list of 2-uple (email, language)', - DeprecationWarning, stacklevel=1) - lang = self.vreg.property_value('ui.language') - recipients = zip(recipients, repeat(lang)) if self.rset is not None: entity = self.entity(self.row or 0, self.col or 0) # if the view is using timestamp in message ids, no way to reference @@ -208,22 +195,32 @@ else: refs = () msgid = None - userdata = self.req.user_data() - origlang = self.req.lang - for emailaddr, lang in recipients: - self.req.set_language(lang) + req = self.req + self.user_data = req.user_data() + origlang = req.lang + for something in recipients: + if isinstance(something, Entity): + # hi-jack self.req to get a session for the returned user + self.req = self.req.hijack_user(something) + emailaddr = something.get_email() + else: + emailaddr, lang = something + self.req.set_language(lang) # since the same view (eg self) may be called multiple time and we # need a fresh stream at each iteration, reset it explicitly self.w = None # XXX call render before subject to set .row/.col attributes on the # view - content = self.render(row=0, col=0, **kwargs) - subject = self.subject() - msg = format_mail(userdata, [emailaddr], content, subject, + try: + content = self.render(row=0, col=0, **kwargs) + subject = self.subject() + except SkipEmail: + continue + msg = format_mail(self.user_data, [emailaddr], content, subject, config=self.config, msgid=msgid, references=refs) yield [emailaddr], msg # restore language - self.req.set_language(origlang) + req.set_language(origlang) def render_and_send(self, **kwargs): """generate and send an email message for this view""" @@ -243,3 +240,7 @@ raise NotImplementedError send = send_now + + +class SkipEmail(Exception): + """raise this if you decide to skip an email during its generation""" diff -r 5c07c3b2b332 -r 0e549b299f0b dbapi.py --- a/dbapi.py Wed Sep 09 16:11:07 2009 +0200 +++ b/dbapi.py Wed Sep 09 16:48:39 2009 +0200 @@ -284,6 +284,12 @@ # server session compat layer ############################################# + def hijack_user(self, user): + """return a fake request/session using specified user""" + req = DBAPIRequest(self.vreg) + req.set_connection(self.cnx, user) + return req + @property def user(self): if self._user is None and self.cnx: diff -r 5c07c3b2b332 -r 0e549b299f0b debian/changelog --- a/debian/changelog Wed Sep 09 16:11:07 2009 +0200 +++ b/debian/changelog Wed Sep 09 16:48:39 2009 +0200 @@ -1,3 +1,9 @@ +cubicweb (3.4.7-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Wed, 09 Sep 2009 14:08:41 +0200 + cubicweb (3.4.6-1) unstable; urgency=low * new upstream release diff -r 5c07c3b2b332 -r 0e549b299f0b entity.py --- a/entity.py Wed Sep 09 16:11:07 2009 +0200 +++ b/entity.py Wed Sep 09 16:48:39 2009 +0200 @@ -689,14 +689,16 @@ self.set_related_cache(rtype, role, rset) return self.related(rtype, role, limit, entities) - def related_rql(self, rtype, role='subject'): + def related_rql(self, rtype, role='subject', targettypes=None): rschema = self.schema[rtype] if role == 'subject': - targettypes = rschema.objects(self.e_schema) + if targettypes is None: + targettypes = rschema.objects(self.e_schema) restriction = 'E eid %%(x)s, E %s X' % rtype card = greater_card(rschema, (self.e_schema,), targettypes, 0) else: - targettypes = rschema.subjects(self.e_schema) + if targettypes is None: + targettypes = rschema.subjects(self.e_schema) restriction = 'E eid %%(x)s, X %s E' % rtype card = greater_card(rschema, targettypes, (self.e_schema,), 1) if len(targettypes) > 1: diff -r 5c07c3b2b332 -r 0e549b299f0b i18n/en.po --- a/i18n/en.po Wed Sep 09 16:11:07 2009 +0200 +++ b/i18n/en.po Wed Sep 09 16:48:39 2009 +0200 @@ -1646,12 +1646,13 @@ msgid "download" msgstr "" +#, python-format +msgid "download %s" +msgstr "" + msgid "download icon" msgstr "" -msgid "download image" -msgstr "" - msgid "download schema as owl" msgstr "" @@ -2052,6 +2053,12 @@ msgid "invalid date" msgstr "" +msgid "invalid float value" +msgstr "" + +msgid "invalid integer value" +msgstr "" + msgid "is" msgstr "" diff -r 5c07c3b2b332 -r 0e549b299f0b i18n/es.po --- a/i18n/es.po Wed Sep 09 16:11:07 2009 +0200 +++ b/i18n/es.po Wed Sep 09 16:48:39 2009 +0200 @@ -1708,12 +1708,13 @@ msgid "download" msgstr "Descargar" +#, python-format +msgid "download %s" +msgstr "" + msgid "download icon" msgstr "ícono de descarga" -msgid "download image" -msgstr "" - msgid "download schema as owl" msgstr "Descargar esquema en OWL" @@ -2128,6 +2129,12 @@ msgid "invalid date" msgstr "Esta fecha no es válida" +msgid "invalid float value" +msgstr "" + +msgid "invalid integer value" +msgstr "" + msgid "is" msgstr "es" diff -r 5c07c3b2b332 -r 0e549b299f0b i18n/fr.po --- a/i18n/fr.po Wed Sep 09 16:11:07 2009 +0200 +++ b/i18n/fr.po Wed Sep 09 16:48:39 2009 +0200 @@ -1721,12 +1721,13 @@ msgid "download" msgstr "télécharger" +#, python-format +msgid "download %s" +msgstr "télécharger %s" + msgid "download icon" msgstr "icône de téléchargement" -msgid "download image" -msgstr "image de téléchargement" - msgid "download schema as owl" msgstr "télécharger le schéma OWL" @@ -2141,6 +2142,12 @@ msgid "invalid date" msgstr "cette date n'est pas valide" +msgid "invalid float value" +msgstr "nombre flottant non valide" + +msgid "invalid integer value" +msgstr "nombre entier non valide" + msgid "is" msgstr "de type" @@ -3199,3 +3206,6 @@ msgid "you should probably delete that property" msgstr "vous devriez probablement supprimer cette propriété" + +#~ msgid "download image" +#~ msgstr "image de téléchargement" diff -r 5c07c3b2b332 -r 0e549b299f0b server/migractions.py --- a/server/migractions.py Wed Sep 09 16:11:07 2009 +0200 +++ b/server/migractions.py Wed Sep 09 16:48:39 2009 +0200 @@ -166,14 +166,20 @@ % (self.config.appid, backupfile)): return # unpack backup - bkup = tarfile.open(backupfile, 'r|gz') - for name in bkup.getnames(): - if name[0] in '/.': - raise Exception('Security check failed, path starts with "/" or "."') - bkup.close() # XXX seek error if not close+open !?! - bkup = tarfile.open(backupfile, 'r|gz') tmpdir = tempfile.mkdtemp() - bkup.extractall(path=tmpdir) + try: + bkup = tarfile.open(backupfile, 'r|gz') + except tarfile.ReadError: + # assume restoring old backup + shutil.copy(backupfile, osp.join(tmpdir, 'system')) + else: + for name in bkup.getnames(): + if name[0] in '/.': + raise Exception('Security check failed, path starts with "/" or "."') + bkup.close() # XXX seek error if not close+open !?! + bkup = tarfile.open(backupfile, 'r|gz') + bkup.extractall(path=tmpdir) + bkup.close() self.config.open_connections_pools = False repo = self.repo_connect() @@ -186,7 +192,6 @@ print '-> error trying to restore [%s]' % exc if not self.confirm('Continue anyway?', default='n'): raise SystemExit(1) - bkup.close() shutil.rmtree(tmpdir) # call hooks repo.open_connections_pools() diff -r 5c07c3b2b332 -r 0e549b299f0b server/querier.py --- a/server/querier.py Wed Sep 09 16:11:07 2009 +0200 +++ b/server/querier.py Wed Sep 09 16:48:39 2009 +0200 @@ -608,6 +608,8 @@ # return an empty result instead of raising UnknownEid return empty_rset(session, rql, args) cachekey.append(etype) + # ensure eid is correctly typed in args + args[key] = typed_eid(args[key]) cachekey = tuple(cachekey) else: cachekey = rql diff -r 5c07c3b2b332 -r 0e549b299f0b server/session.py --- a/server/session.py Wed Sep 09 16:11:07 2009 +0200 +++ b/server/session.py Wed Sep 09 16:48:39 2009 +0200 @@ -78,16 +78,51 @@ def schema(self): return self.repo.schema - def add_relation(self, fromeid, rtype, toeid): + def hijack_user(self, user): + """return a fake request/session using specified user""" + session = Session(user, self.repo) + session._threaddata = self._threaddata + return session + + def _change_relation(self, cb, fromeid, rtype, toeid): if self.is_super_session: - self.repo.glob_add_relation(self, fromeid, rtype, toeid) + cb(self, fromeid, rtype, toeid) return self.is_super_session = True try: - self.repo.glob_add_relation(self, fromeid, rtype, toeid) + cb(self, fromeid, rtype, toeid) finally: self.is_super_session = False + def add_relation(self, fromeid, rtype, toeid): + """provide direct access to the repository method to add a relation. + + This is equivalent to the following rql query: + + SET X rtype Y WHERE X eid fromeid, T eid toeid + + without read security check but also all the burden of rql execution. + You may use this in hooks when you know both eids of the relation you + want to add. + """ + self._change_relation(self.repo.glob_add_relation, + fromeid, rtype, toeid) + def delete_relation(self, fromeid, rtype, toeid): + """provide direct access to the repository method to delete a relation. + + This is equivalent to the following rql query: + + DELETE X rtype Y WHERE X eid fromeid, T eid toeid + + without read security check but also all the burden of rql execution. + You may use this in hooks when you know both eids of the relation you + want to delete. + """ + self._change_relation(self.repo.glob_delete_relation, + fromeid, rtype, toeid) + + # relations cache handling ################################################# + def update_rel_cache_add(self, subject, rtype, object, symetric=False): self._update_entity_rel_cache_add(subject, rtype, 'subject', object) if symetric: @@ -129,8 +164,11 @@ if row[0] == targeteid: break else: - raise Exception('cache inconsistency for %s %s %s %s' % - (eid, rtype, role, targeteid)) + # this may occurs if the cache has been filed by a hook + # after the database update + self.debug('cache inconsistency for %s %s %s %s', eid, rtype, + role, targeteid) + return del rset.rows[idx] if isinstance(rset.description, list): # else description not set del rset.description[idx] diff -r 5c07c3b2b332 -r 0e549b299f0b server/test/unittest_querier.py --- a/server/test/unittest_querier.py Wed Sep 09 16:11:07 2009 +0200 +++ b/server/test/unittest_querier.py Wed Sep 09 16:48:39 2009 +0200 @@ -210,6 +210,11 @@ # should return an empty result set self.failIf(self.execute('Any X WHERE X eid 99999999')) + def test_typed_eid(self): + # should return an empty result set + rset = self.execute('Any X WHERE X eid %(x)s', {'x': '1'}, 'x') + self.assertIsInstance(rset[0][0], (int, long)) + def test_bytes_storage(self): feid = self.execute('INSERT File X: X name "foo.pdf", X data_format "text/plain", X data %(data)s', {'data': Binary("xxx")})[0][0] diff -r 5c07c3b2b332 -r 0e549b299f0b sobjects/notification.py --- a/sobjects/notification.py Wed Sep 09 16:11:07 2009 +0200 +++ b/sobjects/notification.py Wed Sep 09 16:48:39 2009 +0200 @@ -21,7 +21,7 @@ from cubicweb.server.hookhelper import SendMailOp from cubicweb.server.hooksmanager import Hook -parse_message_id = deprecated('parse_message_id is now defined in cubicweb.common.mail') +parse_message_id = deprecated('parse_message_id is now defined in cubicweb.common.mail')(parse_message_id) class RecipientsFinder(Component): @@ -190,6 +190,6 @@ def subject(self): entity = self.entity(self.row or 0, self.col or 0) return u'%s #%s (%s)' % (self.req.__('New %s' % entity.e_schema), - entity.eid, self.user_login()) + entity.eid, self.user_data['login']) NormalizedTextView = class_renamed('NormalizedTextView', ContentAddedView) diff -r 5c07c3b2b332 -r 0e549b299f0b sobjects/supervising.py --- a/sobjects/supervising.py Wed Sep 09 16:11:07 2009 +0200 +++ b/sobjects/supervising.py Wed Sep 09 16:48:39 2009 +0200 @@ -23,6 +23,8 @@ accepts = ('Any',) def call(self, session, *args): + if session.is_super_session or session.repo.config.repairing: + return # ignore changes triggered by hooks or maintainance shell dest = self.config['supervising-addrs'] if not dest: # no supervisors, don't do this for nothing... return diff -r 5c07c3b2b332 -r 0e549b299f0b test/unittest_entity.py --- a/test/unittest_entity.py Wed Sep 09 16:11:07 2009 +0200 +++ b/test/unittest_entity.py Wed Sep 09 16:48:39 2009 +0200 @@ -132,7 +132,8 @@ seschema.subject_relation('evaluee').set_rproperty(seschema, Note.e_schema, 'cardinality', '1*') # testing basic fetch_attrs attribute self.assertEquals(Personne.fetch_rql(user), - 'Any X,AA,AB,AC ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC') + 'Any X,AA,AB,AC ORDERBY AA ASC ' + 'WHERE X is Personne, X nom AA, X prenom AB, X modification_date AC') pfetch_attrs = Personne.fetch_attrs sfetch_attrs = Societe.fetch_attrs try: @@ -142,18 +143,21 @@ # testing one non final relation Personne.fetch_attrs = ('nom', 'prenom', 'travaille') self.assertEquals(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD ORDERBY AA ASC WHERE X is Personne, X nom AA, X prenom AB, X travaille AC, AC nom AD') + 'Any X,AA,AB,AC,AD ORDERBY AA ASC ' + 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD') # testing two non final relations Personne.fetch_attrs = ('nom', 'prenom', 'travaille', 'evaluee') self.assertEquals(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, ' - 'X prenom AB, X travaille AC, AC nom AD, X evaluee AE, AE modification_date AF') + 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC ' + 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD, ' + 'X evaluee AE?, AE modification_date AF') # testing one non final relation with recursion Personne.fetch_attrs = ('nom', 'prenom', 'travaille') Societe.fetch_attrs = ('nom', 'evaluee') self.assertEquals(Personne.fetch_rql(user), - 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC WHERE X is Personne, X nom AA, X prenom AB, ' - 'X travaille AC, AC nom AD, AC evaluee AE, AE modification_date AF' + 'Any X,AA,AB,AC,AD,AE,AF ORDERBY AA ASC,AF DESC ' + 'WHERE X is Personne, X nom AA, X prenom AB, X travaille AC?, AC nom AD, ' + 'AC evaluee AE?, AE modification_date AF' ) # testing symetric relation Personne.fetch_attrs = ('nom', 'connait') @@ -178,15 +182,16 @@ from cubicweb.entities import fetch_config Personne = self.vreg['etypes'].etype_class('Personne') Note = self.vreg['etypes'].etype_class('Note') + self.failUnless(issubclass(self.vreg['etypes'].etype_class('SubNote'), Note)) Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', 'type')) Note.fetch_attrs, Note.fetch_order = fetch_config(('type',)) - aff = self.add_entity('Personne', nom=u'pouet') - self.assertEquals(aff.related_rql('evaluee'), + p = self.add_entity('Personne', nom=u'pouet') + self.assertEquals(p.related_rql('evaluee'), 'Any X,AA,AB ORDERBY AA ASC WHERE E eid %(x)s, E evaluee X, ' 'X type AA, X modification_date AB') Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', )) # XXX - self.assertEquals(aff.related_rql('evaluee'), + self.assertEquals(p.related_rql('evaluee'), 'Any X,AA ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E evaluee X, X modification_date AA') def test_entity_unrelated(self): diff -r 5c07c3b2b332 -r 0e549b299f0b utils.py --- a/utils.py Wed Sep 09 16:11:07 2009 +0200 +++ b/utils.py Wed Sep 09 16:48:39 2009 +0200 @@ -7,6 +7,8 @@ """ __docformat__ = "restructuredtext en" +from logilab.mtconverter import xml_escape + import locale from md5 import md5 from datetime import datetime, timedelta, date @@ -262,17 +264,18 @@ # 2/ css files for cssfile, media in self.cssfiles: w(u'\n' % - (media, cssfile)) + (media, xml_escape(cssfile))) # 3/ ie css if necessary if self.ie_cssfiles: w(u' \n') # 4/ js files for jsfile in self.jsfiles: - w(u'\n' % jsfile) + w(u'\n' % + xml_escape(jsfile)) # 5/ post inlined scripts (i.e. scripts depending on other JS files) if self.post_inlined_scripts: w(u'