backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 16 Mar 2012 10:29:15 +0100
changeset 8307 8be58694f416
parent 8306 4da49700b06a (current diff)
parent 8305 2279e02e62be (diff)
child 8308 805a257709f6
backport stable
__pkginfo__.py
debian/control
entity.py
misc/migration/3.12.0_Any.py
server/migractions.py
server/sources/storages.py
test/unittest_entity.py
web/views/basetemplates.py
web/views/calendar.py
--- a/.hgtags	Thu Mar 15 14:26:12 2012 +0100
+++ b/.hgtags	Fri Mar 16 10:29:15 2012 +0100
@@ -245,3 +245,8 @@
 4d0f5d18e8a07ab218efe90d758af723ea4a1b2b cubicweb-debian-version-3.14.3-1
 508645a542870cb0def9c43056e5084ff8def5ca cubicweb-version-3.14.4
 bc40991b7f13642d457f5ca80ac1486c29e25a6e cubicweb-debian-version-3.14.4-1
+4c8cb2e9d0ee13af1d584e2920d1ae76f47380e9 cubicweb-debian-version-3.14.4-2
+f559ab9602e7eeb4996ac0f83d544a6e0374e204 cubicweb-version-3.14.5
+55fc796ed5d5f31245ae60bd148c9e42657a1af6 cubicweb-debian-version-3.14.5-1
+db021578232b885dc5e55dfca045332ce01e7f35 cubicweb-version-3.14.6
+75364c0994907764715bd5011f6a59d934dbeb7d cubicweb-debian-version-3.14.6-1
--- a/debian/changelog	Thu Mar 15 14:26:12 2012 +0100
+++ b/debian/changelog	Fri Mar 16 10:29:15 2012 +0100
@@ -1,3 +1,21 @@
+cubicweb (3.14.6-1) unstable; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Tue, 13 Mar 2012 14:21:04 +0100
+
+cubicweb (3.14.5-1) unstable; urgency=low
+
+  * New upstream release
+
+ -- David Douard <david.douard@logilab.fr>  Thu, 01 Mar 2012 15:29:29 +0100
+
+cubicweb (3.14.4-2) unstable; urgency=low
+
+  * add missing build-deps to generate the documentation
+
+ -- David Douard <david.douard@logilab.fr>  Wed, 29 Feb 2012 17:00:52 +0100
+
 cubicweb (3.14.4-1) unstable; urgency=low
 
   * New upstream release
--- a/debian/control	Thu Mar 15 14:26:12 2012 +0100
+++ b/debian/control	Fri Mar 16 10:29:15 2012 +0100
@@ -7,7 +7,7 @@
            Adrien Di Mascio <Adrien.DiMascio@logilab.fr>,
            Aurélien Campéas <aurelien.campeas@logilab.fr>,
            Nicolas Chauvat <nicolas.chauvat@logilab.fr>
-Build-Depends: debhelper (>= 7), python (>= 2.5), python-central (>= 0.5), python-sphinx
+Build-Depends: debhelper (>= 7), python (>= 2.5), python-central (>= 0.5), python-sphinx, python-logilab-common, python-unittest2
 # for the documentation:
 # python-sphinx, python-logilab-common, python-unittest2, logilab-doctools, logilab-xml
 Standards-Version: 3.9.1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/debian/watch	Fri Mar 16 10:29:15 2012 +0100
@@ -0,0 +1,2 @@
+version=3
+http://download.logilab.org/pub/cubicweb cubicweb-(.*)\.tar\.gz
--- a/entity.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/entity.py	Fri Mar 16 10:29:15 2012 +0100
@@ -635,11 +635,12 @@
         mainattr, needcheck = self.cw_rest_attr_info()
         etype = str(self.e_schema)
         path = etype.lower()
+        fallback = False
         if mainattr != 'eid':
             value = getattr(self, mainattr)
             if not can_use_rest_path(value):
                 mainattr = 'eid'
-                path += '/eid'
+                path = None
             elif needcheck:
                 # make sure url is not ambiguous
                 try:
@@ -650,12 +651,16 @@
                     nbresults = self.__unique = self._cw.execute(rql, {'value' : value})[0][0]
                 if nbresults != 1: # ambiguity?
                     mainattr = 'eid'
-                    path += '/eid'
+                    path = None
         if mainattr == 'eid':
             if use_ext_eid:
                 value = self.cw_metainformation()['extid']
             else:
                 value = self.eid
+        if path is None:
+            # fallback url: <base-url>/<eid> url is used as cw entities uri,
+            # prefer it to <base-url>/<etype>/eid/<eid>
+            return unicode(value)
         return '%s/%s' % (path, self._cw.url_quote(value))
 
     def cw_attr_metadata(self, attr, metadata):
--- a/misc/migration/3.12.0_Any.py	Thu Mar 15 14:26:12 2012 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-if schema['TZDatetime'].eid is None:
-    add_entity_type('TZDatetime')
-if schema['TZTime'].eid is None:
-    add_entity_type('TZTime')
--- a/misc/migration/3.14.4_Any.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/misc/migration/3.14.4_Any.py	Fri Mar 16 10:29:15 2012 +0100
@@ -4,6 +4,7 @@
 rdefdef = schema['CWSource'].rdef('name')
 attrtype = y2sql.type_from_constraints(dbhelper, rdefdef.object, rdefdef.constraints).split()[0]
 
-sql(dbhelper.sql_change_col_type('entities', 'asource', attrtype, False))
-sql(dbhelper.sql_change_col_type('entities', 'source', attrtype, False))
-sql(dbhelper.sql_change_col_type('deleted_entities', 'source', attrtype, False))
+cursor = session.cnxset['system']
+dbhelper.change_col_type(cursor, 'entities', 'asource', attrtype, False)
+dbhelper.change_col_type(cursor, 'entities', 'source', attrtype, False)
+dbhelper.change_col_type(cursor, 'deleted_entities', 'source', attrtype, False)
--- a/misc/migration/bootstrapmigration_repository.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/misc/migration/bootstrapmigration_repository.py	Fri Mar 16 10:29:15 2012 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -40,6 +40,13 @@
     sql('UPDATE entities SET asource=cw_name  '
         'FROM cw_CWSource, cw_source_relation '
         'WHERE entities.eid=cw_source_relation.eid_from AND cw_source_relation.eid_to=cw_CWSource.cw_eid')
+    commit()
+
+if schema['TZDatetime'].eid is None:
+    add_entity_type('TZDatetime', auto=False)
+if schema['TZTime'].eid is None:
+    add_entity_type('TZTime', auto=False)
+
 
 if applcubicwebversion <= (3, 14, 0) and cubicwebversion >= (3, 14, 0):
     if 'require_permission' in schema and not 'localperms'in repo.config.cubes():
--- a/rqlrewrite.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/rqlrewrite.py	Fri Mar 16 10:29:15 2012 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -228,39 +228,44 @@
                                            if not r in sti['rhsrelations'])
                 else:
                     vi['rhs_rels'] = vi['lhs_rels'] = {}
-        parent = None
+        previous = None
         inserted = False
         for rqlexpr in rqlexprs:
             self.current_expr = rqlexpr
             if varexistsmap is None:
                 try:
-                    new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, parent)
+                    new = self.insert_snippet(varmap, rqlexpr.snippet_rqlst, previous)
                 except Unsupported:
                     continue
                 inserted = True
                 if new is not None and self._insert_scope is None:
                     self.exists_snippet[rqlexpr] = new
-                parent = parent or new
+                previous = previous or new
             else:
                 # called to reintroduce snippet due to ambiguity creation,
                 # so skip snippets which are not introducing this ambiguity
                 exists = varexistsmap[varmap]
-                if self.exists_snippet[rqlexpr] is exists:
+                if self.exists_snippet.get(rqlexpr) is exists:
                     self.insert_snippet(varmap, rqlexpr.snippet_rqlst, exists)
         if varexistsmap is None and not inserted:
             # no rql expression found matching rql solutions. User has no access right
             raise Unauthorized() # XXX may also be because of bad constraints in schema definition
 
-    def insert_snippet(self, varmap, snippetrqlst, parent=None):
+    def insert_snippet(self, varmap, snippetrqlst, previous=None):
         new = snippetrqlst.where.accept(self)
         existing = self.existingvars
         self.existingvars = None
         try:
-            return self._insert_snippet(varmap, parent, new)
+            return self._insert_snippet(varmap, previous, new)
         finally:
             self.existingvars = existing
 
-    def _insert_snippet(self, varmap, parent, new):
+    def _insert_snippet(self, varmap, previous, new):
+        """insert `new` snippet into the syntax tree, which have been rewritten
+        using `varmap`. In cases where an action is protected by several rql
+        expresssion, `previous` will be the first rql expression which has been
+        inserted, and so should be ORed with the following expressions.
+        """
         if new is not None:
             if self._insert_scope is None:
                 insert_scope = None
@@ -274,28 +279,28 @@
                 insert_scope = self._insert_scope
             if self._insert_scope is None and any(vi.get('stinfo', {}).get('optrelations')
                                                   for vi in self.varinfos):
-                assert parent is None
-                self._insert_scope = self.snippet_subquery(varmap, new)
+                assert previous is None
+                self._insert_scope, new = self.snippet_subquery(varmap, new)
                 self.insert_pending()
                 #self._insert_scope = None
-                return
+                return new
             if not isinstance(new, (n.Exists, n.Not)):
                 new = n.Exists(new)
-            if parent is None:
+            if previous is None:
                 insert_scope.add_restriction(new)
             else:
-                grandpa = parent.parent
-                or_ = n.Or(parent, new)
-                grandpa.replace(parent, or_)
+                grandpa = previous.parent
+                or_ = n.Or(previous, new)
+                grandpa.replace(previous, or_)
             if not self.removing_ambiguity:
                 try:
                     self.compute_solutions()
                 except Unsupported:
                     # some solutions have been lost, can't apply this rql expr
-                    if parent is None:
+                    if previous is None:
                         self.current_statement().remove_node(new, undefine=True)
                     else:
-                        grandpa.replace(or_, parent)
+                        grandpa.replace(or_, previous)
                         self._cleanup_inserted(new)
                     raise
                 else:
@@ -419,7 +424,7 @@
             # some solutions have been lost, can't apply this rql expr
             self.select.remove_subquery(self.select.with_[-1])
             raise
-        return subselect
+        return subselect, snippetrqlst
 
     def remove_ambiguities(self, snippets, newsolutions):
         # the snippet has introduced some ambiguities, we have to resolve them
@@ -476,11 +481,17 @@
 
     def _cleanup_inserted(self, node):
         # cleanup inserted variable references
+        removed = set()
         for vref in node.iget_nodes(n.VariableRef):
             vref.unregister_reference()
             if not vref.variable.stinfo['references']:
                 # no more references, undefine the variable
                 del self.select.defined_vars[vref.name]
+                removed.add(vref.name)
+        for key, newvar in self.rewritten.items(): # I mean items we alter it
+            if newvar in removed:
+                del self.rewritten[key]
+
 
     def _may_be_shared_with(self, sniprel, target):
         """if the snippet relation can be skipped to use a relation from the
--- a/server/migractions.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/server/migractions.py	Fri Mar 16 10:29:15 2012 +0100
@@ -1039,17 +1039,15 @@
             gmap = self.group_mapping()
             cmap = self.cstrtype_mapping()
             done = set()
-            for rdef in rschema.rdefs.itervalues():
-                if not (reposchema.has_entity(rdef.subject)
-                        and reposchema.has_entity(rdef.object)):
+            for subj, obj in rschema.rdefs:
+                if not (reposchema.has_entity(subj)
+                        and reposchema.has_entity(obj)):
                     continue
                 # symmetric relations appears twice
-                if (rdef.subject, rdef.object) in done:
+                if (subj, obj) in done:
                     continue
-                done.add( (rdef.subject, rdef.object) )
-                self._set_rdef_eid(rdef)
-                ss.execschemarql(execute, rdef,
-                                 ss.rdef2rql(rdef, cmap, gmap))
+                done.add( (subj, obj) )
+                self.cmd_add_relation_definition(subj, rtype, obj)
             if rtype in META_RTYPES:
                 # if the relation is in META_RTYPES, ensure we're adding it for
                 # all entity types *in the persistent schema*, not only those in
@@ -1099,9 +1097,8 @@
             print 'warning: relation %s %s %s is already known, skip addition' % (
                 subjtype, rtype, objtype)
             return
-        execute = self._cw.execute
         rdef = self._get_rdef(rschema, subjtype, objtype)
-        ss.execschemarql(execute, rdef,
+        ss.execschemarql(self._cw.execute, rdef,
                          ss.rdef2rql(rdef, self.cstrtype_mapping(),
                                      self.group_mapping()))
         if commit:
@@ -1115,7 +1112,7 @@
             schemaobj = getattr(rdef, attr)
             if getattr(schemaobj, 'eid', None) is None:
                 schemaobj.eid =  self.repo.schema[schemaobj].eid
-                assert schemaobj.eid is not None
+                assert schemaobj.eid is not None, schemaobj
         return rdef
 
     def cmd_drop_relation_definition(self, subjtype, rtype, objtype, commit=True):
--- a/server/sources/storages.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/server/sources/storages.py	Fri Mar 16 10:29:15 2012 +0100
@@ -194,16 +194,16 @@
                 # Mark the new file as added during the transaction.
                 # The file will be removed on rollback
                 AddFileOp.get_instance(entity._cw).add_data(fpath)
-        if oldpath != fpath:
-            # register the new location for the file.
+            # reinstall poped value
             if fpath is None:
                 entity.cw_edited.edited_attribute(attr, None)
             else:
+                # register the new location for the file.
                 entity.cw_edited.edited_attribute(attr, Binary(fpath))
+        if oldpath is not None and oldpath != fpath:
             # Mark the old file as useless so the file will be removed at
             # commit.
-            if oldpath is not None:
-                DeleteFileOp.get_instance(entity._cw).add_data(oldpath)
+            DeleteFileOp.get_instance(entity._cw).add_data(oldpath)
         return binary
 
     def entity_deleted(self, entity, attr):
--- a/server/test/unittest_multisources.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/server/test/unittest_multisources.py	Fri Mar 16 10:29:15 2012 +0100
@@ -337,7 +337,7 @@
         ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
         self.cnx2.commit()
         lc = self.sexecute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
-        self.assertEqual(lc.absolute_url(), 'http://extern.org/card/eid/%s' % ceid)
+        self.assertEqual(lc.absolute_url(), 'http://extern.org/%s' % ceid)
         cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid})
         self.cnx2.commit()
 
@@ -346,7 +346,7 @@
         ceid = cu.execute('INSERT Card X: X title "without wikiid to get eid based url"')[0][0]
         self.cnx3.commit()
         lc = self.sexecute('Card X WHERE X title "without wikiid to get eid based url"').get_entity(0, 0)
-        self.assertEqual(lc.absolute_url(), 'http://testing.fr/cubicweb/card/eid/%s' % lc.eid)
+        self.assertEqual(lc.absolute_url(), 'http://testing.fr/cubicweb/%s' % lc.eid)
         cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid})
         self.cnx3.commit()
 
--- a/test/unittest_entity.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/test/unittest_entity.py	Fri Mar 16 10:29:15 2012 +0100
@@ -673,18 +673,18 @@
         # ambiguity test
         person2 = req.create_entity('Personne', prenom=u'remi', nom=u'doe')
         person.cw_clear_all_caches()
-        self.assertEqual(person.rest_path(), 'personne/eid/%s' % person.eid)
-        self.assertEqual(person2.rest_path(), 'personne/eid/%s' % person2.eid)
+        self.assertEqual(person.rest_path(), unicode(person.eid))
+        self.assertEqual(person2.rest_path(), unicode(person2.eid))
         # unique attr with None value (wikiid in this case)
         card1 = req.create_entity('Card', title=u'hop')
-        self.assertEqual(card1.rest_path(), 'card/eid/%s' % card1.eid)
+        self.assertEqual(card1.rest_path(), unicode(card1.eid))
         # don't use rest if we have /, ? or & in the path (breaks mod_proxy)
         card2 = req.create_entity('Card', title=u'pod', wikiid=u'zo/bi')
-        self.assertEqual(card2.rest_path(), 'card/eid/%d' % card2.eid)
+        self.assertEqual(card2.rest_path(), unicode(card2.eid))
         card3 = req.create_entity('Card', title=u'pod', wikiid=u'zo&bi')
-        self.assertEqual(card3.rest_path(), 'card/eid/%d' % card3.eid)
+        self.assertEqual(card3.rest_path(), unicode(card3.eid))
         card4 = req.create_entity('Card', title=u'pod', wikiid=u'zo?bi')
-        self.assertEqual(card4.rest_path(), 'card/eid/%d' % card4.eid)
+        self.assertEqual(card4.rest_path(), unicode(card4.eid))
 
 
     def test_set_attributes(self):
@@ -723,7 +723,7 @@
         req = self.request()
         card = req.create_entity('Card', wikiid=u'', title=u'test')
         self.assertEqual(card.absolute_url(),
-                          'http://testing.fr/cubicweb/card/eid/%s' % card.eid)
+                          'http://testing.fr/cubicweb/%s' % card.eid)
 
     def test_create_entity(self):
         req = self.request()
--- a/test/unittest_rqlrewrite.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/test/unittest_rqlrewrite.py	Fri Mar 16 10:29:15 2012 +0100
@@ -183,9 +183,9 @@
         self.assertEqual(rqlst.as_string(),
                          "Any A,C,T WHERE A documented_by C?, A is Affaire "
                          "WITH C,T BEING (Any C,T WHERE C title T, "
-                         "EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F), "
-                         "D eid %(A)s, C is Card, "
-                         "EXISTS(C in_state E, E name 'public'))")
+                         "(EXISTS(C in_state B, D in_group F, G require_state B, G name 'read', G require_group F)) "
+                         "OR (EXISTS(C in_state E, E name 'public')), "
+                         "D eid %(A)s, C is Card)")
 
     def test_optional_var_4(self):
         constraint1 = 'A created_by U, X documented_by A'
@@ -199,8 +199,8 @@
                              u'Any X,LA,Y WHERE LA? documented_by X, LA concerne Y, B eid %(C)s, '
                              'EXISTS(X created_by B), EXISTS(Y created_by B), '
                              'X is Card, Y is IN(Division, Note, Societe) '
-                             'WITH LA BEING (Any LA WHERE EXISTS(A created_by B, LA documented_by A), '
-                             'B eid %(D)s, LA is Affaire, EXISTS(E created_by B, LA concerne E))')
+                             'WITH LA BEING (Any LA WHERE (EXISTS(A created_by B, LA documented_by A)) OR (EXISTS(E created_by B, LA concerne E)), '
+                             'B eid %(D)s, LA is Affaire)')
 
     def test_optional_var_inlined(self):
         c1 = ('X require_permission P')
@@ -431,6 +431,33 @@
         self.assertEqual(rqlst.as_string(),
                          u'Any A WHERE NOT EXISTS(A documented_by C, EXISTS(C owned_by B, B login "hop", B is CWUser), C is Card), A is Affaire')
 
+    def test_rqlexpr_multiexpr_outerjoin(self):
+        c1 = RRQLExpression('X owned_by Z, Z login "hop"', 'X')
+        c2 = RRQLExpression('X owned_by Z, Z login "hip"', 'X')
+        c3 = RRQLExpression('X owned_by Z, Z login "momo"', 'X')
+        rqlst = rqlhelper.parse('Any A WHERE A documented_by C?', annotate=False)
+        rewrite(rqlst, {('C', 'X'): (c1, c2, c3)}, {}, 'X')
+        self.assertEqual(rqlst.as_string(),
+                         u'Any A WHERE A documented_by C?, A is Affaire '
+                         'WITH C BEING (Any C WHERE ((EXISTS(C owned_by B, B login "hop")) '
+                         'OR (EXISTS(C owned_by D, D login "momo"))) '
+                         'OR (EXISTS(C owned_by A, A login "hip")), C is Card)')
+
+    def test_multiple_erql_one_bad(self):
+        #: reproduce bug #2236985
+        #: (rqlrewrite fails to remove rewritten entry for unsupported constraint and then crash)
+        #:
+        #: This check a very rare code path triggered by the four condition below
+
+        # 1. c_ok introduce an ambiguity
+        c_ok = ERQLExpression('X concerne R')
+        # 2. c_bad is just plain wrong and won't be kept
+        # 3. but it declare a new variable
+        # 4. this variable require a rewrite
+        c_bad = ERQLExpression('X documented_by R, A in_state R')
+
+        rqlst = parse('Any A, R WHERE A ref R, S is Affaire')
+        rewrite(rqlst, {('A', 'X'): (c_ok, c_bad)}, {})
 
 if __name__ == '__main__':
     unittest_main()
--- a/uilib.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/uilib.py	Fri Mar 16 10:29:15 2012 +0100
@@ -108,7 +108,7 @@
     elif value.days > 2 or value.days < -2:
         return req._('%d days') % int(value.days)
     else:
-        minus = 1 if value.days > 0 else -1
+        minus = 1 if value.days >= 0 else -1
         if value.seconds > 3600:
             return req._('%d hours') % (int(value.seconds // 3600) * minus)
         elif value.seconds >= 120:
--- a/web/data/cubicweb.old.css	Thu Mar 15 14:26:12 2012 +0100
+++ b/web/data/cubicweb.old.css	Fri Mar 16 10:29:15 2012 +0100
@@ -161,6 +161,9 @@
   color: #000;
   background-color: #f2f2f2;
   border: 1px solid #ccc;
+  margin: 10px 0;
+  padding-bottom: 12px;
+  padding-left: 5px;
 }
 
 code {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/fullcalendar.locale.js	Fri Mar 16 10:29:15 2012 +0100
@@ -0,0 +1,38 @@
+/*
+ translations for fullCalendar plugin
+ */
+
+$.fullCalendar.regional = function(lng, options){
+    var defaults = {'fr' : {
+     monthNames:
+       ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
+     monthNamesShort: ['janv.','févr.','mars','avr.','mai','juin','juil.','août','sept.','oct.','nov.','déc.'],
+     dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
+     dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
+     titleFormat: {
+ 	month: 'MMMM yyyy', // ex : Janvier 2010
+ 	week: "d[ MMMM][ yyyy]{ - d MMMM yyyy}", // ex : 10 — 16 Janvier 2010,
+ 	day: 'dddd d MMMM yyyy' // ex : Jeudi 14 Janvier 2010
+     },
+     columnFormat: {'month': 'dddd',
+                  'agendaWeek': 'dddd dd/M/yyyy',
+                  'agendaDay': 'dddd dd/M/yyyy'},
+     axisFormat: 'H:mm',
+     timeFormat: {
+	'': 'H:mm',
+	agenda: 'H:mm{ - H:mm}'},
+     allDayText: 'journée',
+     axisFormat: 'H:mm',
+     buttonText: {
+        today: "aujourd'hui",
+        month: 'mois',
+        week: 'semaine',
+       day: 'jour'
+     }
+  }};
+  if(lng in defaults){
+    return $.extend({}, defaults[lng], options);
+   }
+   else {return options;};
+  };
+;
\ No newline at end of file
--- a/web/test/unittest_views_basetemplates.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/web/test/unittest_views_basetemplates.py	Fri Mar 16 10:29:15 2012 +0100
@@ -35,6 +35,13 @@
         self.set_option('allow-email-login', 'no')
         self.assertEqual(self._login_labels(), ['login', 'password'])
 
+
+class MainNoTopTemplateTC(CubicWebTC):
+
+    def test_valid_xhtml(self):
+        self.view('index', template='main-no-top')
+
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/web/views/basetemplates.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/web/views/basetemplates.py	Fri Mar 16 10:29:15 2012 +0100
@@ -256,10 +256,10 @@
         whead(u'\n'.join(additional_headers) + u'\n')
         self.wview('htmlheader', rset=self.cw_rset)
         w = self.w
-        w(u'<title>%s</title>\n' % xml_escape(page_title))
+        whead(u'<title>%s</title>\n' % xml_escape(page_title))
         w(u'<body>\n')
         w(u'<div id="page">')
-        w(u'<table width="100%" height="100%" border="0"><tr>\n')
+        w(u'<table width="100%" border="0" id="mainLayout"><tr>\n')
         w(u'<td id="navColumnLeft">\n')
         self.topleft_header()
         boxes = list(self._cw.vreg['ctxcomponents'].poss_visible_objects(
@@ -270,11 +270,7 @@
                 box.render(w=w)
             self.w(u'</div>\n')
         w(u'</td>')
-        w(u'<td id="contentcol" rowspan="2">')
-        w(u'<div id="pageContent">\n')
-        vtitle = self._cw.form.get('vtitle')
-        if vtitle:
-            w(u'<div class="vtitle">%s</div>' % xml_escape(vtitle))
+        w(u'<td id="contentColumn" rowspan="2">')
 
     def topleft_header(self):
         logo = self._cw.vreg['components'].select_or_none('logo', self._cw,
--- a/web/views/calendar.py	Thu Mar 15 14:26:12 2012 +0100
+++ b/web/views/calendar.py	Fri Mar 16 10:29:15 2012 +0100
@@ -178,42 +178,31 @@
 
     fullcalendar_options = {
         'firstDay': 1,
+        'firstHour': 8,
+        'defaultView': 'month',
+        'editable': True,
         'header': {'left': 'prev,next today',
                    'center': 'title',
                    'right': 'month,agendaWeek,agendaDay',
                    },
-        'editable': True,
-        'defaultView': 'month',
-        'timeFormat': {'month': '',
-                       '': 'H:mm'},
-        'firstHour': 8,
-        'axisFormat': 'H:mm',
-        'columnFormat': {'month': 'dddd',
-                         'agendaWeek': 'dddd yyyy/M/dd',
-                         'agendaDay': 'dddd yyyy/M/dd'}
         }
 
-
     def call(self):
         self._cw.demote_to_html()
         self._cw.add_css(('fullcalendar.css', 'cubicweb.calendar.css'))
-        self._cw.add_js(('jquery.ui.js', 'fullcalendar.min.js', 'jquery.qtip.min.js'))
+        self._cw.add_js(('jquery.ui.js', 'fullcalendar.min.js', 'jquery.qtip.min.js', 'fullcalendar.locale.js'))
         self.calendar_id = 'cal' + make_uid('uid')
         self.add_onload()
         # write calendar div to load jquery fullcalendar object
         self.w(u'<div id="%s"></div>' % self.calendar_id)
 
-
     def add_onload(self):
         fullcalendar_options = self.fullcalendar_options.copy()
         fullcalendar_options['events'] = self.get_events()
-        fullcalendar_options['buttonText'] = {'today': self._cw._('today'),
-                                              'month': self._cw._('month'),
-                                              'week': self._cw._('week'),
-                                              'day': self._cw._('day')}
+        # i18n
         # js callback to add a tooltip and to put html in event's title
         js = """
-        var options = %s;
+        var options = $.fullCalendar.regional('%s', %s);
         options.eventRender = function(event, $element) {
           // add a tooltip for each event
           var div = '<div class="tooltip">'+ event.description+ '</div>';
@@ -223,8 +212,7 @@
         };
         $("#%s").fullCalendar(options);
         """ #"
-        self._cw.add_onload(js % (json_dumps(fullcalendar_options), self.calendar_id))
-
+        self._cw.add_onload(js % (self._cw.lang, json_dumps(fullcalendar_options), self.calendar_id))
 
     def get_events(self):
         events = []