backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 03 Feb 2010 14:46:32 +0100
changeset 4436 294e084f1263
parent 4434 101344a6ff9b (current diff)
parent 4433 dccf8e5eb79e (diff)
child 4437 21f2e01fdd6a
backport stable
__pkginfo__.py
devtools/__init__.py
doc/book/en/admin/setup.rst
entities/authobjs.py
entities/wfobjs.py
entity.py
etwist/server.py
i18n/fr.po
server/sqlutils.py
vregistry.py
web/data/cubicweb.edition.js
web/httpcache.py
web/views/basecontrollers.py
web/views/treeview.py
web/views/urlrewrite.py
web/views/workflow.py
--- a/.hgtags	Tue Feb 02 18:24:45 2010 +0100
+++ b/.hgtags	Wed Feb 03 14:46:32 2010 +0100
@@ -96,3 +96,5 @@
 dfe2f245248c97bea3a29c8ecc6d293e25ff708e cubicweb-debian-version-3.5.10-1
 f48b2f193961803cf42147272671a335a2daeceb cubicweb-version-3.5.11
 4920121d41f28c8075a4f00461911677396fc566 cubicweb-debian-version-3.5.11-1
+98af3d02b83e7635207781289cc3445fb0829951 cubicweb-version-3.5.12
+4281e1e2d76b9a37f38c0eeb1cbdcaa2fac6533c cubicweb-debian-version-3.5.12-1
--- a/debian/changelog	Tue Feb 02 18:24:45 2010 +0100
+++ b/debian/changelog	Wed Feb 03 14:46:32 2010 +0100
@@ -1,3 +1,9 @@
+cubicweb (3.5.12-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Tue, 02 Feb 2010 11:07:00 +0100
+
 cubicweb (3.5.11-1) unstable; urgency=low
 
   * new upstream release
--- a/doc/book/en/admin/setup.rst	Tue Feb 02 18:24:45 2010 +0100
+++ b/doc/book/en/admin/setup.rst	Wed Feb 03 14:46:32 2010 +0100
@@ -234,9 +234,9 @@
 --------------------------------
 
 This currently assumes that the instances configurations is located
-at C:\etc\cubicweb.d.
+at C:\\etc\\cubicweb.d.
 
-For a cube 'my_cube', you will then find C:\etc\cubicweb.d\my_cube\win32svc.py
+For a cube 'my_cube', you will then find C:\\etc\\cubicweb.d\\my_cube\\win32svc.py
 that has to be used thusly::
 
   win32svc install
--- a/entities/authobjs.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/entities/authobjs.py	Wed Feb 03 14:46:32 2010 +0100
@@ -83,7 +83,7 @@
     def is_in_group(self, group):
         """convience / shortcut method to test if the user belongs to `group`
         """
-        return group in self._groups
+        return group in self.groups
 
     def is_anonymous(self):
         """ checks if user is an anonymous user"""
--- a/entities/wfobjs.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/entities/wfobjs.py	Wed Feb 03 14:46:32 2010 +0100
@@ -491,7 +491,7 @@
             stateeid = statename.eid
         else:
             if not isinstance(statename, basestring):
-                warn('give a state name')
+                warn('[3.5] give a state name', DeprecationWarning)
                 state = self.current_workflow.state_by_eid(statename)
             else:
                 state = self.current_workflow.state_by_name(statename)
--- a/entity.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/entity.py	Wed Feb 03 14:46:32 2010 +0100
@@ -272,7 +272,7 @@
     def has_perm(self, action):
         return self.e_schema.has_perm(self._cw, action, eid=self.eid)
 
-    def view(self, vid, __registry='views', **kwargs):
+    def view(self, __vid, __registry='views', **kwargs):
         """shortcut to apply a view on this entity"""
         view = self._cw.vreg[__registry].select(vid, self._cw, rset=self.cw_rset,
                                                 row=self.cw_row, col=self.cw_col,
@@ -477,6 +477,7 @@
                 continue
             yield attr
 
+    _cw_completed = False
     def complete(self, attributes=None, skip_bytes=True):
         """complete this entity by adding missing attributes (i.e. query the
         repository to fill the entity)
@@ -486,6 +487,10 @@
           if true, attribute of type Bytes won't be considered
         """
         assert self.has_eid()
+        if self._cw_completed:
+            return
+        if attributes is None:
+            self._cw_completed = True
         varmaker = rqlvar_maker()
         V = varmaker.next()
         rql = ['WHERE %s eid %%(x)s' % V]
@@ -763,14 +768,23 @@
             self._related_cache.pop('%s_%s' % (rtype, role), None)
 
     def clear_all_caches(self):
+        """flush all caches on this entity. Further attributes/relations access
+        will triggers new database queries to get back values.
+
+        If you use custom caches on your entity class (take care to @cached!),
+        you should override this method to clear them as well.
+        """
+        # clear attributes cache
         haseid = 'eid' in self
+        self._cw_completed = False
         self.clear()
-        for rschema, _, role in self.e_schema.relation_definitions():
-            self.clear_related_cache(rschema.type, role)
         # set eid if it was in, else we may get nasty error while editing this
         # entity if it's bound to a repo session
         if haseid:
             self['eid'] = self.eid
+        # clear relations cache
+        for rschema, _, role in self.e_schema.relation_definitions():
+            self.clear_related_cache(rschema.type, role)
 
     # raw edition utilities ###################################################
 
--- a/etwist/request.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/etwist/request.py	Wed Feb 03 14:46:32 2010 +0100
@@ -113,7 +113,7 @@
 
     def header_if_modified_since(self):
         """If the HTTP header If-modified-since is set, return the equivalent
-        mx date time value (GMT), else return None
+        date time value (GMT), else return None
         """
         mtime = self.get_header('If-modified-since', raw=False)
         if mtime:
--- a/etwist/server.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/etwist/server.py	Wed Feb 03 14:46:32 2010 +0100
@@ -10,6 +10,7 @@
 import sys
 import os
 import select
+import errno
 import hotshot
 from time import mktime
 from datetime import date, timedelta
--- a/i18n/fr.po	Tue Feb 02 18:24:45 2010 +0100
+++ b/i18n/fr.po	Wed Feb 03 14:46:32 2010 +0100
@@ -752,7 +752,11 @@
 "a RQL expression which should return some results, else the transition won't "
 "be available. This query may use X and U variables that will respectivly "
 "represents the current entity and the current user"
-msgstr "une expression RQL devant retourner des résultats pour que la transition puisse être passée. Cette expression peut utiliser les variables X et U qui représentent respectivement l'entité à laquelle on veut appliquer la transition et l'utilisateur courant."
+msgstr ""
+"une expression RQL devant retourner des résultats pour que la transition "
+"puisse être passée. Cette expression peut utiliser les variables X et U qui "
+"représentent respectivement l'entité à laquelle on veut appliquer la "
+"transition et l'utilisateur courant."
 
 msgid "a URI representing an object in external data store"
 msgstr "une Uri désignant un objet dans un entrepôt de données externe"
@@ -1262,7 +1266,8 @@
 msgstr "boîte d'actions"
 
 msgid "boxes_edit_box_description"
-msgstr "boîte affichant les différentes actions possibles sur les données affichées"
+msgstr ""
+"boîte affichant les différentes actions possibles sur les données affichées"
 
 msgid "boxes_filter_box"
 msgstr "filtrer"
--- a/vregistry.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/vregistry.py	Wed Feb 03 14:46:32 2010 +0100
@@ -122,7 +122,6 @@
             # use classid() to compare classes because vreg will probably
             # have its own version of the class, loaded through execfile
             if classid(registered) == clsid:
-                # XXX automatic reloading management
                 self[oid].remove(registered)
                 break
         else:
--- a/web/data/cubicweb.edition.js	Tue Feb 02 18:24:45 2010 +0100
+++ b/web/data/cubicweb.edition.js	Wed Feb 03 14:46:32 2010 +0100
@@ -434,7 +434,7 @@
  */
 function setFormsTarget(node) {
     var $node = jQuery(node || document.body);
-    $node.find('form.entityForm').each(function () {
+    $node.find('form').each(function () {
 	var form = jQuery(this);
 	var target = form.attr('cubicweb:target');
 	if (target) {
--- a/web/httpcache.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/web/httpcache.py	Wed Feb 03 14:46:32 2010 +0100
@@ -8,6 +8,7 @@
 """
 __docformat__ = "restructuredtext en"
 
+from time import mktime
 from datetime import datetime
 
 # time delta usable to convert localized time to GMT time
@@ -23,6 +24,7 @@
     def set_headers(self):
         self.req.set_header('Cache-control', 'no-cache')
 
+
 class MaxAgeHTTPCacheManager(NoHTTPCacheManager):
     """max-age cache manager: set max-age cache control policy, with max-age
     specified with the `cache_max_age` attribute of the view
@@ -31,6 +33,7 @@
         self.req.set_header('Cache-control',
                             'max-age=%s' % self.view.cache_max_age)
 
+
 class EtagHTTPCacheManager(NoHTTPCacheManager):
     """etag based cache manager for startup views
 
@@ -38,8 +41,6 @@
     * set policy to 'must-revalidate' and expires to the current time to force
       revalidation on each request
     """
-    # GMT time required
-    date_format = "%a, %d %b %Y %H:%M:%S GMT"
 
     def etag(self):
         return self.view.__regid__ + '/' + ','.join(sorted(self.req.user.groups))
@@ -49,6 +50,7 @@
         return 0
 
     def last_modified(self):
+        """return view's last modified GMT time"""
         return self.view.last_modified()
 
     def set_headers(self):
@@ -61,7 +63,12 @@
         req.set_header('Cache-control',
                        'must-revalidate;max-age=%s' % self.max_age())
         mdate = self.last_modified()
-        req.set_header('Last-modified', mdate.strftime(self.date_format))
+        # use a timestamp, not a formatted raw header, and let
+        # the front-end correctly generate it
+        # ("%a, %d %b %Y %H:%M:%S GMT" return localized date that
+        # twisted don't parse correctly)
+        req.set_header('Last-modified', mktime(mdate.timetuple()), raw=False)
+
 
 class EntityHTTPCacheManager(EtagHTTPCacheManager):
     """etag based cache manager for view displaying a single entity
@@ -99,6 +106,7 @@
     self.http_cache_manager(self).set_headers()
 viewmod.View.set_http_cache_headers = set_http_cache_headers
 
+
 def last_modified(self):
     """return the date/time where this view should be considered as
     modified. Take care of possible related objects modifications.
@@ -111,16 +119,13 @@
         mtime = self._cw.header_if_modified_since()
         if mtime:
             tdelta = (ctime - mtime)
-            if tdelta.days * 24*60*60 + tdelta.seconds > self.cache_max_age:
-                mtime = ctime
-        else:
-            mtime = ctime
-    else:
-        mtime = ctime
+            if tdelta.days * 24*60*60 + tdelta.seconds <= self.cache_max_age:
+                return mtime
     # mtime = ctime will force page rerendering
-    return mtime
+    return ctime
 viewmod.View.last_modified = last_modified
 
+
 # configure default caching
 viewmod.View.http_cache_manager = NoHTTPCacheManager
 # max-age=0 to actually force revalidation when needed
--- a/web/views/basecontrollers.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/web/views/basecontrollers.py	Wed Feb 03 14:46:32 2010 +0100
@@ -487,6 +487,7 @@
             cookies[statename] = ';'.join(marked)
             self._cw.set_cookie(cookies, statename)
 
+    @jsonize
     def js_set_cookie(self, cookiename, cookievalue):
         # XXX we should consider jQuery.Cookie
         cookiename, cookievalue = str(cookiename), str(cookievalue)
--- a/web/views/treeview.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/web/views/treeview.py	Wed Feb 03 14:46:32 2010 +0100
@@ -59,9 +59,14 @@
             self._init_headers(treeid, toplevel_thru_ajax)
             ulid = ' id="tree-%s"' % treeid
         self.w(u'<ul%s class="%s">' % (ulid, self.css_classes))
-        for rowidx in xrange(len(self.cw_rset)):
-            self.wview(self.itemvid, self.cw_rset, row=rowidx, col=0,
-                       vid=subvid, parentvid=self.__regid__, treeid=treeid, **morekwargs)
+        for i, entity in enumerate(sorted(self.cw_rset.entities(),
+                                          key=lambda x: x.dc_title())):
+            if i+1 < len(self.cw_rset):
+                morekwargs['is_last'] = False
+            else:
+                morekwargs['is_last'] = True
+            entity.view(self.itemvid, vid=subvid, parentvid=self.__regid__,
+                        treeid=treeid, w=self.w, **morekwargs)
         self.w(u'</ul>')
 
     def cell_call(self, *args, **allargs):
@@ -71,6 +76,7 @@
         allargs.pop('col')
         self.call(*args, **allargs)
 
+
 class FileTreeView(TreeView):
     """specific version of the treeview to display file trees
     """
@@ -103,7 +109,7 @@
     """default treeitem view for entities which don't implement ITree"""
     __regid__ = 'treeitemview'
 
-    def cell_call(self, row, col, vid='oneline', parentvid='treeview', treeid=None):
+    def cell_call(self, row, col, vid='oneline', treeid=None, **morekwargs):
         assert treeid is not None
         entity = self.cw_rset.get_entity(row, col)
         itemview = self._cw.view(vid, self.cw_rset, row=row, col=col)
@@ -129,11 +135,11 @@
             return str(eeid) in treestate.value.split(';')
         return self.default_branch_state_is_open
 
-    def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview', **morekwargs):
+    def cell_call(self, row, col, treeid, vid='oneline', parentvid='treeview',
+                  is_last=False, **morekwargs):
         w = self.w
         entity = self.cw_rset.get_entity(row, col)
         liclasses = []
-        is_last = row == len(self.cw_rset) - 1
         is_open = self.open_state(entity.eid, treeid)
         is_leaf = not hasattr(entity, 'is_leaf') or entity.is_leaf()
         if is_leaf:
--- a/web/views/urlrewrite.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/web/views/urlrewrite.py	Wed Feb 03 14:46:32 2010 +0100
@@ -83,6 +83,8 @@
         ('/notfound', dict(vid='404')),
         ('/error', dict(vid='error')),
         ('/sparql', dict(vid='sparql')),
+        # XXX should be case insensitive as 'create', but I would like to find another way than
+        # relying on the etype_selector
         (rgx('/schema/([^/]+?)/?'),  dict(vid='primary', rql=r'Any X WHERE X is CWEType, X name "\1"')),
         (rgx('/add/([^/]+?)/?'), dict(vid='creation', etype=r'\1')),
         (rgx('/doc/images/(.+?)/?'), dict(vid='wdocimages', fid=r'\1')),
--- a/web/views/workflow.py	Tue Feb 02 18:24:45 2010 +0100
+++ b/web/views/workflow.py	Wed Feb 03 14:46:32 2010 +0100
@@ -67,7 +67,7 @@
         form = self.get_form(entity, transition)
         self.w(u'<h4>%s %s</h4>\n' % (self._cw._(transition.name),
                                       entity.view('oneline')))
-        msg = _('status will change from %(st1)s to %(st2)s') % {
+        msg = self.req._('status will change from %(st1)s to %(st2)s') % {
             'st1': entity.printable_state,
             'st2': self._cw._(transition.destination().name)}
         self.w(u'<p>%s</p>\n' % msg)