backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 04 Oct 2011 12:23:52 +0200
changeset 7907 08320697ca1a
parent 7906 203d574c8a1d (current diff)
parent 7905 32ad1c29e477 (diff)
child 7910 e5d5609e3bf1
backport stable
entity.py
server/repository.py
server/sqlutils.py
server/test/unittest_querier.py
server/test/unittest_rql2sql.py
web/views/tableview.py
--- a/doc/book/en/devweb/views/startup.rst	Fri Sep 30 18:10:28 2011 +0200
+++ b/doc/book/en/devweb/views/startup.rst	Tue Oct 04 12:23:52 2011 +0200
@@ -1,15 +1,18 @@
 Startup views
 -------------
 
-(:mod:`cubicweb.web.views.startup`)
+Startup views are views requiring no context, from which you usually start
+browsing (for instance the index page). The usual selectors are
+:class:`~cubicweb.selectors.none_rset` or :class:`~cubicweb.selectors.yes`.
 
-The usual selectors are no_rset or yes. These views don't apply to a
-result set.
+You'll find here a description of startup views provided by the framework.
 
-*index*
-    This view defines the home page of your application. It does not require
-    a result set to apply to.
+.. automodule:: cubicweb.web.views.startup
+
+
+Other startup views:
 
 *schema*
     A view dedicated to the display of the schema of the instance
 
+.. XXX to be continued
\ No newline at end of file
--- a/server/repository.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/server/repository.py	Tue Oct 04 12:23:52 2011 +0200
@@ -1177,8 +1177,7 @@
         # delete remaining relations: if user can delete the entity, he can
         # delete all its relations without security checking
         with security_enabled(session, read=False, write=False):
-            eids = [_e.eid for _e in entities]
-            in_eids = ','.join((str(eid) for eid in eids))
+            in_eids = ','.join([str(_e.eid) for _e in entities])
             for rschema, _, role in entities[0].e_schema.relation_definitions():
                 rtype = rschema.type
                 if rtype in schema.VIRTUAL_RTYPES or rtype in pendingrtypes:
@@ -1423,7 +1422,11 @@
             source = self.sources_by_uri[sourceuri]
             if source.should_call_hooks:
                 self.hm.call_hooks('before_delete_entity', session, entities=entities)
-            self._delete_info_multi(session, entities, sourceuri)
+            if session.deleted_in_transaction(source.eid):
+                # source is being deleted, think to give scleanup argument
+                self._delete_info_multi(session, entities, sourceuri, scleanup=source.eid)
+            else:
+                self._delete_info_multi(session, entities, sourceuri)
             source.delete_entities(session, entities)
             if source.should_call_hooks:
                 self.hm.call_hooks('after_delete_entity', session, entities=entities)
--- a/server/sources/pyrorql.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/server/sources/pyrorql.py	Tue Oct 04 12:23:52 2011 +0200
@@ -445,7 +445,7 @@
 
     def delete_entity(self, session, entity):
         """delete an entity from the source"""
-        if session.deleted_in_transaction (self.eid):
+        if session.deleted_in_transaction(self.eid):
             # source is being deleted, don't propagate
             self._query_cache.clear()
             return
@@ -466,7 +466,7 @@
 
     def delete_relation(self, session, subject, rtype, object):
         """delete a relation from the source"""
-        if session.deleted_in_transaction (self.eid):
+        if session.deleted_in_transaction(self.eid):
             # source is being deleted, don't propagate
             self._query_cache.clear()
             return
--- a/server/sources/rql2sql.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/server/sources/rql2sql.py	Tue Oct 04 12:23:52 2011 +0200
@@ -1426,10 +1426,18 @@
             return sql
         leftvars = cmp.children[0].get_nodes(VariableRef)
         assert len(leftvars) == 1
-        leftalias = self._var_table(leftvars[0].variable.stinfo['attrvar'])
+        if leftvars[0].variable.stinfo['attrvar'] is None:
+            assert isinstance(leftvars[0].variable, ColumnAlias)
+            leftalias = leftvars[0].variable._q_sqltable
+        else:
+            leftalias = self._var_table(leftvars[0].variable.stinfo['attrvar'])
         rightvars = cmp.children[1].get_nodes(VariableRef)
         assert len(rightvars) == 1
-        rightalias = self._var_table(rightvars[0].variable.stinfo['attrvar'])
+        if rightvars[0].variable.stinfo['attrvar'] is None:
+            assert isinstance(rightvars[0].variable, ColumnAlias)
+            rightalias = rightvars[0].variable._q_sqltable
+        else:
+            rightalias = self._var_table(rightvars[0].variable.stinfo['attrvar'])
         if optional == 'right':
             self._state.replace_tables_by_outer_join(
                 leftalias, rightalias, 'LEFT', sql)
--- a/server/sqlutils.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/server/sqlutils.py	Tue Oct 04 12:23:52 2011 +0200
@@ -338,6 +338,17 @@
         return _limit_size(text, maxsize)
     cnx.create_function("TEXT_LIMIT_SIZE", 2, limit_size2)
 
+    from logilab.common.date import strptime
+    def weekday(ustr):
+        try:
+            dt = strptime(ustr, '%Y-%m-%d %H:%M:%S')
+        except:
+            dt =  strptime(ustr, '%Y-%m-%d')
+        # expect sunday to be 1, saturday 7 while weekday method return 0 for
+        # monday
+        return (dt.weekday() + 1) % 7
+    cnx.create_function("WEEKDAY", 1, weekday)
+
     import yams.constraints
     yams.constraints.patch_sqlite_decimal()
 
--- a/server/test/unittest_querier.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/server/test/unittest_querier.py	Tue Oct 04 12:23:52 2011 +0200
@@ -443,7 +443,7 @@
         self.execute("INSERT Personne X: X nom 'foo', X datenaiss %(d)s",
                      {'d': datetime(2001, 2,3, 12,13)})
         test_data = [('YEAR', 2001), ('MONTH', 2), ('DAY', 3),
-                     ('HOUR', 12), ('MINUTE', 13)]
+                     ('HOUR', 12), ('MINUTE', 13), ('WEEKDAY', 6)]
         for funcname, result in test_data:
             rset = self.execute('Any %s(D) WHERE X is Personne, X datenaiss D'
                                 % funcname)
--- a/server/test/unittest_rql2sql.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/server/test/unittest_rql2sql.py	Tue Oct 04 12:23:52 2011 +0200
@@ -1396,6 +1396,11 @@
                     '''SELECT CAST(EXTRACT(MONTH from _P.cw_creation_date) AS INTEGER)
 FROM cw_Personne AS _P''')
 
+    def test_weekday_extraction(self):
+        self._check("Any WEEKDAY(D) WHERE P is Personne, P creation_date D",
+                    '''SELECT (CAST(EXTRACT(DOW from _P.cw_creation_date) AS INTEGER) + 1)
+FROM cw_Personne AS _P''')
+
     def test_substring(self):
         self._check("Any SUBSTRING(N, 1, 1) WHERE P nom N, P is Personne",
                     '''SELECT SUBSTR(_P.cw_nom, 1, 1)
@@ -1524,6 +1529,12 @@
 FROM (SELECT MAX(_A.cw_ordernum) AS C0
 FROM cw_CWAttribute AS _A) AS _T0, cw_CWAttribute AS _A
 WHERE _A.cw_ordernum=_T0.C0'''),
+
+            ('Any O1 HAVING O1=O2? WITH O1 BEING (Any MAX(O) WHERE A ordernum O, A is CWAttribute), O2 BEING (Any MAX(O) WHERE A ordernum O, A is CWRelation)',
+             '''SELECT _T0.C0
+FROM (SELECT MAX(_A.cw_ordernum) AS C0
+FROM cw_CWAttribute AS _A) AS _T0 LEFT OUTER JOIN (SELECT MAX(_A.cw_ordernum) AS C0
+FROM cw_CWRelation AS _A) AS _T1 ON (_T0.C0=_T1.C0)'''),
             )):
             yield t
 
@@ -1749,11 +1760,16 @@
         self._check('Any X WHERE X is CWUser, X creation_date D HAVING YEAR(D) = "2010" OR D = NULL',
                     '''SELECT _X.cw_eid
 FROM cw_CWUser AS _X
-WHERE ((YEAR(_X.cw_creation_date)=2010) OR (_X.cw_creation_date IS NULL))''')
+WHERE ((DATEPART(YEAR, _X.cw_creation_date)=2010) OR (_X.cw_creation_date IS NULL))''')
 
     def test_date_extraction(self):
         self._check("Any MONTH(D) WHERE P is Personne, P creation_date D",
-                    '''SELECT MONTH(_P.cw_creation_date)
+                    '''SELECT DATEPART(MONTH, _P.cw_creation_date)
+FROM cw_Personne AS _P''')
+
+    def test_weekday_extraction(self):
+        self._check("Any WEEKDAY(D) WHERE P is Personne, P creation_date D",
+                    '''SELECT DATEPART(WEEKDAY, _P.cw_creation_date)
 FROM cw_Personne AS _P''')
 
     def test_symmetric(self):
@@ -1893,9 +1909,9 @@
                     'GROUPBY YEAR(XECT),MONTH(XECT) ORDERBY 1 '
                     'WHERE X creation_date XSCT, X modification_date XECT, '
                     'X ordernum XCE, X is CWAttribute',
-                    '''SELECT ((YEAR(_X.cw_modification_date) * 100) + MONTH(_X.cw_modification_date)), COUNT(_X.cw_eid), SUM(_X.cw_ordernum), AVG((_X.cw_creation_date - _X.cw_modification_date))
+                    '''SELECT ((DATEPART(YEAR, _X.cw_modification_date) * 100) + DATEPART(MONTH, _X.cw_modification_date)), COUNT(_X.cw_eid), SUM(_X.cw_ordernum), AVG((_X.cw_creation_date - _X.cw_modification_date))
 FROM cw_CWAttribute AS _X
-GROUP BY YEAR(_X.cw_modification_date),MONTH(_X.cw_modification_date)
+GROUP BY DATEPART(YEAR, _X.cw_modification_date),DATEPART(MONTH, _X.cw_modification_date)
 ORDER BY 1'''),
 
 
@@ -1910,6 +1926,12 @@
                     '''SELECT MONTH(_P.cw_creation_date)
 FROM cw_Personne AS _P''')
 
+    def test_weekday_extraction(self):
+        # custom impl. in cw.server.sqlutils
+        self._check("Any WEEKDAY(D) WHERE P is Personne, P creation_date D",
+                    '''SELECT WEEKDAY(_P.cw_creation_date)
+FROM cw_Personne AS _P''')
+
     def test_regexp(self):
         self._check("Any X WHERE X login REGEXP '[0-9].*'",
                     '''SELECT _X.cw_eid
@@ -2068,6 +2090,11 @@
                     '''SELECT EXTRACT(MONTH from _P.cw_creation_date)
 FROM cw_Personne AS _P''')
 
+    def test_weekday_extraction(self):
+        self._check("Any WEEKDAY(D) WHERE P is Personne, P creation_date D",
+                    '''SELECT DAYOFWEEK(_P.cw_creation_date)
+FROM cw_Personne AS _P''')
+
     def test_cast(self):
         self._check("Any CAST(String, P) WHERE P is Personne",
                     '''SELECT CAST(_P.cw_eid AS mediumtext)
--- a/web/uicfg.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/web/uicfg.py	Tue Oct 04 12:23:52 2011 +0200
@@ -30,6 +30,16 @@
       * ``schema``
       * ``subobject`` (not displayed by default)
 
+   By default only entities on the ``application`` category are shown.
+
+.. sourcecode:: python
+
+    from cubicweb.web import uicfg
+    # force hiding
+    uicfg.indexview_etype_section['HideMe'] = 'subobject'
+    # force display
+    uicfg.indexview_etype_section['ShowMe'] = 'application'
+
 
 Actions box configuration
 `````````````````````````
--- a/web/views/pyviews.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/web/views/pyviews.py	Tue Oct 04 12:23:52 2011 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 #
 # This file is part of CubicWeb.
@@ -24,18 +24,29 @@
 
 
 class PyValTableView(View):
-    """display a list of list of values into an html table.
+    """display a list of list of values into an HTML table.
 
     Take care, content is NOT xml-escaped.
+
+    If `headers` is specfied, it is expected to be a list of headers to be
+    inserted as first row (in <thead>).
+
+    If `colheaders` is True, the first column will be considered as an headers
+    column an its values will be inserted inside <th> instead of <td>.
+
+    `cssclass` is the CSS class used on the <table> tag, and default to
+    'listing' (so that the table will look similar to those generated by the
+    table view).
     """
     __regid__ = 'pyvaltable'
     __select__ = match_kwargs('pyvalue')
 
-    def call(self, pyvalue, headers=None):
+    def call(self, pyvalue, headers=None, colheaders=False,
+             cssclass='listing'):
         if headers is None:
             headers = self._cw.form.get('headers')
         w = self.w
-        w(u'<table class="listing">\n')
+        w(u'<table class="%s">\n' % cssclass)
         if headers:
             w(u'<thead>')
             w(u'<tr>')
@@ -46,6 +57,9 @@
         w(u'<tbody>')
         for row in pyvalue:
             w(u'<tr>')
+            if colheaders:
+                w(u'<th>%s</th>' % row[0])
+                row = row[1:]
             for cell in row:
                 w(u'<td>%s</td>' % cell)
             w(u'</tr>\n')
--- a/web/views/startup.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/web/views/startup.py	Tue Oct 04 12:23:52 2011 +0200
@@ -15,8 +15,10 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""Set of HTML startup views. A startup view is global, e.g. doesn't apply to a
-result set.
+"""This module contains the default index page and management view.
+
+.. autoclass:: IndexView
+.. autoclass:: ManageView
 """
 
 __docformat__ = "restructuredtext en"
@@ -32,6 +34,19 @@
 from cubicweb.web import ajax_replace_url, uicfg, httpcache
 
 class ManageView(StartupView):
+    """:__regid__: *manage*
+
+    The manage view, display some information about what's contained by your
+    site and provides access to administration stuff such as user and groups
+    management.
+
+    Regarding the section displaying link to entity type, notice by default it
+    won't display entity types which are related to another one using a
+    mandatory (cardinality == 1) composite relation.
+
+    You can still configure that behaviour manually using the
+    `indexview_etype_section` as explained in :mod:`cubicweb.web.uicfg`.
+    """
     __regid__ = 'manage'
     title = _('manage')
     http_cache_manager = httpcache.EtagHTTPCacheManager
@@ -149,6 +164,13 @@
 
 
 class IndexView(ManageView):
+    """:__regid__: *index*
+
+    The default index view, that you'll get when accessing your site's root url.
+    It's by default indentical to the
+    :class:`~cubicweb.web.views.startup.ManageView`, but you'll usually want to
+    customize this one.
+    """
     __regid__ = 'index'
     title = _('view_index')
 
--- a/web/views/tableview.py	Fri Sep 30 18:10:28 2011 +0200
+++ b/web/views/tableview.py	Tue Oct 04 12:23:52 2011 +0200
@@ -372,6 +372,7 @@
     def cell_call(self, row, col):
         _ = self._cw._
         entity = self.cw_rset.get_entity(row, col)
+        entity.complete()
         infos = {}
         for col in self.columns:
             meth = getattr(self, 'build_%s_cell' % col, None)