' % entity.printable_value('name'))
if entity.display_cw_logo():
- self.w(u'')
+ self.w(u'')
if entity.description:
self.w(u'
%s
' % entity.printable_value('description'))
@@ -522,7 +522,7 @@
def render_entity_attributes(self, entity):
if entity.display_cw_logo():
- self.w(u'')
+ self.w(u'')
if entity.description:
self.w(u'
%s
' % entity.printable_value('description'))
diff -r 3a98422df969 -r 7ceb0971c694 doc/tutorials/base/discovering-the-ui.rst
--- a/doc/tutorials/base/discovering-the-ui.rst Tue Dec 15 19:05:41 2015 +0100
+++ b/doc/tutorials/base/discovering-the-ui.rst Wed Dec 16 11:23:48 2015 +0100
@@ -101,16 +101,16 @@
You can achieve the same thing by following the same path as we did for the blog
creation, e.g. by clicking on the `[+]` at the left of the 'Blog entry' link on
-the index page. The diffidence being that since there is no context information,
+the index page. The difference being that since there is no context information,
the 'blog entry of' selector won't be preset to the blog.
If you click on the 'modify' link of the action box, you are back to
the form to edit the entity you just created, except that the form now
has another section with a combo-box entitled 'add relation'. It
-provisos a generic way to edit relations which don't appears in the
+provides a generic way to edit relations which don't appears in the
above form. Choose the relation you want to add and a second combo box
-appears where you can pick existing entities. If there are too many
+appears where you can pick existing entities. If there are too many
of them, you will be offered to navigate to the target entity, that is
go away from the form and go back to it later, once you've selected
the entity you want to link with.
diff -r 3a98422df969 -r 7ceb0971c694 doc/tutorials/dataimport/diseasome_import.py
--- a/doc/tutorials/dataimport/diseasome_import.py Tue Dec 15 19:05:41 2015 +0100
+++ b/doc/tutorials/dataimport/diseasome_import.py Wed Dec 16 11:23:48 2015 +0100
@@ -95,7 +95,7 @@
# Perform a first commit, of the entities
store.flush()
kwargs = {}
- for uri, relations in all_relations.iteritems():
+ for uri, relations in all_relations.items():
from_eid = uri_to_eid.get(uri)
# ``subjtype`` should be initialized if ``SQLGenObjectStore`` is used
# and there are inlined relations in the schema.
@@ -108,7 +108,7 @@
kwargs['subjtype'] = uri_to_etype.get(uri)
if not from_eid:
continue
- for rtype, rels in relations.iteritems():
+ for rtype, rels in relations.items():
if rtype in ('classes', 'possible_drugs', 'omim', 'omim_page',
'chromosomal_location', 'same_as', 'gene_id',
'hgnc_id', 'hgnc_page'):
diff -r 3a98422df969 -r 7ceb0971c694 doc/tutorials/dataimport/diseasome_parser.py
--- a/doc/tutorials/dataimport/diseasome_parser.py Tue Dec 15 19:05:41 2015 +0100
+++ b/doc/tutorials/dataimport/diseasome_parser.py Wed Dec 16 11:23:48 2015 +0100
@@ -97,4 +97,4 @@
entities[subj]['relations'].setdefault(MAPPING_RELS[rel], set())
entities[subj]['relations'][MAPPING_RELS[rel]].add(unicode(obj))
return ((ent.get('attributes'), ent.get('relations'))
- for ent in entities.itervalues())
+ for ent in entities.values())
diff -r 3a98422df969 -r 7ceb0971c694 entities/__init__.py
--- a/entities/__init__.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/__init__.py Wed Dec 16 11:23:48 2015 +0100
@@ -19,8 +19,12 @@
__docformat__ = "restructuredtext en"
+from warnings import warn
+
+from six import text_type, string_types
from logilab.common.decorators import classproperty
+from logilab.common.deprecation import deprecated
from cubicweb import Unauthorized
from cubicweb.entity import Entity
@@ -35,7 +39,7 @@
@classproperty
def cw_etype(cls):
"""entity type as a unicode string"""
- return unicode(cls.__regid__)
+ return text_type(cls.__regid__)
@classmethod
def cw_create_url(cls, req, **kwargs):
@@ -43,6 +47,7 @@
return req.build_url('add/%s' % cls.__regid__, **kwargs)
@classmethod
+ @deprecated('[3.22] use cw_fti_index_rql_limit instead')
def cw_fti_index_rql_queries(cls, req):
"""return the list of rql queries to fetch entities to FT-index
@@ -60,6 +65,37 @@
return ['Any %s WHERE %s' % (', '.join(selected),
', '.join(restrictions))]
+ @classmethod
+ def cw_fti_index_rql_limit(cls, req, limit=1000):
+ """generate rsets of entities to FT-index
+
+ By default, each successive result set is limited to 1000 entities
+ """
+ if cls.cw_fti_index_rql_queries.__func__ != AnyEntity.cw_fti_index_rql_queries.__func__:
+ warn("[3.22] cw_fti_index_rql_queries is replaced by cw_fti_index_rql_limit",
+ DeprecationWarning)
+ for rql in cls.cw_fti_index_rql_queries(req):
+ yield req.execute(rql)
+ return
+ restrictions = ['X is %s' % cls.__regid__]
+ selected = ['X']
+ start = 0
+ for attrschema in sorted(cls.e_schema.indexable_attributes()):
+ varname = attrschema.type.upper()
+ restrictions.append('X %s %s' % (attrschema, varname))
+ selected.append(varname)
+ while True:
+ q_restrictions = restrictions + ['X eid > %s' % start]
+ rset = req.execute('Any %s ORDERBY X LIMIT %s WHERE %s' %
+ (', '.join(selected),
+ limit,
+ ', '.join(q_restrictions)))
+ if rset:
+ start = rset[-1][0]
+ yield rset
+ else:
+ break
+
# meta data api ###########################################################
def dc_title(self):
@@ -134,7 +170,7 @@
return self.dc_title().lower()
value = self.cw_attr_value(rtype)
# do not restrict to `unicode` because Bytes will return a `str` value
- if isinstance(value, basestring):
+ if isinstance(value, string_types):
return self.printable_value(rtype, format='text/plain').lower()
return value
diff -r 3a98422df969 -r 7ceb0971c694 entities/adapters.py
--- a/entities/adapters.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/adapters.py Wed Dec 16 11:23:48 2015 +0100
@@ -1,4 +1,4 @@
-# copyright 2010-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2010-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -18,19 +18,15 @@
"""some basic entity adapter implementations, for interfaces used in the
framework itself.
"""
-
-__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from itertools import chain
-from warnings import warn
from hashlib import md5
from logilab.mtconverter import TransformError
from logilab.common.decorators import cached
-from cubicweb import ValidationError, view, ViolatedConstraint
-from cubicweb.schema import CONSTRAINTS
+from cubicweb import ValidationError, view, ViolatedConstraint, UniqueTogetherError
from cubicweb.predicates import is_instance, relation_possible, match_exception
@@ -63,8 +59,8 @@
NOTE: the dictionary keys should match the list returned by the
`allowed_massmail_keys` method.
"""
- return dict( (attr, getattr(self.entity, attr))
- for attr in self.allowed_massmail_keys() )
+ return dict((attr, getattr(self.entity, attr))
+ for attr in self.allowed_massmail_keys())
class INotifiableAdapter(view.EntityAdapter):
@@ -156,40 +152,46 @@
if role == 'subject':
for entity_ in getattr(entity, rschema.type):
merge_weight_dict(words, entity_.cw_adapt_to('IFTIndexable').get_words())
- else: # if role == 'object':
+ else: # if role == 'object':
for entity_ in getattr(entity, 'reverse_%s' % rschema.type):
merge_weight_dict(words, entity_.cw_adapt_to('IFTIndexable').get_words())
return words
+
def merge_weight_dict(maindict, newdict):
- for weight, words in newdict.iteritems():
+ for weight, words in newdict.items():
maindict.setdefault(weight, []).extend(words)
+
class IDownloadableAdapter(view.EntityAdapter):
"""interface for downloadable entities"""
__regid__ = 'IDownloadable'
__abstract__ = True
- def download_url(self, **kwargs): # XXX not really part of this interface
- """return a URL to download entity's content"""
+ def download_url(self, **kwargs): # XXX not really part of this interface
+ """return a URL to download entity's content
+
+ It should be a unicode object containing url-encoded ASCII.
+ """
raise NotImplementedError
def download_content_type(self):
- """return MIME type of the downloadable content"""
+ """return MIME type (unicode) of the downloadable content"""
raise NotImplementedError
def download_encoding(self):
- """return encoding of the downloadable content"""
+ """return encoding (unicode) of the downloadable content"""
raise NotImplementedError
def download_file_name(self):
- """return file name of the downloadable content"""
+ """return file name (unicode) of the downloadable content"""
raise NotImplementedError
def download_data(self):
- """return actual data of the downloadable content"""
+ """return actual data (bytes) of the downloadable content"""
raise NotImplementedError
+
# XXX should propose to use two different relations for children/parent
class ITreeAdapter(view.EntityAdapter):
"""This adapter provides a tree interface.
@@ -337,7 +339,7 @@
try:
# check we are not jumping to another tree
if (adapter.tree_relation != self.tree_relation or
- adapter.child_role != self.child_role):
+ adapter.child_role != self.child_role):
break
entity = adapter.parent()
adapter = entity.cw_adapt_to('ITree')
@@ -347,9 +349,34 @@
return path
+class ISerializableAdapter(view.EntityAdapter):
+ """Adapter to serialize an entity to a bare python structure that may be
+ directly serialized to e.g. JSON.
+ """
+
+ __regid__ = 'ISerializable'
+ __select__ = is_instance('Any')
+
+ def serialize(self):
+ entity = self.entity
+ entity.complete()
+ data = {
+ 'cw_etype': entity.cw_etype,
+ 'cw_source': entity.cw_metainformation()['source']['uri'],
+ }
+ for rschema, __ in entity.e_schema.attribute_definitions():
+ attr = rschema.type
+ try:
+ value = entity.cw_attr_cache[attr]
+ except KeyError:
+ # Bytes
+ continue
+ data[attr] = value
+ return data
+
+
# error handling adapters ######################################################
-from cubicweb import UniqueTogetherError
class IUserFriendlyError(view.EntityAdapter):
__regid__ = 'IUserFriendlyError'
@@ -380,13 +407,14 @@
__select__ = match_exception(ViolatedConstraint)
def raise_user_exception(self):
- _ = self._cw._
cstrname = self.exc.cstrname
eschema = self.entity.e_schema
for rschema, attrschema in eschema.attribute_definitions():
rdef = rschema.rdef(eschema, attrschema)
for constraint in rdef.constraints:
- if cstrname == 'cstr' + md5(eschema.type + rschema.type + constraint.type() + (constraint.serialize() or '')).hexdigest():
+ if cstrname == 'cstr' + md5(
+ (eschema.type + rschema.type + constraint.type() +
+ (constraint.serialize() or '')).encode('ascii')).hexdigest():
break
else:
continue
diff -r 3a98422df969 -r 7ceb0971c694 entities/authobjs.py
--- a/entities/authobjs.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/authobjs.py Wed Dec 16 11:23:48 2015 +0100
@@ -19,6 +19,8 @@
__docformat__ = "restructuredtext en"
+from six import string_types
+
from logilab.common.decorators import cached
from cubicweb import Unauthorized
@@ -126,7 +128,7 @@
:type groups: str or iterable(str)
:param groups: a group name or an iterable on group names
"""
- if isinstance(groups, basestring):
+ if isinstance(groups, string_types):
groups = frozenset((groups,))
elif isinstance(groups, (tuple, list)):
groups = frozenset(groups)
diff -r 3a98422df969 -r 7ceb0971c694 entities/lib.py
--- a/entities/lib.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/lib.py Wed Dec 16 11:23:48 2015 +0100
@@ -19,9 +19,10 @@
__docformat__ = "restructuredtext en"
from warnings import warn
+from datetime import datetime
-from urlparse import urlsplit, urlunsplit
-from datetime import datetime
+from six.moves import range
+from six.moves.urllib.parse import urlsplit, urlunsplit
from logilab.mtconverter import xml_escape
@@ -67,7 +68,7 @@
{'y': self.eid})
if skipeids is None:
skipeids = set()
- for i in xrange(len(rset)):
+ for i in range(len(rset)):
eid = rset[i][0]
if eid in skipeids:
continue
@@ -146,4 +147,3 @@
if date:
return date > self.timestamp
return False
-
diff -r 3a98422df969 -r 7ceb0971c694 entities/sources.py
--- a/entities/sources.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/sources.py Wed Dec 16 11:23:48 2015 +0100
@@ -42,7 +42,7 @@
cfg.update(config)
options = SOURCE_TYPES[self.type].options
sconfig = SourceConfiguration(self._cw.vreg.config, options=options)
- for opt, val in cfg.iteritems():
+ for opt, val in cfg.items():
try:
sconfig.set_option(opt, val)
except OptionError:
diff -r 3a98422df969 -r 7ceb0971c694 entities/test/unittest_base.py
--- a/entities/test/unittest_base.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/test/unittest_base.py Wed Dec 16 11:23:48 2015 +0100
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -21,6 +21,7 @@
from logilab.common.testlib import unittest_main
from logilab.common.decorators import clear_cache
+from logilab.common.registry import yes
from cubicweb.devtools.testlib import CubicWebTC
@@ -45,13 +46,13 @@
self.assertEqual(entity.dc_creator(), u'member')
def test_type(self):
- #dc_type may be translated
+ # dc_type may be translated
with self.admin_access.client_cnx() as cnx:
member = cnx.entity_from_eid(self.membereid)
self.assertEqual(member.dc_type(), 'CWUser')
def test_cw_etype(self):
- #cw_etype is never translated
+ # cw_etype is never translated
with self.admin_access.client_cnx() as cnx:
member = cnx.entity_from_eid(self.membereid)
self.assertEqual(member.cw_etype, 'CWUser')
@@ -60,16 +61,37 @@
# XXX move to yams
self.assertEqual(self.schema['CWUser'].meta_attributes(), {})
self.assertEqual(dict((str(k), v)
- for k, v in self.schema['State'].meta_attributes().iteritems()),
- {'description_format': ('format', 'description')})
+ for k, v in self.schema['State'].meta_attributes().items()),
+ {'description_format': ('format', 'description')})
def test_fti_rql_method(self):
+ class EmailAddress(AnyEntity):
+ __regid__ = 'EmailAddress'
+ __select__ = AnyEntity.__select__ & yes(2)
+
+ @classmethod
+ def cw_fti_index_rql_queries(cls, req):
+ return ['EmailAddress Y']
+
with self.admin_access.web_request() as req:
+ req.create_entity('EmailAddress', address=u'foo@bar.com')
eclass = self.vreg['etypes'].etype_class('EmailAddress')
+ # deprecated
self.assertEqual(['Any X, ADDRESS, ALIAS WHERE X is EmailAddress, '
'X address ADDRESS, X alias ALIAS'],
eclass.cw_fti_index_rql_queries(req))
+ self.assertEqual(['Any X, ADDRESS, ALIAS ORDERBY X LIMIT 1000 WHERE X is EmailAddress, '
+ 'X address ADDRESS, X alias ALIAS, X eid > 0'],
+ [rset.rql for rset in eclass.cw_fti_index_rql_limit(req)])
+
+ # test backwards compatibility with custom method
+ with self.temporary_appobjects(EmailAddress):
+ self.vreg['etypes'].clear_caches()
+ eclass = self.vreg['etypes'].etype_class('EmailAddress')
+ self.assertEqual(['EmailAddress Y'],
+ [rset.rql for rset in eclass.cw_fti_index_rql_limit(req)])
+
class EmailAddressTC(BaseEntityTC):
@@ -87,14 +109,16 @@
self.assertEqual(email3.prefered.eid, email3.eid)
def test_mangling(self):
+ query = 'INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"'
with self.admin_access.repo_cnx() as cnx:
- email = cnx.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0)
+ email = cnx.execute(query).get_entity(0, 0)
self.assertEqual(email.display_address(), 'maarten.ter.huurne@philips.com')
self.assertEqual(email.printable_value('address'), 'maarten.ter.huurne@philips.com')
self.vreg.config.global_set_option('mangle-emails', True)
try:
self.assertEqual(email.display_address(), 'maarten.ter.huurne at philips dot com')
- self.assertEqual(email.printable_value('address'), 'maarten.ter.huurne at philips dot com')
+ self.assertEqual(email.printable_value('address'),
+ 'maarten.ter.huurne at philips dot com')
email = cnx.execute('INSERT EmailAddress X: X address "syt"').get_entity(0, 0)
self.assertEqual(email.display_address(), 'syt')
self.assertEqual(email.printable_value('address'), 'syt')
@@ -110,6 +134,7 @@
self.assertEqual(email.printable_value('address', format='text/plain'),
'maarten&ter@philips.com')
+
class CWUserTC(BaseEntityTC):
def test_complete(self):
@@ -147,10 +172,11 @@
with self.admin_access.repo_cnx() as cnx:
e = cnx.execute('CWUser U WHERE U login "member"').get_entity(0, 0)
# Bytes/Password attributes should be omitted
- self.assertEqual(e.cw_adapt_to('IEmailable').allowed_massmail_keys(),
- set(('surname', 'firstname', 'login', 'last_login_time',
- 'creation_date', 'modification_date', 'cwuri', 'eid'))
- )
+ self.assertEqual(
+ e.cw_adapt_to('IEmailable').allowed_massmail_keys(),
+ set(('surname', 'firstname', 'login', 'last_login_time',
+ 'creation_date', 'modification_date', 'cwuri', 'eid'))
+ )
def test_cw_instantiate_object_relation(self):
""" a weird non regression test """
@@ -193,7 +219,7 @@
# no specific class for Subdivisions, the default one should be selected
eclass = self.select_eclass('SubDivision')
self.assertTrue(eclass.__autogenerated__)
- #self.assertEqual(eclass.__bases__, (AnyEntity,))
+ # self.assertEqual(eclass.__bases__, (AnyEntity,))
# build class from most generic to most specific and make
# sure the most specific is always selected
self.vreg._loadedmods[__name__] = {}
@@ -212,5 +238,24 @@
eclass = self.select_eclass('Division')
self.assertEqual(eclass.cw_etype, 'Division')
+
+class ISerializableTC(CubicWebTC):
+
+ def test_serialization(self):
+ with self.admin_access.repo_cnx() as cnx:
+ entity = cnx.create_entity('CWGroup', name=u'tmp')
+ cnx.commit()
+ serializer = entity.cw_adapt_to('ISerializable')
+ expected = {
+ 'cw_etype': u'CWGroup',
+ 'cw_source': 'system',
+ 'cwuri': u'http://testing.fr/cubicweb/%s' % entity.eid,
+ 'creation_date': entity.creation_date,
+ 'modification_date': entity.modification_date,
+ 'name': u'tmp',
+ }
+ self.assertEqual(serializer.serialize(), expected)
+
+
if __name__ == '__main__':
unittest_main()
diff -r 3a98422df969 -r 7ceb0971c694 entities/test/unittest_wfobjs.py
--- a/entities/test/unittest_wfobjs.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/test/unittest_wfobjs.py Wed Dec 16 11:23:48 2015 +0100
@@ -107,7 +107,7 @@
def setup_database(self):
rschema = self.schema['in_state']
- for rdef in rschema.rdefs.itervalues():
+ for rdef in rschema.rdefs.values():
self.assertEqual(rdef.cardinality, '1*')
with self.admin_access.client_cnx() as cnx:
self.member_eid = self.create_user(cnx, 'member').eid
diff -r 3a98422df969 -r 7ceb0971c694 entities/wfobjs.py
--- a/entities/wfobjs.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entities/wfobjs.py Wed Dec 16 11:23:48 2015 +0100
@@ -21,9 +21,11 @@
* workflow history (TrInfo)
* adapter for workflowable entities (IWorkflowableAdapter)
"""
+from __future__ import print_function
__docformat__ = "restructuredtext en"
+from six import text_type, string_types
from logilab.common.decorators import cached, clear_cache
from logilab.common.deprecation import deprecated
@@ -97,7 +99,7 @@
def transition_by_name(self, trname):
rset = self._cw.execute('Any T, TN WHERE T name TN, T name %(n)s, '
'T transition_of WF, WF eid %(wf)s',
- {'n': unicode(trname), 'wf': self.eid})
+ {'n': text_type(trname), 'wf': self.eid})
if rset:
return rset.get_entity(0, 0)
return None
@@ -114,7 +116,7 @@
def add_state(self, name, initial=False, **kwargs):
"""add a state to this workflow"""
- state = self._cw.create_entity('State', name=unicode(name), **kwargs)
+ state = self._cw.create_entity('State', name=text_type(name), **kwargs)
self._cw.execute('SET S state_of WF WHERE S eid %(s)s, WF eid %(wf)s',
{'s': state.eid, 'wf': self.eid})
if initial:
@@ -126,7 +128,7 @@
def _add_transition(self, trtype, name, fromstates,
requiredgroups=(), conditions=(), **kwargs):
- tr = self._cw.create_entity(trtype, name=unicode(name), **kwargs)
+ tr = self._cw.create_entity(trtype, name=text_type(name), **kwargs)
self._cw.execute('SET T transition_of WF '
'WHERE T eid %(t)s, WF eid %(wf)s',
{'t': tr.eid, 'wf': self.eid})
@@ -224,19 +226,19 @@
matches = user.matching_groups(groups)
if matches:
if DBG:
- print 'may_be_fired: %r may fire: user matches %s' % (self.name, groups)
+ print('may_be_fired: %r may fire: user matches %s' % (self.name, groups))
return matches
if 'owners' in groups and user.owns(eid):
if DBG:
- print 'may_be_fired: %r may fire: user is owner' % self.name
+ print('may_be_fired: %r may fire: user is owner' % self.name)
return True
# check one of the rql expression conditions matches if any
if self.condition:
if DBG:
- print ('my_be_fired: %r: %s' %
- (self.name, [(rqlexpr.expression,
+ print('my_be_fired: %r: %s' %
+ (self.name, [(rqlexpr.expression,
rqlexpr.check_expression(self._cw, eid))
- for rqlexpr in self.condition]))
+ for rqlexpr in self.condition]))
for rqlexpr in self.condition:
if rqlexpr.check_expression(self._cw, eid):
return True
@@ -256,13 +258,13 @@
for gname in requiredgroups:
rset = self._cw.execute('SET T require_group G '
'WHERE T eid %(x)s, G name %(gn)s',
- {'x': self.eid, 'gn': unicode(gname)})
+ {'x': self.eid, 'gn': text_type(gname)})
assert rset, '%s is not a known group' % gname
- if isinstance(conditions, basestring):
+ if isinstance(conditions, string_types):
conditions = (conditions,)
for expr in conditions:
- if isinstance(expr, basestring):
- kwargs = {'expr': unicode(expr)}
+ if isinstance(expr, string_types):
+ kwargs = {'expr': text_type(expr)}
else:
assert isinstance(expr, dict)
kwargs = expr
@@ -414,7 +416,7 @@
"""return the default workflow for entities of this type"""
# XXX CWEType method
wfrset = self._cw.execute('Any WF WHERE ET default_workflow WF, '
- 'ET name %(et)s', {'et': unicode(self.entity.cw_etype)})
+ 'ET name %(et)s', {'et': text_type(self.entity.cw_etype)})
if wfrset:
return wfrset.get_entity(0, 0)
self.warning("can't find any workflow for %s", self.entity.cw_etype)
@@ -479,7 +481,7 @@
'Any T,TT, TN WHERE S allowed_transition T, S eid %(x)s, '
'T type TT, T type %(type)s, '
'T name TN, T transition_of WF, WF eid %(wfeid)s',
- {'x': self.current_state.eid, 'type': unicode(type),
+ {'x': self.current_state.eid, 'type': text_type(type),
'wfeid': self.current_workflow.eid})
for tr in rset.entities():
if tr.may_be_fired(self.entity.eid):
@@ -528,7 +530,7 @@
def _get_transition(self, tr):
assert self.current_workflow
- if isinstance(tr, basestring):
+ if isinstance(tr, string_types):
_tr = self.current_workflow.transition_by_name(tr)
assert _tr is not None, 'not a %s transition: %s' % (
self.__regid__, tr)
@@ -549,7 +551,7 @@
tr = self._get_transition(tr)
if any(tr_ for tr_ in self.possible_transitions()
if tr_.eid == tr.eid):
- self.fire_transition(tr)
+ self.fire_transition(tr, comment, commentformat)
def change_state(self, statename, comment=None, commentformat=None, tr=None):
"""change the entity's state to the given state (name or entity) in
diff -r 3a98422df969 -r 7ceb0971c694 entity.py
--- a/entity.py Tue Dec 15 19:05:41 2015 +0100
+++ b/entity.py Wed Dec 16 11:23:48 2015 +0100
@@ -22,6 +22,9 @@
from warnings import warn
from functools import partial
+from six import text_type, string_types, integer_types
+from six.moves import range
+
from logilab.common.decorators import cached
from logilab.common.deprecation import deprecated
from logilab.common.registry import yes
@@ -57,7 +60,7 @@
"""return True if value can be used at the end of a Rest URL path"""
if value is None:
return False
- value = unicode(value)
+ value = text_type(value)
# the check for ?, /, & are to prevent problems when running
# behind Apache mod_proxy
if value == u'' or u'?' in value or u'/' in value or u'&' in value:
@@ -105,7 +108,7 @@
"""
st = cstr.snippet_rqlst.copy()
# replace relations in ST by eid infos from linkto where possible
- for (info_rtype, info_role), eids in lt_infos.iteritems():
+ for (info_rtype, info_role), eids in lt_infos.items():
eid = eids[0] # NOTE: we currently assume a pruned lt_info with only 1 eid
for rel in st.iget_nodes(RqlRelation):
targetvar = rel_matches(rel, info_rtype, info_role, evar.name)
@@ -132,7 +135,7 @@
def pruned_lt_info(eschema, lt_infos):
pruned = {}
- for (lt_rtype, lt_role), eids in lt_infos.iteritems():
+ for (lt_rtype, lt_role), eids in lt_infos.items():
# we can only use lt_infos describing relation with a cardinality
# of value 1 towards the linked entity
if not len(eids) == 1:
@@ -144,6 +147,7 @@
pruned[(lt_rtype, lt_role)] = eids
return pruned
+
class Entity(AppObject):
"""an entity instance has e_schema automagically set on
the class and instances has access to their issuing cursor.
@@ -279,7 +283,7 @@
select = Select()
mainvar = select.get_variable(mainvar)
select.add_selected(mainvar)
- elif isinstance(mainvar, basestring):
+ elif isinstance(mainvar, string_types):
assert mainvar in select.defined_vars
mainvar = select.get_variable(mainvar)
# eases string -> syntax tree test transition: please remove once stable
@@ -455,7 +459,7 @@
if len(value) == 0:
continue # avoid crash with empty IN clause
elif len(value) == 1:
- value = iter(value).next()
+ value = next(iter(value))
else:
# prepare IN clause
pendingrels.append( (attr, role, value) )
@@ -530,6 +534,7 @@
def __init__(self, req, rset=None, row=None, col=0):
AppObject.__init__(self, req, rset=rset, row=row, col=col)
self._cw_related_cache = {}
+ self._cw_adapters_cache = {}
if rset is not None:
self.eid = rset[row][col]
else:
@@ -545,12 +550,12 @@
raise NotImplementedError('comparison not implemented for %s' % self.__class__)
def __eq__(self, other):
- if isinstance(self.eid, (int, long)):
+ if isinstance(self.eid, integer_types):
return self.eid == other.eid
return self is other
def __hash__(self):
- if isinstance(self.eid, (int, long)):
+ if isinstance(self.eid, integer_types):
return self.eid
return super(Entity, self).__hash__()
@@ -567,10 +572,7 @@
return None if it can not be adapted.
"""
- try:
- cache = self._cw_adapters_cache
- except AttributeError:
- self._cw_adapters_cache = cache = {}
+ cache = self._cw_adapters_cache
try:
return cache[interface]
except KeyError:
@@ -677,8 +679,8 @@
if path is None:
# fallback url: / url is used as cw entities uri,
# prefer it to //eid/
- return unicode(value)
- return '%s/%s' % (path, self._cw.url_quote(value))
+ return text_type(value)
+ return u'%s/%s' % (path, self._cw.url_quote(value))
def cw_attr_metadata(self, attr, metadata):
"""return a metadata for an attribute (None if unspecified)"""
@@ -695,7 +697,7 @@
attr = str(attr)
if value is _marker:
value = getattr(self, attr)
- if isinstance(value, basestring):
+ if isinstance(value, string_types):
value = value.strip()
if value is None or value == '': # don't use "not", 0 is an acceptable value
return u''
@@ -849,7 +851,7 @@
if attributes is None:
self._cw_completed = True
varmaker = rqlvar_maker()
- V = varmaker.next()
+ V = next(varmaker)
rql = ['WHERE %s eid %%(x)s' % V]
selected = []
for attr in (attributes or self._cw_to_complete_attributes(skip_bytes, skip_pwd)):
@@ -857,7 +859,7 @@
if attr in self.cw_attr_cache:
continue
# case where attribute must be completed, but is not yet in entity
- var = varmaker.next()
+ var = next(varmaker)
rql.append('%s %s %s' % (V, attr, var))
selected.append((attr, var))
# +1 since this doesn't include the main variable
@@ -876,7 +878,7 @@
# * user has read perm on the relation and on the target entity
assert rschema.inlined
assert role == 'subject'
- var = varmaker.next()
+ var = next(varmaker)
# keep outer join anyway, we don't want .complete to crash on
# missing mandatory relation (see #1058267)
rql.append('%s %s %s?' % (V, rtype, var))
@@ -892,10 +894,10 @@
raise Exception('unable to fetch attributes for entity with eid %s'
% self.eid)
# handle attributes
- for i in xrange(1, lastattr):
+ for i in range(1, lastattr):
self.cw_attr_cache[str(selected[i-1][0])] = rset[i]
# handle relations
- for i in xrange(lastattr, len(rset)):
+ for i in range(lastattr, len(rset)):
rtype, role = selected[i-1][0]
value = rset[i]
if value is None:
@@ -1145,9 +1147,7 @@
self._cw.vreg.solutions(self._cw, select, args)
# insert RQL expressions for schema constraints into the rql syntax tree
if vocabconstraints:
- # RQLConstraint is a subclass for RQLVocabularyConstraint, so they
- # will be included as well
- cstrcls = RQLVocabularyConstraint
+ cstrcls = (RQLVocabularyConstraint, RQLConstraint)
else:
cstrcls = RQLConstraint
lt_infos = pruned_lt_info(self.e_schema, lt_infos or {})
@@ -1236,8 +1236,8 @@
no relation is given
"""
if rtype is None:
- self._cw_related_cache = {}
- self._cw_adapters_cache = {}
+ self._cw_related_cache.clear()
+ self._cw_adapters_cache.clear()
else:
assert role
self._cw_related_cache.pop('%s_%s' % (rtype, role), None)
diff -r 3a98422df969 -r 7ceb0971c694 etwist/__init__.py
--- a/etwist/__init__.py Tue Dec 15 19:05:41 2015 +0100
+++ b/etwist/__init__.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,4 +18,3 @@
""" CW - nevow/twisted client
"""
-
diff -r 3a98422df969 -r 7ceb0971c694 etwist/request.py
--- a/etwist/request.py Tue Dec 15 19:05:41 2015 +0100
+++ b/etwist/request.py Wed Dec 16 11:23:48 2015 +0100
@@ -31,7 +31,7 @@
self._twreq = req
super(CubicWebTwistedRequestAdapter, self).__init__(
vreg, https, req.args, headers=req.received_headers)
- for key, name_stream_list in req.files.iteritems():
+ for key, name_stream_list in req.files.items():
for name, stream in name_stream_list:
if name is not None:
name = unicode(name, self.encoding)
diff -r 3a98422df969 -r 7ceb0971c694 etwist/server.py
--- a/etwist/server.py Tue Dec 15 19:05:41 2015 +0100
+++ b/etwist/server.py Wed Dec 16 11:23:48 2015 +0100
@@ -22,8 +22,10 @@
import select
import traceback
import threading
-from urlparse import urlsplit, urlunsplit
from cgi import FieldStorage, parse_header
+
+from six.moves.urllib.parse import urlsplit, urlunsplit
+
from cubicweb.statsd_logger import statsd_timeit
from twisted.internet import reactor, task, threads
diff -r 3a98422df969 -r 7ceb0971c694 etwist/service.py
--- a/etwist/service.py Tue Dec 15 19:05:41 2015 +0100
+++ b/etwist/service.py Wed Dec 16 11:23:48 2015 +0100
@@ -15,6 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see .
+from __future__ import print_function
+
import os
import sys
@@ -22,7 +24,7 @@
import win32serviceutil
import win32service
except ImportError:
- print 'Win32 extensions for Python are likely not installed.'
+ print('Win32 extensions for Python are likely not installed.')
sys.exit(3)
from os.path import join
diff -r 3a98422df969 -r 7ceb0971c694 etwist/twctl.py
--- a/etwist/twctl.py Tue Dec 15 19:05:41 2015 +0100
+++ b/etwist/twctl.py Wed Dec 16 11:23:48 2015 +0100
@@ -17,10 +17,6 @@
# with CubicWeb. If not, see .
"""cubicweb-clt handlers for twisted"""
-from os.path import join
-
-from logilab.common.shellutils import rm
-
from cubicweb.toolsutils import CommandHandler
from cubicweb.web.webctl import WebCreateHandler, WebUpgradeHandler
@@ -36,9 +32,6 @@
def start_server(self, config):
from cubicweb.etwist import server
- config.info('clear ui caches')
- for cachedir in ('uicache', 'uicachehttps'):
- rm(join(config.appdatahome, cachedir, '*'))
return server.run(config)
class TWStopHandler(CommandHandler):
diff -r 3a98422df969 -r 7ceb0971c694 ext/rest.py
--- a/ext/rest.py Tue Dec 15 19:05:41 2015 +0100
+++ b/ext/rest.py Wed Dec 16 11:23:48 2015 +0100
@@ -38,7 +38,9 @@
from itertools import chain
from logging import getLogger
from os.path import join
-from urlparse import urlsplit
+
+from six import text_type
+from six.moves.urllib.parse import urlsplit
from docutils import statemachine, nodes, utils, io
from docutils.core import Publisher
@@ -172,7 +174,7 @@
rql = params['rql']
if vid is None:
vid = params.get('vid')
- except (ValueError, KeyError), exc:
+ except (ValueError, KeyError) as exc:
msg = inliner.reporter.error('Could not parse bookmark path %s [%s].'
% (bookmark.path, exc), line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
@@ -186,7 +188,7 @@
vid = 'noresult'
view = _cw.vreg['views'].select(vid, _cw, rset=rset)
content = view.render()
- except Exception, exc:
+ except Exception as exc:
content = 'An error occurred while interpreting directive bookmark: %r' % exc
set_classes(options)
return [nodes.raw('', content, format='html')], []
@@ -401,7 +403,7 @@
the data formatted as HTML or the original data if an error occurred
"""
req = context._cw
- if isinstance(data, unicode):
+ if isinstance(data, text_type):
encoding = 'unicode'
# remove unprintable characters unauthorized in xml
data = data.translate(ESC_UCAR_TABLE)
@@ -446,8 +448,8 @@
return res
except BaseException:
LOGGER.exception('error while publishing ReST text')
- if not isinstance(data, unicode):
- data = unicode(data, encoding, 'replace')
+ if not isinstance(data, text_type):
+ data = text_type(data, encoding, 'replace')
return xml_escape(req._('error while publishing ReST text')
+ '\n\n' + data)
diff -r 3a98422df969 -r 7ceb0971c694 ext/tal.py
--- a/ext/tal.py Tue Dec 15 19:05:41 2015 +0100
+++ b/ext/tal.py Wed Dec 16 11:23:48 2015 +0100
@@ -184,7 +184,10 @@
interpreter.execute(self)
except UnicodeError as unierror:
LOGGER.exception(str(unierror))
- raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
+ exc = simpleTALES.ContextContentException(
+ "found non-unicode %r string in Context!" % unierror.args[1])
+ exc.__traceback__ = sys.exc_info()[-1]
+ raise exc
def compile_template(template):
@@ -203,7 +206,7 @@
:type filepath: str
:param template: path of the file to compile
"""
- fp = file(filepath)
+ fp = open(filepath)
file_content = unicode(fp.read()) # template file should be pure ASCII
fp.close()
return compile_template(file_content)
@@ -232,7 +235,8 @@
result = eval(expr, globals, locals)
except Exception as ex:
ex = ex.__class__('in %r: %s' % (expr, ex))
- raise ex, None, sys.exc_info()[-1]
+ ex.__traceback__ = sys.exc_info()[-1]
+ raise ex
if (isinstance (result, simpleTALES.ContextVariable)):
return result.value()
return result
diff -r 3a98422df969 -r 7ceb0971c694 ext/test/data/views.py
--- a/ext/test/data/views.py Tue Dec 15 19:05:41 2015 +0100
+++ b/ext/test/data/views.py Wed Dec 16 11:23:48 2015 +0100
@@ -21,4 +21,3 @@
class CustomRsetTableView(tableview.RsetTableView):
__regid__ = 'mytable'
-
diff -r 3a98422df969 -r 7ceb0971c694 ext/test/unittest_rest.py
--- a/ext/test/unittest_rest.py Tue Dec 15 19:05:41 2015 +0100
+++ b/ext/test/unittest_rest.py Wed Dec 16 11:23:48 2015 +0100
@@ -15,6 +15,8 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see .
+from six import PY3
+
from logilab.common.testlib import unittest_main
from cubicweb.devtools.testlib import CubicWebTC
@@ -87,7 +89,9 @@
context = self.context(req)
out = rest_publish(context, ':rql:`Any X WHERE X is CWUser:toto`')
self.assertTrue(out.startswith("
an error occurred while interpreting this "
- "rql directive: ObjectNotFound(u'toto',)
')
self.wview(self.__regid__, rset, row=i, **kwargs)
@@ -213,7 +215,7 @@
return self._cw.build_url('view', vid=self.__regid__)
coltypes = rset.column_types(0)
if len(coltypes) == 1:
- etype = iter(coltypes).next()
+ etype = next(iter(coltypes))
if not self._cw.vreg.schema.eschema(etype).final:
if len(rset) == 1:
entity = rset.get_entity(0, 0)
@@ -281,7 +283,7 @@
else :
etypes = rset.column_types(0)
if len(etypes) == 1:
- etype = iter(etypes).next()
+ etype = next(iter(etypes))
clabel = display_name(self._cw, etype, 'plural')
else :
clabel = u'#[*] (%s)' % vtitle
@@ -394,7 +396,7 @@
if rset is None:
rset = self.cw_rset = self._cw.execute(self.startup_rql())
if rset:
- for i in xrange(len(rset)):
+ for i in range(len(rset)):
self.wview(self.__regid__, rset, row=i, **kwargs)
else:
self.no_entities(**kwargs)
diff -r 3a98422df969 -r 7ceb0971c694 web/__init__.py
--- a/web/__init__.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/__init__.py Wed Dec 16 11:23:48 2015 +0100
@@ -20,10 +20,9 @@
"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
-from urllib import quote as urlquote
-
+from six.moves.urllib.parse import quote as urlquote
from logilab.common.deprecation import deprecated
from cubicweb.web._exceptions import *
diff -r 3a98422df969 -r 7ceb0971c694 web/_exceptions.py
--- a/web/_exceptions.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/_exceptions.py Wed Dec 16 11:23:48 2015 +0100
@@ -20,7 +20,7 @@
__docformat__ = "restructuredtext en"
-import httplib
+from six.moves import http_client
from cubicweb._exceptions import *
from cubicweb.utils import json_dumps
@@ -41,7 +41,7 @@
"""base class for publishing related exception"""
def __init__(self, *args, **kwargs):
- self.status = kwargs.pop('status', httplib.OK)
+ self.status = kwargs.pop('status', http_client.OK)
super(PublishException, self).__init__(*args, **kwargs)
class LogOut(PublishException):
@@ -52,7 +52,7 @@
class Redirect(PublishException):
"""raised to redirect the http request"""
- def __init__(self, location, status=httplib.SEE_OTHER):
+ def __init__(self, location, status=http_client.SEE_OTHER):
super(Redirect, self).__init__(status=status)
self.location = location
@@ -71,7 +71,7 @@
"""raised when a request can't be served because of a bad input"""
def __init__(self, *args, **kwargs):
- kwargs.setdefault('status', httplib.BAD_REQUEST)
+ kwargs.setdefault('status', http_client.BAD_REQUEST)
super(RequestError, self).__init__(*args, **kwargs)
@@ -79,14 +79,14 @@
"""raised when an edit request doesn't specify any eid to edit"""
def __init__(self, *args, **kwargs):
- kwargs.setdefault('status', httplib.BAD_REQUEST)
+ kwargs.setdefault('status', http_client.BAD_REQUEST)
super(NothingToEdit, self).__init__(*args, **kwargs)
class ProcessFormError(RequestError):
"""raised when posted data can't be processed by the corresponding field
"""
def __init__(self, *args, **kwargs):
- kwargs.setdefault('status', httplib.BAD_REQUEST)
+ kwargs.setdefault('status', http_client.BAD_REQUEST)
super(ProcessFormError, self).__init__(*args, **kwargs)
class NotFound(RequestError):
@@ -94,13 +94,13 @@
a 404 error should be returned"""
def __init__(self, *args, **kwargs):
- kwargs.setdefault('status', httplib.NOT_FOUND)
+ kwargs.setdefault('status', http_client.NOT_FOUND)
super(NotFound, self).__init__(*args, **kwargs)
class RemoteCallFailed(RequestError):
"""raised when a json remote call fails
"""
- def __init__(self, reason='', status=httplib.INTERNAL_SERVER_ERROR):
+ def __init__(self, reason='', status=http_client.INTERNAL_SERVER_ERROR):
super(RemoteCallFailed, self).__init__(reason, status=status)
self.reason = reason
diff -r 3a98422df969 -r 7ceb0971c694 web/action.py
--- a/web/action.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/action.py Wed Dec 16 11:23:48 2015 +0100
@@ -33,7 +33,7 @@
"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from cubicweb import target
from cubicweb.predicates import (partial_relation_possible, match_search_state,
@@ -91,7 +91,7 @@
"""base class for actions consisting to create a new object with an initial
relation set to an entity.
- Additionaly to EntityAction behaviour, this class is parametrized using
+ Additionally to EntityAction behaviour, this class is parametrized using
.rtype, .role and .target_etype attributes to check if the action apply and
if the logged user has access to it (see
:class:`~cubicweb.selectors.partial_relation_possible` selector
@@ -111,4 +111,3 @@
return self._cw.vreg["etypes"].etype_class(ttype).cw_create_url(self._cw,
__redirectpath=entity.rest_path(), __linkto=linkto,
__redirectvid=self._cw.form.get('__redirectvid', ''))
-
diff -r 3a98422df969 -r 7ceb0971c694 web/application.py
--- a/web/application.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/application.py Wed Dec 16 11:23:48 2015 +0100
@@ -25,7 +25,8 @@
from warnings import warn
import json
-import httplib
+from six import text_type, binary_type
+from six.moves import http_client
from logilab.common.deprecation import deprecated
@@ -68,8 +69,8 @@
def __init__(self, appli):
self.repo = appli.repo
self.vreg = appli.vreg
- self.session_manager = self.vreg['components'].select('sessionmanager',
- repo=self.repo)
+ self.session_manager = self.vreg['sessions'].select('sessionmanager',
+ repo=self.repo)
global SESSION_MANAGER
SESSION_MANAGER = self.session_manager
if self.vreg.config.mode != 'test':
@@ -80,8 +81,8 @@
def reset_session_manager(self):
data = self.session_manager.dump_data()
- self.session_manager = self.vreg['components'].select('sessionmanager',
- repo=self.repo)
+ self.session_manager = self.vreg['sessions'].select('sessionmanager',
+ repo=self.repo)
self.session_manager.restore_data(data)
global SESSION_MANAGER
SESSION_MANAGER = self.session_manager
@@ -256,7 +257,7 @@
# activate realm-based auth
realm = self.vreg.config['realm']
req.set_header('WWW-Authenticate', [('Basic', {'realm' : realm })], raw=False)
- content = ''
+ content = b''
try:
try:
session = self.get_session(req)
@@ -290,7 +291,7 @@
if self.vreg.config['auth-mode'] == 'cookie' and ex.url:
req.headers_out.setHeader('location', str(ex.url))
if ex.status is not None:
- req.status_out = httplib.SEE_OTHER
+ req.status_out = http_client.SEE_OTHER
# When the authentification is handled by http we must
# explicitly ask for authentification to flush current http
# authentification information
@@ -310,23 +311,24 @@
# the request does not use https, redirect to login form
https_url = self.vreg.config['https-url']
if https_url and req.base_url() != https_url:
- req.status_out = httplib.SEE_OTHER
+ req.status_out = http_client.SEE_OTHER
req.headers_out.setHeader('location', https_url + 'login')
else:
# We assume here that in http auth mode the user *May* provide
# Authentification Credential if asked kindly.
if self.vreg.config['auth-mode'] == 'http':
- req.status_out = httplib.UNAUTHORIZED
+ req.status_out = http_client.UNAUTHORIZED
# In the other case (coky auth) we assume that there is no way
# for the user to provide them...
# XXX But WHY ?
else:
- req.status_out = httplib.FORBIDDEN
+ req.status_out = http_client.FORBIDDEN
# If previous error handling already generated a custom content
# do not overwrite it. This is used by LogOut Except
# XXX ensure we don't actually serve content
if not content:
content = self.need_login_content(req)
+ assert isinstance(content, binary_type)
return content
@@ -368,7 +370,7 @@
except cors.CORSPreflight:
# Return directly an empty 200
req.status_out = 200
- result = ''
+ result = b''
except StatusResponse as ex:
warn('[3.16] StatusResponse is deprecated use req.status_out',
DeprecationWarning, stacklevel=2)
@@ -394,12 +396,12 @@
except Unauthorized as ex:
req.data['errmsg'] = req._('You\'re not authorized to access this page. '
'If you think you should, please contact the site administrator.')
- req.status_out = httplib.FORBIDDEN
+ req.status_out = http_client.FORBIDDEN
result = self.error_handler(req, ex, tb=False)
except Forbidden as ex:
req.data['errmsg'] = req._('This action is forbidden. '
'If you think it should be allowed, please contact the site administrator.')
- req.status_out = httplib.FORBIDDEN
+ req.status_out = http_client.FORBIDDEN
result = self.error_handler(req, ex, tb=False)
except (BadRQLQuery, RequestError) as ex:
result = self.error_handler(req, ex, tb=False)
@@ -413,7 +415,7 @@
raise
### Last defense line
except BaseException as ex:
- req.status_out = httplib.INTERNAL_SERVER_ERROR
+ req.status_out = http_client.INTERNAL_SERVER_ERROR
result = self.error_handler(req, ex, tb=True)
finally:
if req.cnx and not commited:
@@ -437,7 +439,7 @@
req.headers_out.setHeader('location', str(ex.location))
assert 300 <= ex.status < 400
req.status_out = ex.status
- return ''
+ return b''
def validation_error_handler(self, req, ex):
ex.translate(req._) # translate messages using ui language
@@ -453,9 +455,9 @@
# messages.
location = req.form['__errorurl'].rsplit('#', 1)[0]
req.headers_out.setHeader('location', str(location))
- req.status_out = httplib.SEE_OTHER
- return ''
- req.status_out = httplib.CONFLICT
+ req.status_out = http_client.SEE_OTHER
+ return b''
+ req.status_out = http_client.CONFLICT
return self.error_handler(req, ex, tb=False)
def error_handler(self, req, ex, tb=False):
@@ -491,14 +493,14 @@
def ajax_error_handler(self, req, ex):
req.set_header('content-type', 'application/json')
- status = httplib.INTERNAL_SERVER_ERROR
+ status = http_client.INTERNAL_SERVER_ERROR
if isinstance(ex, PublishException) and ex.status is not None:
status = ex.status
if req.status_out < 400:
# don't overwrite it if it's already set
req.status_out = status
- json_dumper = getattr(ex, 'dumps', lambda : json.dumps({'reason': unicode(ex)}))
- return json_dumper()
+ json_dumper = getattr(ex, 'dumps', lambda : json.dumps({'reason': text_type(ex)}))
+ return json_dumper().encode('utf-8')
# special case handling
diff -r 3a98422df969 -r 7ceb0971c694 web/box.py
--- a/web/box.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/box.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,7 +18,9 @@
"""abstract box classes for CubicWeb web client"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
+
+from six import add_metaclass
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import class_deprecated, class_renamed
@@ -41,7 +43,7 @@
actions_by_cat.setdefault(action.category, []).append(
(action.title, action) )
for key, values in actions_by_cat.items():
- actions_by_cat[key] = [act for title, act in sorted(values)]
+ actions_by_cat[key] = [act for title, act in sorted(values, key=lambda x: x[0])]
if categories_in_order:
for cat in categories_in_order:
if cat in actions_by_cat:
@@ -53,6 +55,7 @@
# old box system, deprecated ###################################################
+@add_metaclass(class_deprecated)
class BoxTemplate(View):
"""base template for boxes, usually a (contextual) list of possible
actions. Various classes attributes may be used to control the box
@@ -66,7 +69,6 @@
box.render(self.w)
"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] *BoxTemplate classes are deprecated, use *CtxComponent instead (%(cls)s)'
__registry__ = 'ctxcomponents'
@@ -193,4 +195,3 @@
AjaxEditRelationBoxTemplate = class_renamed(
'AjaxEditRelationBoxTemplate', AjaxEditRelationCtxComponent,
'[3.10] AjaxEditRelationBoxTemplate has been renamed to AjaxEditRelationCtxComponent (%(cls)s)')
-
diff -r 3a98422df969 -r 7ceb0971c694 web/captcha.py
--- a/web/captcha.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/captcha.py Wed Dec 16 11:23:48 2015 +0100
@@ -22,7 +22,9 @@
__docformat__ = "restructuredtext en"
from random import randint, choice
-from cStringIO import StringIO
+from io import BytesIO
+
+from six.moves import range
from PIL import Image, ImageFont, ImageDraw, ImageFilter
@@ -51,7 +53,7 @@
draw = ImageDraw.Draw(img)
# draw 100 random colored boxes on the background
x, y = img.size
- for num in xrange(100):
+ for num in range(100):
draw.rectangle((randint(0, x), randint(0, y),
randint(0, x), randint(0, y)),
fill=randint(0, 0xffffff))
@@ -67,7 +69,7 @@
"""
text = u''.join(choice('QWERTYUOPASDFGHJKLZXCVBNM') for i in range(size))
img = pil_captcha(text, fontfile, fontsize)
- out = StringIO()
+ out = BytesIO()
img.save(out, format)
out.seek(0)
return text, out
diff -r 3a98422df969 -r 7ceb0971c694 web/component.py
--- a/web/component.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/component.py Wed Dec 16 11:23:48 2015 +0100
@@ -20,10 +20,12 @@
"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from warnings import warn
+from six import PY3, add_metaclass, text_type
+
from logilab.common.deprecation import class_deprecated, class_renamed, deprecated
from logilab.mtconverter import xml_escape
@@ -215,6 +217,9 @@
def __unicode__(self):
return tags.a(self.label, href=self.href, **self.attrs)
+ if PY3:
+ __str__ = __unicode__
+
def render(self, w):
w(tags.a(self.label, href=self.href, **self.attrs))
@@ -425,7 +430,7 @@
@property
def domid(self):
- return domid(self.__regid__) + unicode(self.entity.eid)
+ return domid(self.__regid__) + text_type(self.entity.eid)
def lazy_view_holder(self, w, entity, oid, registry='views'):
"""add a holder and return a URL that may be used to replace this
@@ -498,7 +503,7 @@
args['subject'],
args['object'])
return u'[%s] %s' % (
- xml_escape(unicode(jscall)), label, etarget.view('incontext'))
+ xml_escape(text_type(jscall)), label, etarget.view('incontext'))
def related_boxitems(self, entity):
return [self.box_item(entity, etarget, 'delete_relation', u'-')
@@ -515,7 +520,7 @@
"""returns the list of unrelated entities, using the entity's
appropriate vocabulary function
"""
- skip = set(unicode(e.eid) for e in entity.related(self.rtype, role(self),
+ skip = set(text_type(e.eid) for e in entity.related(self.rtype, role(self),
entities=True))
skip.add(None)
skip.add(INTERNAL_FIELD_VALUE)
@@ -571,7 +576,7 @@
# to be defined in concrete classes
rtype = role = target_etype = None
- # class attributes below *must* be set in concrete classes (additionaly to
+ # class attributes below *must* be set in concrete classes (additionally to
# rtype / role [/ target_etype]. They should correspond to js_* methods on
# the json controller
@@ -633,7 +638,7 @@
if maydel:
if not js_css_added:
js_css_added = self.add_js_css()
- jscall = unicode(js.ajaxBoxRemoveLinkedEntity(
+ jscall = text_type(js.ajaxBoxRemoveLinkedEntity(
self.__regid__, entity.eid, rentity.eid,
self.fname_remove,
self.removed_msg and _(self.removed_msg)))
@@ -648,7 +653,7 @@
if mayadd:
multiple = self.rdef.role_cardinality(self.role) in '*+'
w(u'
')
- jscall = unicode(js.ajaxBoxShowSelector(
+ jscall = text_type(js.ajaxBoxShowSelector(
self.__regid__, entity.eid, self.fname_vocabulary,
self.fname_validate, self.added_msg and _(self.added_msg),
_(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]),
@@ -677,6 +682,7 @@
# old contextual components, deprecated ########################################
+@add_metaclass(class_deprecated)
class EntityVComponent(Component):
"""abstract base class for additinal components displayed in content
headers and footer according to:
@@ -687,7 +693,6 @@
it should be configured using .accepts, .etype, .rtype, .target and
.context class attributes
"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] *VComponent classes are deprecated, use *CtxComponent instead (%(cls)s)'
__registry__ = 'ctxcomponents'
diff -r 3a98422df969 -r 7ceb0971c694 web/controller.py
--- a/web/controller.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/controller.py Wed Dec 16 11:23:48 2015 +0100
@@ -19,6 +19,8 @@
__docformat__ = "restructuredtext en"
+from six import PY2
+
from logilab.mtconverter import xml_escape
from logilab.common.registry import yes
from logilab.common.deprecation import deprecated
@@ -87,7 +89,7 @@
rql = req.form.get('rql')
if rql:
req.ensure_ro_rql(rql)
- if not isinstance(rql, unicode):
+ if PY2 and not isinstance(rql, unicode):
rql = unicode(rql, req.encoding)
pp = req.vreg['components'].select_or_none('magicsearch', req)
if pp is not None:
@@ -132,8 +134,6 @@
newparams['_cwmsgid'] = self._cw.set_redirect_message(msg)
if '__action_apply' in self._cw.form:
self._return_to_edition_view(newparams)
- if '__action_cancel' in self._cw.form:
- self._return_to_lastpage(newparams)
else:
self._return_to_original_view(newparams)
@@ -155,7 +155,7 @@
and '_cwmsgid' in newparams):
# are we here on creation or modification?
if any(eid == self._edited_entity.eid
- for eid in self._cw.data.get('eidmap', {}).itervalues()):
+ for eid in self._cw.data.get('eidmap', {}).values()):
msg = self._cw._('click here to see created entity')
else:
msg = self._cw._('click here to see edited entity')
@@ -201,11 +201,9 @@
raise Redirect(self._cw.build_url(path, **newparams))
- def _return_to_lastpage(self, newparams):
- """cancel-button case: in this case we are always expecting to go back
- where we came from, and this is not easy. Currently we suppose that
- __redirectpath is specifying that place if found, else we look in the
- request breadcrumbs for the last visited page.
+ def _redirect(self, newparams):
+ """Raise a redirect. We use __redirectpath if it specified, else we
+ return to the home page.
"""
if '__redirectpath' in self._cw.form:
# if redirect path was explicitly specified in the form, use it
@@ -213,7 +211,7 @@
url = self._cw.build_url(path)
url = append_url_params(url, self._cw.form.get('__redirectparams'))
else:
- url = self._cw.last_visited_page()
+ url = self._cw.base_url()
# The newparams must update the params in all cases
url = self._cw.rebuild_url(url, **newparams)
raise Redirect(url)
@@ -221,4 +219,3 @@
from cubicweb import set_log_methods
set_log_methods(Controller, LOGGER)
-
diff -r 3a98422df969 -r 7ceb0971c694 web/cors.py
--- a/web/cors.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/cors.py Wed Dec 16 11:23:48 2015 +0100
@@ -14,7 +14,7 @@
"""
-import urlparse
+from six.moves.urllib.parse import urlsplit
from cubicweb.web import LOGGER
info = LOGGER.info
@@ -37,7 +37,7 @@
In case of non-compliance, no CORS-related header is set.
"""
- base_url = urlparse.urlsplit(req.base_url())
+ base_url = urlsplit(req.base_url())
expected_host = '://'.join((base_url.scheme, base_url.netloc))
if not req.get_header('Origin') or req.get_header('Origin') == expected_host:
# not a CORS request, nothing to do
@@ -50,7 +50,7 @@
process_preflight(req, config)
else: # Simple CORS or actual request
process_simple(req, config)
- except CORSFailed, exc:
+ except CORSFailed as exc:
info('Cross origin resource sharing failed: %s' % exc)
except CORSPreflight:
info('Cross origin resource sharing: valid Preflight request %s')
@@ -101,7 +101,7 @@
if '*' not in allowed_origins and origin not in allowed_origins:
raise CORSFailed('Origin is not allowed')
# bit of sanity check; see "6.3 Security"
- myhost = urlparse.urlsplit(req.base_url()).netloc
+ myhost = urlsplit(req.base_url()).netloc
host = req.get_header('Host')
if host != myhost:
info('cross origin resource sharing detected possible '
@@ -111,4 +111,3 @@
# include "Vary: Origin" header (see 6.4)
req.headers_out.addHeader('Vary', 'Origin')
return origin
-
diff -r 3a98422df969 -r 7ceb0971c694 web/data/cubicweb.edition.js
--- a/web/data/cubicweb.edition.js Tue Dec 15 19:05:41 2015 +0100
+++ b/web/data/cubicweb.edition.js Wed Dec 16 11:23:48 2015 +0100
@@ -537,6 +537,24 @@
}
/**
+ * Cancel the operations done on the given form.
+ *
+ */
+$(function () {
+ $(document).on('click', '.cwjs-edition-cancel', function (evt) {
+ var $mynode = $(evt.currentTarget),
+ $form = $mynode.closest('form'),
+ $error = $form.find(':input[name="__errorurl"]'),
+ errorurl = $error.attr('value'),
+ args = ajaxFuncArgs('cancel_edition', null, errorurl);
+ loadRemote(AJAX_BASE_URL, args, 'POST', true);
+ history.back();
+ return false;
+ });
+});
+
+
+/**
* .. function:: validateForm(formid, action, onsuccess, onfailure)
*
* called on traditionnal form submission : the idea is to try
diff -r 3a98422df969 -r 7ceb0971c694 web/facet.py
--- a/web/facet.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/facet.py Wed Dec 16 11:23:48 2015 +0100
@@ -50,13 +50,15 @@
"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from functools import reduce
from warnings import warn
from copy import deepcopy
from datetime import datetime, timedelta
+from six import text_type, string_types
+
from logilab.mtconverter import xml_escape
from logilab.common.graph import has_path
from logilab.common.decorators import cached, cachedproperty
@@ -80,7 +82,7 @@
ptypes = facet.cw_rset.column_types(0)
if len(ptypes) == 1:
return display_name(facet._cw, facet.rtype, form=facet.role,
- context=iter(ptypes).next())
+ context=next(iter(ptypes)))
return display_name(facet._cw, facet.rtype, form=facet.role)
def get_facet(req, facetid, select, filtered_variable):
@@ -133,7 +135,7 @@
or the first variable selected in column 0
"""
if mainvar is None:
- vref = select.selection[0].iget_nodes(nodes.VariableRef).next()
+ vref = next(select.selection[0].iget_nodes(nodes.VariableRef))
return vref.variable
return select.defined_vars[mainvar]
@@ -156,7 +158,7 @@
for term in select.selection[:]:
select.remove_selected(term)
# remove unbound variables which only have some type restriction
- for dvar in list(select.defined_vars.itervalues()):
+ for dvar in list(select.defined_vars.values()):
if not (dvar is filtered_variable or dvar.stinfo['relations']):
select.undefine_variable(dvar)
# global tree config: DISTINCT, LIMIT, OFFSET
@@ -305,7 +307,7 @@
# optional relation
return ovar
if all(rdef.cardinality[cardidx] in '1+'
- for rdef in rschema.rdefs.itervalues()):
+ for rdef in rschema.rdefs.values()):
# mandatory relation without any restriction on the other variable
for orel in ovar.stinfo['relations']:
if rel is orel:
@@ -670,7 +672,7 @@
insert_attr_select_relation(
select, self.filtered_variable, self.rtype, self.role,
self.target_attr, select_target_entity=False)
- values = [unicode(x) for x, in self.rqlexec(select.as_string())]
+ values = [text_type(x) for x, in self.rqlexec(select.as_string())]
except Exception:
self.exception('while computing values for %s', self)
return []
@@ -719,14 +721,14 @@
def rset_vocabulary(self, rset):
if self.i18nable:
- _ = self._cw._
+ tr = self._cw._
else:
- _ = unicode
+ tr = text_type
if self.rql_sort:
- values = [(_(label), eid) for eid, label in rset]
+ values = [(tr(label), eid) for eid, label in rset]
else:
if self.label_vid is None:
- values = [(_(label), eid) for eid, label in rset]
+ values = [(tr(label), eid) for eid, label in rset]
else:
values = [(entity.view(self.label_vid), entity.eid)
for entity in rset.entities()]
@@ -754,7 +756,7 @@
# XXX handle rel is None case in RQLPathFacet?
if self.restr_attr != 'eid':
self.select.set_distinct(True)
- if isinstance(value, basestring):
+ if isinstance(value, string_types):
# only one value selected
if value:
self.select.add_constant_restriction(
@@ -808,7 +810,7 @@
rschema = self._cw.vreg.schema.rschema(self.rtype)
# XXX when called via ajax, no rset to compute possible types
possibletypes = self.cw_rset and self.cw_rset.column_types(0)
- for rdef in rschema.rdefs.itervalues():
+ for rdef in rschema.rdefs.values():
if possibletypes is not None:
if self.role == 'subject':
if rdef.subject not in possibletypes:
@@ -829,13 +831,13 @@
if self._cw.vreg.schema.rschema(self.rtype).final:
return False
if self.role == 'object':
- subj = utils.rqlvar_maker(defined=self.select.defined_vars,
- aliases=self.select.aliases).next()
+ subj = next(utils.rqlvar_maker(defined=self.select.defined_vars,
+ aliases=self.select.aliases))
obj = self.filtered_variable.name
else:
subj = self.filtered_variable.name
- obj = utils.rqlvar_maker(defined=self.select.defined_vars,
- aliases=self.select.aliases).next()
+ obj = next(utils.rqlvar_maker(defined=self.select.defined_vars,
+ aliases=self.select.aliases))
restrictions = []
if self.select.where:
restrictions.append(self.select.where.as_string())
@@ -916,15 +918,13 @@
def rset_vocabulary(self, rset):
if self.i18nable:
- _ = self._cw._
+ tr = self._cw._
else:
- _ = unicode
+ tr = text_type
if self.rql_sort:
- return [(_(value), value) for value, in rset]
- values = [(_(value), value) for value, in rset]
- if self.sortasc:
- return sorted(values)
- return reversed(sorted(values))
+ return [(tr(value), value) for value, in rset]
+ values = [(tr(value), value) for value, in rset]
+ return sorted(values, reverse=not self.sortasc)
class AttributeFacet(RelationAttributeFacet):
@@ -1073,7 +1073,7 @@
assert self.path and isinstance(self.path, (list, tuple)), \
'path should be a list of 3-uples, not %s' % self.path
for part in self.path:
- if isinstance(part, basestring):
+ if isinstance(part, string_types):
part = part.split()
assert len(part) == 3, \
'path should be a list of 3-uples, not %s' % part
@@ -1126,7 +1126,7 @@
cleanup_select(select, self.filtered_variable)
varmap, restrvar = self.add_path_to_select(skiplabel=True)
select.append_selected(nodes.VariableRef(restrvar))
- values = [unicode(x) for x, in self.rqlexec(select.as_string())]
+ values = [text_type(x) for x, in self.rqlexec(select.as_string())]
except Exception:
self.exception('while computing values for %s', self)
return []
@@ -1149,7 +1149,7 @@
varmap = {'X': self.filtered_variable}
actual_filter_variable = None
for part in self.path:
- if isinstance(part, basestring):
+ if isinstance(part, string_types):
part = part.split()
subject, rtype, object = part
if skiplabel and object == self.label_variable:
@@ -1165,7 +1165,7 @@
if len(attrtypes) > 1:
raise Exception('ambigous attribute %s, specify attrtype on %s'
% (rtype, self.__class__))
- self.restr_attr_type = iter(attrtypes).next()
+ self.restr_attr_type = next(iter(attrtypes))
if skipattrfilter:
actual_filter_variable = subject
continue
@@ -1253,7 +1253,7 @@
rset = self._range_rset()
if rset:
minv, maxv = rset[0]
- return [(unicode(minv), minv), (unicode(maxv), maxv)]
+ return [(text_type(minv), minv), (text_type(maxv), maxv)]
return []
def possible_values(self):
@@ -1272,7 +1272,7 @@
def formatvalue(self, value):
"""format `value` before in order to insert it in the RQL query"""
- return unicode(value)
+ return text_type(value)
def infvalue(self, min=False):
if min:
@@ -1373,7 +1373,7 @@
# *list* (see rqlexec implementation)
if rset:
minv, maxv = rset[0]
- return [(unicode(minv), minv), (unicode(maxv), maxv)]
+ return [(text_type(minv), minv), (text_type(maxv), maxv)]
return []
@@ -1392,7 +1392,7 @@
skiplabel=True, skipattrfilter=True)
restrel = None
for part in self.path:
- if isinstance(part, basestring):
+ if isinstance(part, string_types):
part = part.split()
subject, rtype, object = part
if object == self.filter_variable:
@@ -1516,7 +1516,7 @@
if not val or val & mask])
def possible_values(self):
- return [unicode(val) for label, val in self.vocabulary()]
+ return [text_type(val) for label, val in self.vocabulary()]
## html widets ################################################################
@@ -1595,7 +1595,7 @@
if selected:
cssclass += ' facetValueSelected'
w(u'
\n'
- % (cssclass, xml_escape(unicode(value))))
+ % (cssclass, xml_escape(text_type(value))))
# If it is overflowed one must add padding to compensate for the vertical
# scrollbar; given current css values, 4 blanks work perfectly ...
padding = u' ' * self.scrollbar_padding_factor if overflow else u''
@@ -1754,7 +1754,7 @@
imgsrc = self._cw.data_url(self.unselected_img)
imgalt = self._cw._('not selected')
w(u'
')
w(u' ' % (imgsrc, imgalt))
w(u''
diff -r 3a98422df969 -r 7ceb0971c694 web/form.py
--- a/web/form.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/form.py Wed Dec 16 11:23:48 2015 +0100
@@ -20,6 +20,8 @@
from warnings import warn
+from six import add_metaclass
+
from logilab.common.decorators import iclassmethod
from logilab.common.deprecation import deprecated
@@ -74,8 +76,8 @@
found
"""
+@add_metaclass(metafieldsform)
class Form(AppObject):
- __metaclass__ = metafieldsform
__registry__ = 'forms'
parent_form = None
@@ -120,7 +122,7 @@
extrakw = {}
# search for navigation parameters and customization of existing
# attributes; remaining stuff goes in extrakwargs
- for key, val in kwargs.iteritems():
+ for key, val in kwargs.items():
if key in controller.NAV_FORM_PARAMETERS:
hiddens.append( (key, val) )
elif key == 'redirect_path':
@@ -280,4 +282,3 @@
def remaining_errors(self):
return sorted(self.form_valerror.errors.items())
-
diff -r 3a98422df969 -r 7ceb0971c694 web/formfields.py
--- a/web/formfields.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/formfields.py Wed Dec 16 11:23:48 2015 +0100
@@ -66,6 +66,8 @@
from warnings import warn
from datetime import datetime, timedelta
+from six import PY2, text_type, string_types
+
from logilab.mtconverter import xml_escape
from logilab.common import nullobject
from logilab.common.date import ustrftime
@@ -159,7 +161,7 @@
:attr:`order`
key used by automatic forms to sort fields
:attr:`ignore_req_params`
- when true, this field won't consider value potentialy specified using
+ when true, this field won't consider value potentially specified using
request's form parameters (eg you won't be able to specify a value using for
instance url like http://mywebsite.com/form?field=value)
@@ -231,11 +233,14 @@
def __unicode__(self):
return self.as_string(False)
- def __str__(self):
- return self.as_string(False).encode('UTF8')
+ if PY2:
+ def __str__(self):
+ return self.as_string(False).encode('UTF8')
+ else:
+ __str__ = __unicode__
def __repr__(self):
- return self.as_string(True).encode('UTF8')
+ return self.as_string(True)
def init_widget(self, widget):
if widget is not None:
@@ -279,7 +284,7 @@
return u''
if value is True:
return u'1'
- return unicode(value)
+ return text_type(value)
def get_widget(self, form):
"""return the widget instance associated to this field"""
@@ -381,7 +386,7 @@
assert self.choices is not None
if callable(self.choices):
# pylint: disable=E1102
- if getattr(self.choices, 'im_self', None) is self:
+ if getattr(self.choices, '__self__', None) is self:
vocab = self.choices(form=form, **kwargs)
else:
vocab = self.choices(form=form, field=self, **kwargs)
@@ -508,7 +513,7 @@
class StringField(Field):
"""Use this field to edit unicode string (`String` yams type). This field
- additionaly support a `max_length` attribute that specify a maximum size for
+ additionally support a `max_length` attribute that specify a maximum size for
the string (`None` meaning no limit).
Unless explicitly specified, the widget for this field will be:
@@ -780,7 +785,7 @@
If the stream format is one of text/plain, text/html, text/rest,
text/markdown
- then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionaly
+ then a :class:`~cubicweb.web.formwidgets.TextArea` will be additionally
displayed, allowing to directly the file's content when desired, instead
of choosing a file from user's file system.
"""
@@ -794,7 +799,7 @@
if data:
encoding = self.encoding(form)
try:
- form.formvalues[(self, form)] = unicode(data.getvalue(), encoding)
+ form.formvalues[(self, form)] = data.getvalue().decode(encoding)
except UnicodeError:
pass
else:
@@ -815,7 +820,7 @@
def _process_form_value(self, form):
value = form._cw.form.get(self.input_name(form))
- if isinstance(value, unicode):
+ if isinstance(value, text_type):
# file modified using a text widget
return Binary(value.encode(self.encoding(form)))
return super(EditableFileField, self)._process_form_value(form)
@@ -823,7 +828,7 @@
class BigIntField(Field):
"""Use this field to edit big integers (`BigInt` yams type). This field
- additionaly support `min` and `max` attributes that specify a minimum and/or
+ additionally support `min` and `max` attributes that specify a minimum and/or
maximum value for the integer (`None` meaning no boundary).
Unless explicitly specified, the widget for this field will be a
@@ -842,7 +847,7 @@
self.widget.attrs.setdefault('size', self.default_text_input_size)
def _ensure_correctly_typed(self, form, value):
- if isinstance(value, basestring):
+ if isinstance(value, string_types):
value = value.strip()
if not value:
return None
@@ -907,7 +912,7 @@
class FloatField(IntField):
- """Use this field to edit floats (`Float` yams type). This field additionaly
+ """Use this field to edit floats (`Float` yams type). This field additionally
support `min` and `max` attributes as the
:class:`~cubicweb.web.formfields.IntField`.
@@ -924,7 +929,7 @@
return self.format_single_value(req, 1.234)
def _ensure_correctly_typed(self, form, value):
- if isinstance(value, basestring):
+ if isinstance(value, string_types):
value = value.strip()
if not value:
return None
@@ -946,7 +951,7 @@
def format_single_value(self, req, value):
if value:
value = format_time(value.days * 24 * 3600 + value.seconds)
- return unicode(value)
+ return text_type(value)
return u''
def example_format(self, req):
@@ -956,7 +961,7 @@
return u'20s, 10min, 24h, 4d'
def _ensure_correctly_typed(self, form, value):
- if isinstance(value, basestring):
+ if isinstance(value, string_types):
value = value.strip()
if not value:
return None
@@ -986,14 +991,14 @@
return self.format_single_value(req, datetime.now())
def _ensure_correctly_typed(self, form, value):
- if isinstance(value, basestring):
+ if isinstance(value, string_types):
value = value.strip()
if not value:
return None
try:
value = form._cw.parse_datetime(value, self.etype)
except ValueError as ex:
- raise ProcessFormError(unicode(ex))
+ raise ProcessFormError(text_type(ex))
return value
@@ -1083,7 +1088,7 @@
linkedto = form.linked_to.get((self.name, self.role))
if linkedto:
buildent = form._cw.entity_from_eid
- return [(buildent(eid).view('combobox'), unicode(eid))
+ return [(buildent(eid).view('combobox'), text_type(eid))
for eid in linkedto]
return []
@@ -1095,7 +1100,7 @@
# vocabulary doesn't include current values, add them
if form.edited_entity.has_eid():
rset = form.edited_entity.related(self.name, self.role)
- vocab += [(e.view('combobox'), unicode(e.eid))
+ vocab += [(e.view('combobox'), text_type(e.eid))
for e in rset.entities()]
return vocab
@@ -1129,11 +1134,11 @@
if entity.eid in done:
continue
done.add(entity.eid)
- res.append((entity.view('combobox'), unicode(entity.eid)))
+ res.append((entity.view('combobox'), text_type(entity.eid)))
return res
def format_single_value(self, req, value):
- return unicode(value)
+ return text_type(value)
def process_form_value(self, form):
"""process posted form and return correctly typed value"""
diff -r 3a98422df969 -r 7ceb0971c694 web/formwidgets.py
--- a/web/formwidgets.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/formwidgets.py Wed Dec 16 11:23:48 2015 +0100
@@ -99,6 +99,8 @@
from datetime import date
from warnings import warn
+from six import text_type, string_types
+
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import deprecated
from logilab.common.date import todatetime
@@ -282,7 +284,7 @@
"""
posted = form._cw.form
val = posted.get(field.input_name(form, self.suffix))
- if isinstance(val, basestring):
+ if isinstance(val, string_types):
val = val.strip()
return val
@@ -416,7 +418,7 @@
lines = value.splitlines()
linecount = len(lines)
for line in lines:
- linecount += len(line) / self._columns
+ linecount += len(line) // self._columns
attrs.setdefault('cols', self._columns)
attrs.setdefault('rows', min(self._maxrows, linecount + self._minrows))
return tags.textarea(value, name=field.input_name(form, self.suffix),
@@ -474,7 +476,7 @@
options.append(u'')
if not 'size' in attrs:
if self._multiple:
- size = unicode(min(self.default_size, len(vocab) or 1))
+ size = text_type(min(self.default_size, len(vocab) or 1))
else:
size = u'1'
attrs['size'] = size
@@ -616,7 +618,7 @@
iattrs['checked'] = u'checked'
tag = tags.input(name=field.input_name(form, self.suffix),
type=self.type, value=value, **iattrs)
- options.append(u'%s %s' % (tag, label))
+ options.append(u'' % (tag, xml_escape(label)))
return sep.join(options)
@@ -706,7 +708,7 @@
else:
value = self.value
attrs = self.attributes(form, field)
- attrs.setdefault('size', unicode(self.default_size))
+ attrs.setdefault('size', text_type(self.default_size))
return tags.input(name=field.input_name(form, self.suffix),
value=value, type='text', **attrs)
@@ -779,13 +781,13 @@
try:
date = todatetime(req.parse_datetime(datestr, 'Date'))
except ValueError as exc:
- raise ProcessFormError(unicode(exc))
+ raise ProcessFormError(text_type(exc))
if timestr is None:
return date
try:
time = req.parse_datetime(timestr, 'Time')
except ValueError as exc:
- raise ProcessFormError(unicode(exc))
+ raise ProcessFormError(text_type(exc))
return date.replace(hour=time.hour, minute=time.minute, second=time.second)
@@ -993,12 +995,12 @@
req = form._cw
values = {}
path = req.form.get(field.input_name(form, 'path'))
- if isinstance(path, basestring):
+ if isinstance(path, string_types):
path = path.strip()
if path is None:
path = u''
fqs = req.form.get(field.input_name(form, 'fqs'))
- if isinstance(fqs, basestring):
+ if isinstance(fqs, string_types):
fqs = fqs.strip() or None
if fqs:
for i, line in enumerate(fqs.split('\n')):
@@ -1009,7 +1011,7 @@
except ValueError:
raise ProcessFormError(req._("wrong query parameter line %s") % (i+1))
# value will be url quoted by build_url_params
- values.setdefault(key.encode(req.encoding), []).append(val)
+ values.setdefault(key, []).append(val)
if not values:
return path
return u'%s?%s' % (path, req.build_url_params(**values))
@@ -1094,4 +1096,3 @@
'%(label)s' % {
'label': label, 'imgsrc': imgsrc,
'domid': self.domid, 'href': self.href}
-
diff -r 3a98422df969 -r 7ceb0971c694 web/htmlwidgets.py
--- a/web/htmlwidgets.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/htmlwidgets.py Wed Dec 16 11:23:48 2015 +0100
@@ -24,6 +24,9 @@
import random
from math import floor
+from six import add_metaclass
+from six.moves import range
+
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import class_deprecated
@@ -115,9 +118,9 @@
self.w(u'
')
+@add_metaclass(class_deprecated)
class SideBoxWidget(BoxWidget):
"""default CubicWeb's sidebox widget"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
title_class = u'sideBoxTitle'
@@ -207,9 +210,9 @@
self.w(u'
')
+@add_metaclass(class_deprecated)
class BoxField(HTMLWidget):
"""couples label / value meant to be displayed in a box"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, label, value):
self.label = label
@@ -220,18 +223,19 @@
u'%s'
% (self.label, self.value))
+
+@add_metaclass(class_deprecated)
class BoxSeparator(HTMLWidget):
"""a menu separator"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def _render(self):
self.w(u'
')
+@add_metaclass(class_deprecated)
class BoxLink(HTMLWidget):
"""a link in a box"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, href, label, _class='', title='', ident='', escape=False):
self.href = href
@@ -252,9 +256,9 @@
self.w(u'
%s
\n' % (self._class, link))
+@add_metaclass(class_deprecated)
class BoxHtml(HTMLWidget):
"""a form in a box"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] class %(cls)s is deprecated'
def __init__(self, rawhtml):
self.rawhtml = rawhtml
@@ -339,17 +343,17 @@
self.w(u'')
self.w(u'
')
for column in self.columns:
- attrs = ('%s="%s"' % (name, value) for name, value in column.cell_attrs.iteritems())
+ attrs = ('%s="%s"' % (name, value) for name, value in column.cell_attrs.items())
self.w(u'
%s
' % (' '.join(attrs), column.name or u''))
self.w(u'
')
self.w(u'')
- for rowindex in xrange(len(self.model.get_rows())):
+ for rowindex in range(len(self.model.get_rows())):
klass = (rowindex%2==1) and 'odd' or 'even'
self.w(u'
' % (klass, self.highlight))
for column, sortvalue in self.itercols(rowindex):
attrs = dict(column.cell_attrs)
attrs["cubicweb:sortvalue"] = sortvalue
- attrs = ('%s="%s"' % (name, value) for name, value in attrs.iteritems())
+ attrs = ('%s="%s"' % (name, value) for name, value in attrs.items())
self.w(u'
' % (' '.join(attrs)))
for cellvid, colindex in column.cellrenderers:
self.model.render_cell(cellvid, rowindex, colindex, w=self.w)
@@ -361,5 +365,3 @@
def itercols(self, rowindex):
for column in self.columns:
yield column, self.model.sortvalue(rowindex, column.rset_sortcol)
-
-
diff -r 3a98422df969 -r 7ceb0971c694 web/http_headers.py
--- a/web/http_headers.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/http_headers.py Wed Dec 16 11:23:48 2015 +0100
@@ -2,11 +2,14 @@
# http://twistedmatrix.com/trac/wiki/TwistedWeb2
-import types, time
+import time
from calendar import timegm
import base64
import re
-import urlparse
+
+from six import string_types
+from six.moves.urllib.parse import urlparse
+
def dashCapitalize(s):
''' Capitalize a string, making sure to treat - as a word seperator '''
@@ -295,9 +298,9 @@
cur = cur+1
if qpair:
- raise ValueError, "Missing character after '\\'"
+ raise ValueError("Missing character after '\\'")
if quoted:
- raise ValueError, "Missing end quote"
+ raise ValueError("Missing end quote")
if start != cur:
if foldCase:
@@ -347,7 +350,7 @@
##### parser utilities:
def checkSingleToken(tokens):
if len(tokens) != 1:
- raise ValueError, "Expected single token, not %s." % (tokens,)
+ raise ValueError("Expected single token, not %s." % (tokens,))
return tokens[0]
def parseKeyValue(val):
@@ -355,11 +358,11 @@
return val[0], None
elif len(val) == 3 and val[1] == Token('='):
return val[0], val[2]
- raise ValueError, "Expected key or key=value, but got %s." % (val,)
+ raise ValueError("Expected key or key=value, but got %s." % (val,))
def parseArgs(field):
args = split(field, Token(';'))
- val = args.next()
+ val = next(args)
args = [parseKeyValue(arg) for arg in args]
return val, args
@@ -380,7 +383,7 @@
def unique(seq):
'''if seq is not a string, check it's a sequence of one element and return it'''
- if isinstance(seq, basestring):
+ if isinstance(seq, string_types):
return seq
if len(seq) != 1:
raise ValueError('single value required, not %s' % seq)
@@ -398,7 +401,7 @@
"""Ensure origin is a valid URL-base stuff, or null"""
if origin == 'null':
return origin
- p = urlparse.urlparse(origin)
+ p = urlparse(origin)
if p.params or p.query or p.username or p.path not in ('', '/'):
raise ValueError('Incorrect Accept-Control-Allow-Origin value %s' % origin)
if p.scheme not in ('http', 'https'):
@@ -452,14 +455,15 @@
"""
if (value in (True, 1) or
- isinstance(value, basestring) and value.lower() == 'true'):
+ isinstance(value, string_types) and value.lower() == 'true'):
return 'true'
if (value in (False, 0) or
- isinstance(value, basestring) and value.lower() == 'false'):
+ isinstance(value, string_types) and value.lower() == 'false'):
return 'false'
raise ValueError("Invalid true/false header value: %s" % value)
class MimeType(object):
+ @classmethod
def fromString(klass, mimeTypeString):
"""Generate a MimeType object from the given string.
@@ -469,8 +473,6 @@
"""
return DefaultHTTPHandler.parse('content-type', [mimeTypeString])
- fromString = classmethod(fromString)
-
def __init__(self, mediaType, mediaSubtype, params={}, **kwargs):
"""
@type mediaType: C{str}
@@ -499,14 +501,14 @@
return "MimeType(%r, %r, %r)" % (self.mediaType, self.mediaSubtype, self.params)
def __hash__(self):
- return hash(self.mediaType)^hash(self.mediaSubtype)^hash(tuple(self.params.iteritems()))
+ return hash(self.mediaType)^hash(self.mediaSubtype)^hash(tuple(self.params.items()))
##### Specific header parsers.
def parseAccept(field):
type, args = parseArgs(field)
if len(type) != 3 or type[1] != Token('/'):
- raise ValueError, "MIME Type "+str(type)+" invalid."
+ raise ValueError("MIME Type "+str(type)+" invalid.")
# okay, this spec is screwy. A 'q' parameter is used as the separator
# between MIME parameters and (as yet undefined) additional HTTP
@@ -569,7 +571,7 @@
type, args = parseArgs(header)
if len(type) != 3 or type[1] != Token('/'):
- raise ValueError, "MIME Type "+str(type)+" invalid."
+ raise ValueError("MIME Type "+str(type)+" invalid.")
args = [(kv[0].lower(), kv[1]) for kv in args]
@@ -730,7 +732,7 @@
out ="%s/%s"%(mimeType.mediaType, mimeType.mediaSubtype)
if mimeType.params:
- out+=';'+generateKeyValues(mimeType.params.iteritems())
+ out+=';'+generateKeyValues(mimeType.params.items())
if q != 1.0:
out+=(';q=%.3f' % (q,)).rstrip('0').rstrip('.')
@@ -766,7 +768,8 @@
v = [field.strip().lower() for field in v.split(',')]
return k, v
-def generateCacheControl((k, v)):
+def generateCacheControl(args):
+ k, v = args
if v is None:
return str(k)
else:
@@ -833,7 +836,7 @@
def generateContentType(mimeType):
out = "%s/%s" % (mimeType.mediaType, mimeType.mediaSubtype)
if mimeType.params:
- out += ';' + generateKeyValues(mimeType.params.iteritems())
+ out += ';' + generateKeyValues(mimeType.params.items())
return out
def generateIfRange(dateOrETag):
@@ -854,7 +857,7 @@
try:
l = []
- for k, v in dict(challenge).iteritems():
+ for k, v in dict(challenge).items():
l.append("%s=%s" % (k, quoteString(v)))
_generated.append("%s %s" % (scheme, ", ".join(l)))
@@ -864,7 +867,7 @@
return _generated
def generateAuthorization(seq):
- return [' '.join(seq)]
+ return [' '.join(str(v) for v in seq)]
####
@@ -1326,10 +1329,10 @@
self._headers = {}
self.handler = handler
if headers is not None:
- for key, value in headers.iteritems():
+ for key, value in headers.items():
self.setHeader(key, value)
if rawHeaders is not None:
- for key, value in rawHeaders.iteritems():
+ for key, value in rawHeaders.items():
self.setRawHeaders(key, value)
def _setRawHeaders(self, headers):
@@ -1458,7 +1461,7 @@
"""Return an iterator of key, value pairs of all headers
contained in this object, as strings. The keys are capitalized
in canonical capitalization."""
- for k, v in self._raw_headers.iteritems():
+ for k, v in self._raw_headers.items():
if v is _RecalcNeeded:
v = self._toRaw(k)
yield self.canonicalNameCaps(k), v
@@ -1480,7 +1483,7 @@
is strictly an error, but we're nice.).
"""
-iteritems = lambda x: x.iteritems()
+iteritems = lambda x: x.items()
parser_general_headers = {
diff -r 3a98422df969 -r 7ceb0971c694 web/httpcache.py
--- a/web/httpcache.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/httpcache.py Wed Dec 16 11:23:48 2015 +0100
@@ -31,6 +31,7 @@
def set_headers(self):
self.req.set_header('Cache-control', 'no-cache')
+ self.req.set_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
class MaxAgeHTTPCacheManager(NoHTTPCacheManager):
@@ -68,7 +69,7 @@
try:
req.set_header('Etag', '"%s"' % self.etag())
except NoEtag:
- self.req.set_header('Cache-control', 'no-cache')
+ super(EtagHTTPCacheManager, self).set_headers()
return
req.set_header('Cache-control',
'must-revalidate,max-age=%s' % self.max_age())
@@ -178,4 +179,3 @@
('if-none-match', if_none_match),
#('if-modified-since', if_modified_since),
]
-
diff -r 3a98422df969 -r 7ceb0971c694 web/propertysheet.py
--- a/web/propertysheet.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/propertysheet.py Wed Dec 16 11:23:48 2015 +0100
@@ -22,6 +22,8 @@
import re
import os
import os.path as osp
+import tempfile
+
TYPE_CHECKS = [('STYLESHEETS', list), ('JAVASCRIPTS', list),
('STYLESHEETS_IE', list), ('STYLESHEETS_PRINT', list),
@@ -51,13 +53,13 @@
self.clear()
self._ordered_propfiles = []
self._propfile_mtime = {}
- self._sourcefile_mtime = {}
- self._cache = {}
def load(self, fpath):
scriptglobals = self.context.copy()
scriptglobals['__file__'] = fpath
- execfile(fpath, scriptglobals, self)
+ with open(fpath, 'rb') as fobj:
+ code = compile(fobj.read(), fpath, 'exec')
+ exec(code, scriptglobals, self)
for name, type in TYPE_CHECKS:
if name in self:
if not isinstance(self[name], type):
@@ -67,10 +69,7 @@
self._ordered_propfiles.append(fpath)
def need_reload(self):
- for rid, (adirectory, rdirectory, mtime) in self._cache.items():
- if os.stat(osp.join(rdirectory, rid)).st_mtime > mtime:
- del self._cache[rid]
- for fpath, mtime in self._propfile_mtime.iteritems():
+ for fpath, mtime in self._propfile_mtime.items():
if os.stat(fpath).st_mtime > mtime:
return True
return False
@@ -86,31 +85,29 @@
self.reload()
def process_resource(self, rdirectory, rid):
+ cachefile = osp.join(self._cache_directory, rid)
+ self.debug('processing %s/%s into %s',
+ rdirectory, rid, cachefile)
+ rcachedir = osp.dirname(cachefile)
+ if not osp.exists(rcachedir):
+ os.makedirs(rcachedir)
+ sourcefile = osp.join(rdirectory, rid)
+ with open(sourcefile) as f:
+ content = f.read()
+ # XXX replace % not followed by a paren by %% to avoid having to do
+ # this in the source css file ?
try:
- return self._cache[rid][0]
- except KeyError:
- cachefile = osp.join(self._cache_directory, rid)
- self.debug('caching processed %s/%s into %s',
- rdirectory, rid, cachefile)
- rcachedir = osp.dirname(cachefile)
- if not osp.exists(rcachedir):
- os.makedirs(rcachedir)
- sourcefile = osp.join(rdirectory, rid)
- content = file(sourcefile).read()
- # XXX replace % not followed by a paren by %% to avoid having to do
- # this in the source css file ?
- try:
- content = self.compile(content)
- except ValueError as ex:
- self.error("can't process %s/%s: %s", rdirectory, rid, ex)
- adirectory = rdirectory
- else:
- stream = file(cachefile, 'w')
+ content = self.compile(content)
+ except ValueError as ex:
+ self.error("can't process %s/%s: %s", rdirectory, rid, ex)
+ adirectory = rdirectory
+ else:
+ tmpfd, tmpfile = tempfile.mkstemp(dir=rcachedir, prefix=osp.basename(cachefile))
+ with os.fdopen(tmpfd, 'w') as stream:
stream.write(content)
- stream.close()
- adirectory = self._cache_directory
- self._cache[rid] = (adirectory, rdirectory, os.stat(sourcefile).st_mtime)
- return adirectory
+ os.rename(tmpfile, cachefile)
+ adirectory = self._cache_directory
+ return adirectory
def compile(self, content):
return self._percent_rgx.sub('%%', content) % self
diff -r 3a98422df969 -r 7ceb0971c694 web/request.py
--- a/web/request.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/request.py Wed Dec 16 11:23:48 2015 +0100
@@ -22,15 +22,16 @@
import time
import random
import base64
-import urllib
-from StringIO import StringIO
from hashlib import sha1 # pylint: disable=E0611
-from Cookie import SimpleCookie
from calendar import timegm
from datetime import date, datetime
-from urlparse import urlsplit
-import httplib
from warnings import warn
+from io import BytesIO
+
+from six import PY2, binary_type, text_type, string_types
+from six.moves import http_client
+from six.moves.urllib.parse import urlsplit, quote as urlquote
+from six.moves.http_cookies import SimpleCookie
from rql.utils import rqlvar_maker
@@ -41,7 +42,7 @@
from cubicweb import AuthenticationError
from cubicweb.req import RequestSessionBase
from cubicweb.uilib import remove_html_tags, js
-from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid
+from cubicweb.utils import HTMLHead, make_uid
from cubicweb.view import TRANSITIONAL_DOCTYPE_NOEXT
from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
RequestError, StatusResponse)
@@ -51,7 +52,7 @@
_MARKER = object()
def build_cb_uid(seed):
- sha = sha1('%s%s%s' % (time.time(), seed, random.random()))
+ sha = sha1(('%s%s%s' % (time.time(), seed, random.random())).encode('ascii'))
return 'cb_%s' % (sha.hexdigest())
@@ -137,12 +138,12 @@
#: received headers
self._headers_in = Headers()
if headers is not None:
- for k, v in headers.iteritems():
+ for k, v in headers.items():
self._headers_in.addRawHeader(k, v)
#: form parameters
self.setup_params(form)
#: received body
- self.content = StringIO()
+ self.content = BytesIO()
# prepare output header
#: Header used for the final response
self.headers_out = Headers()
@@ -242,7 +243,7 @@
'__redirectvid', '__redirectrql'))
def setup_params(self, params):
- """WARNING: we're intentionaly leaving INTERNAL_FIELD_VALUE here
+ """WARNING: we're intentionally leaving INTERNAL_FIELD_VALUE here
subclasses should overrides to
"""
@@ -250,12 +251,13 @@
if params is None:
return
encoding = self.encoding
- for param, val in params.iteritems():
+ for param, val in params.items():
if isinstance(val, (tuple, list)):
- val = [unicode(x, encoding) for x in val]
+ if PY2:
+ val = [unicode(x, encoding) for x in val]
if len(val) == 1:
val = val[0]
- elif isinstance(val, str):
+ elif PY2 and isinstance(val, str):
val = unicode(val, encoding)
if param in self.no_script_form_params and val:
val = self.no_script_form_param(param, val)
@@ -317,7 +319,7 @@
return None
def set_message(self, msg):
- assert isinstance(msg, unicode)
+ assert isinstance(msg, text_type)
self.reset_message()
self._msg = msg
@@ -330,7 +332,7 @@
def set_redirect_message(self, msg):
# TODO - this should probably be merged with append_to_redirect_message
- assert isinstance(msg, unicode)
+ assert isinstance(msg, text_type)
msgid = self.redirect_message_id()
self.session.data[msgid] = msg
return msgid
@@ -396,26 +398,6 @@
return False
return True
- def update_breadcrumbs(self):
- """stores the last visisted page in session data"""
- searchstate = self.search_state[0]
- if searchstate == 'normal':
- breadcrumbs = self.session.data.get('breadcrumbs')
- if breadcrumbs is None:
- breadcrumbs = SizeConstrainedList(10)
- self.session.data['breadcrumbs'] = breadcrumbs
- breadcrumbs.append(self.url())
- else:
- url = self.url()
- if breadcrumbs and breadcrumbs[-1] != url:
- breadcrumbs.append(url)
-
- def last_visited_page(self):
- breadcrumbs = self.session.data.get('breadcrumbs')
- if breadcrumbs:
- return breadcrumbs.pop()
- return self.base_url()
-
# web edition helpers #####################################################
@cached # so it's writed only once
@@ -437,7 +419,7 @@
eids = form['eid']
except KeyError:
raise NothingToEdit(self._('no selected entities'))
- if isinstance(eids, basestring):
+ if isinstance(eids, string_types):
eids = (eids,)
for peid in eids:
if withtype:
@@ -569,18 +551,18 @@
header = [disposition]
unicode_filename = None
try:
- ascii_filename = filename.encode('ascii')
+ ascii_filename = filename.encode('ascii').decode('ascii')
except UnicodeEncodeError:
# fallback filename for very old browser
unicode_filename = filename
- ascii_filename = filename.encode('ascii', 'ignore')
+ ascii_filename = filename.encode('ascii', 'ignore').decode('ascii')
# escape " and \
# see http://greenbytes.de/tech/tc2231/#attwithfilenameandextparamescaped
ascii_filename = ascii_filename.replace('\x5c', r'\\').replace('"', r'\"')
header.append('filename="%s"' % ascii_filename)
if unicode_filename is not None:
# encoded filename according RFC5987
- urlquoted_filename = urllib.quote(unicode_filename.encode('utf-8'), '')
+ urlquoted_filename = urlquote(unicode_filename.encode('utf-8'), '')
header.append("filename*=utf-8''" + urlquoted_filename)
self.set_header('content-disposition', ';'.join(header))
@@ -596,7 +578,7 @@
:param localfile: if True, the default data dir prefix is added to the
JS filename
"""
- if isinstance(jsfiles, basestring):
+ if isinstance(jsfiles, string_types):
jsfiles = (jsfiles,)
for jsfile in jsfiles:
if localfile:
@@ -616,7 +598,7 @@
the css inclusion. cf:
http://msdn.microsoft.com/en-us/library/ms537512(VS.85).aspx
"""
- if isinstance(cssfiles, basestring):
+ if isinstance(cssfiles, string_types):
cssfiles = (cssfiles,)
if ieonly:
if self.ie_browser():
@@ -702,7 +684,7 @@
return urlsplit(self.base_url())[2]
def data_url(self, relpath):
- """returns the absolute path for a data resouce"""
+ """returns the absolute path for a data resource"""
return self.datadir_url + relpath
@cached
@@ -722,25 +704,19 @@
Some response cache headers may be set by this method.
"""
modified = True
- if self.get_header('Cache-Control') not in ('max-age=0', 'no-cache'):
- # Here, we search for any invalid 'not modified' condition
- # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3
- validators = get_validators(self._headers_in)
- if validators: # if we have no
- modified = any(func(val, self.headers_out) for func, val in validators)
+ # Here, we search for any invalid 'not modified' condition
+ # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3
+ validators = get_validators(self._headers_in)
+ if validators: # if we have no
+ modified = any(func(val, self.headers_out) for func, val in validators)
# Forge expected response
- if modified:
- if 'Expires' not in self.headers_out:
- # Expires header seems to be required by IE7 -- Are you sure ?
- self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
- # /!\ no raise, the function returns and we keep processing the request
- else:
+ if not modified:
# overwrite headers_out to forge a brand new not-modified response
self.headers_out = self._forge_cached_headers()
if self.http_method() in ('HEAD', 'GET'):
- self.status_out = httplib.NOT_MODIFIED
+ self.status_out = http_client.NOT_MODIFIED
else:
- self.status_out = httplib.PRECONDITION_FAILED
+ self.status_out = http_client.PRECONDITION_FAILED
# XXX replace by True once validate_cache bw compat method is dropped
return self.status_out
# XXX replace by False once validate_cache bw compat method is dropped
@@ -800,7 +776,7 @@
def header_accept_language(self):
"""returns an ordered list of preferred languages"""
acceptedlangs = self.get_header('Accept-Language', raw=False) or {}
- for lang, _ in sorted(acceptedlangs.iteritems(), key=lambda x: x[1],
+ for lang, _ in sorted(acceptedlangs.items(), key=lambda x: x[1],
reverse=True):
lang = lang.split('-')[0]
yield lang
@@ -844,7 +820,7 @@
scheme = scheme.lower()
try:
assert scheme == "basic"
- user, passwd = base64.decodestring(rest).split(":", 1)
+ user, passwd = base64.decodestring(rest.encode('ascii')).split(b":", 1)
# XXX HTTP header encoding: use email.Header?
return user.decode('UTF8'), passwd
except Exception as ex:
@@ -966,8 +942,10 @@
def __getattribute__(self, attr):
raise AuthenticationError()
- def __nonzero__(self):
+ def __bool__(self):
return False
+
+ __nonzero__ = __bool__
class _MockAnonymousSession(object):
sessionid = 'thisisnotarealsession'
@@ -1023,8 +1001,8 @@
self.set_language(lang)
except KeyError:
# this occurs usually during test execution
- self._ = self.__ = unicode
- self.pgettext = lambda x, y: unicode(y)
+ self._ = self.__ = text_type
+ self.pgettext = lambda x, y: text_type(y)
entity_metas = _cnx_func('entity_metas')
source_defs = _cnx_func('source_defs')
diff -r 3a98422df969 -r 7ceb0971c694 web/schemaviewer.py
--- a/web/schemaviewer.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/schemaviewer.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,7 +18,9 @@
"""an helper class to display CubicWeb schema using ureports"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
+
+from six import string_types
from logilab.common.ureports import Section, Title, Table, Link, Span, Text
@@ -218,7 +220,7 @@
elif prop == 'constraints':
val = ', '.join([c.expression for c in val])
elif isinstance(val, dict):
- for key, value in val.iteritems():
+ for key, value in val.items():
if isinstance(value, (list, tuple)):
val[key] = ', '.join(sorted( str(v) for v in value))
val = str(val)
@@ -226,7 +228,7 @@
elif isinstance(val, (list, tuple)):
val = sorted(val)
val = ', '.join(str(v) for v in val)
- elif val and isinstance(val, basestring):
+ elif val and isinstance(val, string_types):
val = _(val)
else:
val = str(val)
diff -r 3a98422df969 -r 7ceb0971c694 web/test/data/schema.py
--- a/web/test/data/schema.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/data/schema.py Wed Dec 16 11:23:48 2015 +0100
@@ -20,6 +20,9 @@
String, Int, Datetime, Boolean, Float)
from yams.constraints import IntervalBoundConstraint
+from cubicweb import _
+
+
class Salesterm(EntityType):
described_by_test = SubjectRelation('File', cardinality='1*',
composite='subject', inlined=True)
diff -r 3a98422df969 -r 7ceb0971c694 web/test/data/static/jstests/ajax_url0.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/data/static/jstests/ajax_url0.html Wed Dec 16 11:23:48 2015 +0100
@@ -0,0 +1,3 @@
+
'))
# def test_json_exec(self):
# rql = 'Any T,N WHERE T is Tag, T name N'
@@ -824,7 +833,7 @@
rset.req = req
source = ctrl.publish()
# maydel jscall
- self.assertIn('ajaxBoxRemoveLinkedEntity', source)
+ self.assertIn(b'ajaxBoxRemoveLinkedEntity', source)
def test_pending_insertion(self):
with self.remote_calling('add_pending_inserts', [['12', 'tags', '13']]) as (_, req):
@@ -887,16 +896,16 @@
# silly tests
def test_external_resource(self):
with self.remote_calling('external_resource', 'RSS_LOGO') as (res, _):
- self.assertEqual(json_dumps(self.config.uiprops['RSS_LOGO']),
+ self.assertEqual(json_dumps(self.config.uiprops['RSS_LOGO']).encode('ascii'),
res)
def test_i18n(self):
with self.remote_calling('i18n', ['bimboom']) as (res, _):
- self.assertEqual(json_dumps(['bimboom']), res)
+ self.assertEqual(json_dumps(['bimboom']).encode('ascii'), res)
def test_format_date(self):
with self.remote_calling('format_date', '2007-01-01 12:00:00') as (res, _):
- self.assertEqual(json_dumps('2007/01/01'), res)
+ self.assertEqual(json_dumps('2007/01/01').encode('ascii'), res)
def test_ajaxfunc_noparameter(self):
@ajaxfunc
@@ -968,7 +977,7 @@
def js_foo(self):
return u'hello'
with self.remote_calling('foo') as (res, _):
- self.assertEqual(res, u'hello')
+ self.assertEqual(res, b'hello')
def test_monkeypatch_jsoncontroller_xhtmlize(self):
with self.assertRaises(RemoteCallFailed):
@@ -979,7 +988,7 @@
def js_foo(self):
return u'hello'
with self.remote_calling('foo') as (res, _):
- self.assertEqual(u'
hello
', res)
+ self.assertEqual(b'
hello
', res)
def test_monkeypatch_jsoncontroller_jsonize(self):
with self.assertRaises(RemoteCallFailed):
@@ -990,7 +999,7 @@
def js_foo(self):
return 12
with self.remote_calling('foo') as (res, _):
- self.assertEqual(res, '12')
+ self.assertEqual(res, b'12')
def test_monkeypatch_jsoncontroller_stdfunc(self):
@monkeypatch(JSonController)
@@ -998,7 +1007,7 @@
def js_reledit_form(self):
return 12
with self.remote_calling('reledit_form') as (res, _):
- self.assertEqual(res, '12')
+ self.assertEqual(res, b'12')
class UndoControllerTC(CubicWebTC):
@@ -1042,7 +1051,7 @@
"""
with self.admin_access.web_request() as req:
scheme, netloc, path, query, fragment = urlsplit(url)
- query_dict = url_parse_query(query)
+ query_dict = parse_qs(query)
expected_url = urljoin(req.base_url(), expected_path)
self.assertEqual( urlunsplit((scheme, netloc, path, None, None)), expected_url)
@@ -1058,17 +1067,6 @@
result = controller.publish(rset=None)
self.assertURLPath(cm.exception.location, rpath)
- def test_redirect_default(self):
- with self.admin_access.web_request() as req:
- txuuid = self.txuuid_toto_email
- req.form['txuuid'] = txuuid
- req.session.data['breadcrumbs'] = [ urljoin(req.base_url(), path)
- for path in ('tata', 'toto',)]
- controller = self.vreg['controllers'].select('undo', req)
- with self.assertRaises(Redirect) as cm:
- result = controller.publish(rset=None)
- self.assertURLPath(cm.exception.location, 'toto')
-
class LoginControllerTC(CubicWebTC):
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_views_baseviews.py
--- a/web/test/unittest_views_baseviews.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_views_baseviews.py Wed Dec 16 11:23:48 2015 +0100
@@ -129,8 +129,8 @@
source_lines = [line.strip()
for line in html_source.splitlines(False)
if line.strip()]
- self.assertListEqual(['',
- ''],
+ self.assertListEqual([b'',
+ b''],
source_lines[:2])
def test_set_doctype_no_reset_xmldecl(self):
@@ -151,9 +151,9 @@
source_lines = [line.strip()
for line in html_source.splitlines(False)
if line.strip()]
- self.assertListEqual([html_doctype,
- '',
- ''],
+ self.assertListEqual([html_doctype.encode('ascii'),
+ b'',
+ b''],
source_lines[:3])
if __name__ == '__main__':
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_views_csv.py
--- a/web/test/unittest_views_csv.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_views_csv.py Wed Dec 16 11:23:48 2015 +0100
@@ -30,19 +30,19 @@
self.assertEqual(req.headers_out.getRawHeaders('content-type'),
['text/comma-separated-values;charset=UTF-8'])
expected_data = "String;COUNT(CWUser)\nguests;1\nmanagers;1"
- self.assertMultiLineEqual(expected_data, data)
+ self.assertMultiLineEqual(expected_data, data.decode('utf-8'))
def test_csvexport_on_empty_rset(self):
"""Should return the CSV header.
"""
with self.admin_access.web_request() as req:
- rset = req.execute('Any GN,COUNT(X) GROUPBY GN ORDERBY GN '
- 'WHERE X in_group G, G name GN, X login "Miles"')
+ rset = req.execute(u'Any GN,COUNT(X) GROUPBY GN ORDERBY GN '
+ 'WHERE X in_group G, G name GN, X login "Miles"')
data = self.view('csvexport', rset, req=req)
self.assertEqual(req.headers_out.getRawHeaders('content-type'),
['text/comma-separated-values;charset=UTF-8'])
expected_data = "String;COUNT(CWUser)"
- self.assertMultiLineEqual(expected_data, data)
+ self.assertMultiLineEqual(expected_data, data.decode('utf-8'))
if __name__ == '__main__':
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_views_editforms.py
--- a/web/test/unittest_views_editforms.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_views_editforms.py Wed Dec 16 11:23:48 2015 +0100
@@ -255,4 +255,3 @@
if __name__ == '__main__':
unittest_main()
-
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_views_errorform.py
--- a/web/test/unittest_views_errorform.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_views_errorform.py Wed Dec 16 11:23:48 2015 +0100
@@ -50,8 +50,8 @@
req.data['excinfo'] = sys.exc_info()
req.data['ex'] = e
html = self.view('error', req=req)
- self.failUnless(re.search(r'^$',
+ self.assertTrue(re.search(b'^$',
html.source, re.M))
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_views_json.py
--- a/web/test/unittest_views_json.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_views_json.py Wed Dec 16 11:23:48 2015 +0100
@@ -16,12 +16,14 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see .
+from six import binary_type
+
from cubicweb.devtools.testlib import CubicWebTC
class JsonViewsTC(CubicWebTC):
anonymize = True
- res_jsonp_data = '[["guests", 1]]'
+ res_jsonp_data = b'[["guests", 1]]'
def setUp(self):
super(JsonViewsTC, self).setUp()
@@ -29,14 +31,15 @@
def test_json_rsetexport(self):
with self.admin_access.web_request() as req:
- rset = req.execute('Any GN,COUNT(X) GROUPBY GN ORDERBY GN WHERE X in_group G, G name GN')
+ rset = req.execute(
+ 'Any GN,COUNT(X) GROUPBY GN ORDERBY GN WHERE X in_group G, G name GN')
data = self.view('jsonexport', rset, req=req)
self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
self.assertListEqual(data, [["guests", 1], ["managers", 1]])
def test_json_rsetexport_empty_rset(self):
with self.admin_access.web_request() as req:
- rset = req.execute('Any X WHERE X is CWUser, X login "foobarbaz"')
+ rset = req.execute(u'Any X WHERE X is CWUser, X login "foobarbaz"')
data = self.view('jsonexport', rset, req=req)
self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
self.assertListEqual(data, [])
@@ -47,21 +50,24 @@
'rql': u'Any GN,COUNT(X) GROUPBY GN ORDERBY GN '
'WHERE X in_group G, G name GN'})
data = self.ctrl_publish(req, ctrl='jsonp')
- self.assertIsInstance(data, str)
- self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/javascript'])
+ self.assertIsInstance(data, binary_type)
+ self.assertEqual(req.headers_out.getRawHeaders('content-type'),
+ ['application/javascript'])
# because jsonp anonymizes data, only 'guests' group should be found
- self.assertEqual(data, 'foo(%s)' % self.res_jsonp_data)
+ self.assertEqual(data, b'foo(' + self.res_jsonp_data + b')')
def test_json_rsetexport_with_jsonp_and_bad_vid(self):
with self.admin_access.web_request() as req:
req.form.update({'callback': 'foo',
- 'vid': 'table', # <-- this parameter should be ignored by jsonp controller
+ # "vid" parameter should be ignored by jsonp controller
+ 'vid': 'table',
'rql': 'Any GN,COUNT(X) GROUPBY GN ORDERBY GN '
'WHERE X in_group G, G name GN'})
data = self.ctrl_publish(req, ctrl='jsonp')
- self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/javascript'])
+ self.assertEqual(req.headers_out.getRawHeaders('content-type'),
+ ['application/javascript'])
# result should be plain json, not the table view
- self.assertEqual(data, 'foo(%s)' % self.res_jsonp_data)
+ self.assertEqual(data, b'foo(' + self.res_jsonp_data + b')')
def test_json_ersetexport(self):
with self.admin_access.web_request() as req:
@@ -71,7 +77,7 @@
self.assertEqual(data[0]['name'], 'guests')
self.assertEqual(data[1]['name'], 'managers')
- rset = req.execute('Any G WHERE G is CWGroup, G name "foo"')
+ rset = req.execute(u'Any G WHERE G is CWGroup, G name "foo"')
data = self.view('ejsonexport', rset, req=req)
self.assertEqual(req.headers_out.getRawHeaders('content-type'), ['application/json'])
self.assertEqual(data, [])
@@ -79,7 +85,8 @@
class NotAnonymousJsonViewsTC(JsonViewsTC):
anonymize = False
- res_jsonp_data = '[["guests", 1], ["managers", 1]]'
+ res_jsonp_data = b'[["guests", 1], ["managers", 1]]'
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_views_searchrestriction.py
--- a/web/test/unittest_views_searchrestriction.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_views_searchrestriction.py Wed Dec 16 11:23:48 2015 +0100
@@ -37,62 +37,62 @@
@property
def select(self):
- return self.parse('Any B,(NOW - CD),S,V,U,GROUP_CONCAT(TN),VN,P,CD,BMD '
- 'GROUPBY B,CD,S,V,U,VN,P,BMD '
- 'WHERE B in_state S, B creation_date CD, '
- 'B modification_date BMD, T? tags B, T name TN, '
- 'V? bookmarked_by B, V title VN, B created_by U?, '
- 'B in_group P, P name "managers"')
+ return self.parse(u'Any B,(NOW - CD),S,V,U,GROUP_CONCAT(TN),VN,P,CD,BMD '
+ 'GROUPBY B,CD,S,V,U,VN,P,BMD '
+ 'WHERE B in_state S, B creation_date CD, '
+ 'B modification_date BMD, T? tags B, T name TN, '
+ 'V? bookmarked_by B, V title VN, B created_by U?, '
+ 'B in_group P, P name "managers"')
def test_1(self):
self.assertEqual(self._generate(self.select, 'in_state', 'subject', 'name'),
- "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
- "B in_state A, B is CWUser, A name C")
+ 'DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name "managers", '
+ 'B in_state A, B is CWUser, A name C')
def test_2(self):
self.assertEqual(self._generate(self.select, 'tags', 'object', 'name'),
- "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
- "A tags B, B is CWUser, A name C")
+ 'DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name "managers", '
+ 'A tags B, B is CWUser, A name C')
def test_3(self):
self.assertEqual(self._generate(self.select, 'created_by', 'subject', 'login'),
- "DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name 'managers', "
- "B created_by A, B is CWUser, A login C")
+ 'DISTINCT Any A,C ORDERBY C WHERE B in_group P, P name "managers", '
+ 'B created_by A, B is CWUser, A login C')
def test_4(self):
- self.assertEqual(self._generate(self.parse('Any X WHERE X is CWUser'), 'created_by', 'subject', 'login'),
+ self.assertEqual(self._generate(self.parse(u'Any X WHERE X is CWUser'), 'created_by', 'subject', 'login'),
"DISTINCT Any A,B ORDERBY B WHERE X is CWUser, X created_by A, A login B")
def test_5(self):
- self.assertEqual(self._generate(self.parse('Any X,L WHERE X is CWUser, X login L'), 'created_by', 'subject', 'login'),
+ self.assertEqual(self._generate(self.parse(u'Any X,L WHERE X is CWUser, X login L'), 'created_by', 'subject', 'login'),
"DISTINCT Any A,B ORDERBY B WHERE X is CWUser, X created_by A, A login B")
def test_nonregr1(self):
- select = self.parse('Any T,V WHERE T bookmarked_by V?, '
- 'V in_state VS, VS name "published", T created_by U')
+ select = self.parse(u'Any T,V WHERE T bookmarked_by V?, '
+ 'V in_state VS, VS name "published", T created_by U')
self.assertEqual(self._generate(select, 'created_by', 'subject', 'login'),
"DISTINCT Any A,B ORDERBY B WHERE T created_by U, "
"T created_by A, T is Bookmark, A login B")
def test_nonregr2(self):
#'DISTINCT Any X,TMP,N WHERE P name TMP, X version_of P, P is Project, X is Version, not X in_state S,S name "published", X num N ORDERBY TMP,N'
- select = self.parse('DISTINCT Any V,TN,L ORDERBY TN,L WHERE T nom TN, V connait T, T is Personne, V is CWUser,'
- 'NOT V in_state VS, VS name "published", V login L')
+ select = self.parse(u'DISTINCT Any V,TN,L ORDERBY TN,L WHERE T nom TN, V connait T, T is Personne, V is CWUser,'
+ 'NOT V in_state VS, VS name "published", V login L')
rschema = self.schema['connait']
- for rdefs in rschema.rdefs.itervalues():
+ for rdefs in rschema.rdefs.values():
rdefs.cardinality = '++'
try:
self.assertEqual(self._generate(select, 'in_state', 'subject', 'name'),
- "DISTINCT Any A,B ORDERBY B WHERE V is CWUser, "
- "NOT EXISTS(V in_state VS), VS name 'published', "
- "V in_state A, A name B")
+ 'DISTINCT Any A,B ORDERBY B WHERE V is CWUser, '
+ 'NOT EXISTS(V in_state VS), VS name "published", '
+ 'V in_state A, A name B')
finally:
- for rdefs in rschema.rdefs.itervalues():
+ for rdefs in rschema.rdefs.values():
rdefs.cardinality = '**'
def test_nonregr3(self):
#'DISTINCT Any X,TMP,N WHERE P name TMP, X version_of P, P is Project, X is Version, not X in_state S,S name "published", X num N ORDERBY TMP,N'
- select = self.parse('DISTINCT Any X, MAX(Y) GROUPBY X WHERE X is CWUser, Y is Bookmark, X in_group A')
+ select = self.parse(u'DISTINCT Any X, MAX(Y) GROUPBY X WHERE X is CWUser, Y is Bookmark, X in_group A')
self.assertEqual(self._generate(select, 'in_group', 'subject', 'name'),
"DISTINCT Any B,C ORDERBY C WHERE X is CWUser, X in_group B, B name C")
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_views_staticcontrollers.py
--- a/web/test/unittest_views_staticcontrollers.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_views_staticcontrollers.py Wed Dec 16 11:23:48 2015 +0100
@@ -70,12 +70,21 @@
with self._publish_static_files(fname) as req:
self.assertEqual(200, req.status_out)
self.assertIn('last-modified', req.headers_out)
+ self.assertIn('expires', req.headers_out)
+ self.assertEqual(req.get_response_header('cache-control'),
+ {'max-age': 604800})
next_headers = {
'if-modified-since': req.get_response_header('last-modified', raw=True),
}
with self._publish_static_files(fname, next_headers) as req:
self.assertEqual(304, req.status_out)
+ def _check_datafile_redirect(self, fname, expected):
+ with self._publish_static_files(fname) as req:
+ self.assertEqual(302, req.status_out)
+ self.assertEqual(req.get_response_header('location'),
+ req.base_url() + expected)
+
def _check_no_datafile(self, fname):
with self._publish_static_files(fname) as req:
self.assertEqual(404, req.status_out)
@@ -90,10 +99,12 @@
self._check_no_datafile('data/%s/cubicweb.css' % ('0'*len(hash)))
with tempattr(self.vreg.config, 'mode', 'notest'):
- self._check_datafile_ok('data/cubicweb.css')
+ self.config._init_base_url() # reset config.datadir_url
+ self._check_datafile_redirect('data/cubicweb.css', 'data/%s/cubicweb.css' % hash)
self._check_datafile_ok('data/%s/cubicweb.css' % hash)
- self._check_no_datafile('data/does/not/exist')
- self._check_no_datafile('data/%s/cubicweb.css' % ('0'*len(hash)))
+ self._check_no_datafile('data/%s/does/not/exist' % hash)
+ self._check_datafile_redirect('data/%s/does/not/exist' % ('0'*len(hash)),
+ 'data/%s/%s/does/not/exist' % (hash, '0'*len(hash)))
class ConcatFilesTC(CubicWebTC):
@@ -120,12 +131,12 @@
yield res, req
def expected_content(self, js_files):
- content = u''
+ content = b''
for js_file in js_files:
dirpath, rid = self.config.locate_resource(js_file)
if dirpath is not None: # ignore resources not found
- with open(osp.join(dirpath, rid)) as f:
- content += f.read() + '\n'
+ with open(osp.join(dirpath, rid), 'rb') as f:
+ content += f.read() + b'\n'
return content
def test_cache(self):
@@ -162,4 +173,3 @@
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()
-
diff -r 3a98422df969 -r 7ceb0971c694 web/test/unittest_viewselector.py
--- a/web/test/unittest_viewselector.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/test/unittest_viewselector.py Wed Dec 16 11:23:48 2015 +0100
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see .
"""XXX rename, split, reorganize this"""
+from __future__ import print_function
from logilab.common.testlib import unittest_main
@@ -76,9 +77,9 @@
try:
self.assertSetEqual(list(content), expected)
except Exception:
- print registry, sorted(expected), sorted(content)
- print 'no more', [v for v in expected if not v in content]
- print 'missing', [v for v in content if not v in expected]
+ print(registry, sorted(expected), sorted(content))
+ print('no more', [v for v in expected if not v in content])
+ print('missing', [v for v in content if not v in expected])
raise
def setUp(self):
@@ -421,7 +422,7 @@
def test_interface_selector(self):
with self.admin_access.web_request() as req:
- req.create_entity('File', data_name=u'bim.png', data=Binary('bim'))
+ req.create_entity('File', data_name=u'bim.png', data=Binary(b'bim'))
# image primary view priority
rset = req.execute('File X WHERE X data_name "bim.png"')
self.assertIsInstance(self.vreg['views'].select('primary', req, rset=rset),
@@ -430,21 +431,21 @@
def test_score_entity_selector(self):
with self.admin_access.web_request() as req:
- req.create_entity('File', data_name=u'bim.png', data=Binary('bim'))
+ req.create_entity('File', data_name=u'bim.png', data=Binary(b'bim'))
# image/ehtml primary view priority
rset = req.execute('File X WHERE X data_name "bim.png"')
self.assertIsInstance(self.vreg['views'].select('image', req, rset=rset),
idownloadable.ImageView)
self.assertRaises(NoSelectableObject, self.vreg['views'].select, 'ehtml', req, rset=rset)
- fileobj = req.create_entity('File', data_name=u'bim.html', data=Binary('bambam 1:
self.w(u"\n")
@@ -314,7 +316,7 @@
self.w(u'
\n')
else:
self.w(u'
\n' % (listid, klass or 'section'))
- for i in xrange(self.cw_rset.rowcount):
+ for i in range(self.cw_rset.rowcount):
self.cell_call(row=i, col=0, vid=subvid, klass=klass, **kwargs)
self.w(u'
\n')
if title:
@@ -393,7 +395,7 @@
@property
def title(self):
- etype = iter(self.cw_rset.column_types(0)).next()
+ etype = next(iter(self.cw_rset.column_types(0)))
return display_name(self._cw, etype, form='plural')
def call(self, **kwargs):
@@ -427,7 +429,7 @@
def call(self, subvid=None, **kwargs):
kwargs['vid'] = subvid
rset = self.cw_rset
- for i in xrange(len(rset)):
+ for i in range(len(rset)):
self.cell_call(i, 0, **kwargs)
if i < rset.rowcount-1:
self.w(self.separator)
diff -r 3a98422df969 -r 7ceb0971c694 web/views/bookmark.py
--- a/web/views/bookmark.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/bookmark.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,7 +18,7 @@
"""Primary view for bookmarks + user's bookmarks box"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from logilab.mtconverter import xml_escape
diff -r 3a98422df969 -r 7ceb0971c694 web/views/boxes.py
--- a/web/views/boxes.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/boxes.py Wed Dec 16 11:23:48 2015 +0100
@@ -26,10 +26,12 @@
* startup views box
"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from warnings import warn
+from six import text_type, add_metaclass
+
from logilab.mtconverter import xml_escape
from logilab.common.deprecation import class_deprecated
@@ -93,7 +95,7 @@
etypes = self.cw_rset.column_types(0)
if len(etypes) == 1:
plural = self.cw_rset.rowcount > 1 and 'plural' or ''
- etypelabel = display_name(self._cw, iter(etypes).next(), plural)
+ etypelabel = display_name(self._cw, next(iter(etypes)), plural)
title = u'%s - %s' % (title, etypelabel.lower())
w(title)
@@ -216,7 +218,7 @@
@property
def domid(self):
- return super(RsetBox, self).domid + unicode(abs(id(self))) + unicode(abs(id(self.cw_rset)))
+ return super(RsetBox, self).domid + text_type(abs(id(self))) + text_type(abs(id(self.cw_rset)))
def render_title(self, w):
w(self.cw_extra_kwargs['title'])
@@ -231,9 +233,9 @@
# helper classes ##############################################################
+@add_metaclass(class_deprecated)
class SideBoxView(EntityView):
"""helper view class to display some entities in a sidebox"""
- __metaclass__ = class_deprecated
__deprecation_warning__ = '[3.10] SideBoxView is deprecated, use RsetBox instead (%(cls)s)'
__regid__ = 'sidebox'
diff -r 3a98422df969 -r 7ceb0971c694 web/views/calendar.py
--- a/web/views/calendar.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/calendar.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,7 +18,7 @@
"""html calendar views"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
import copy
from datetime import timedelta
diff -r 3a98422df969 -r 7ceb0971c694 web/views/csvexport.py
--- a/web/views/csvexport.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/csvexport.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,7 +18,10 @@
"""csv export views"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
+
+from six import PY2
+from six.moves import range
from cubicweb.schema import display_name
from cubicweb.predicates import any_rset, empty_rset
@@ -29,7 +32,7 @@
"""mixin class for CSV views"""
templatable = False
content_type = "text/comma-separated-values"
- binary = True # avoid unicode assertion
+ binary = PY2 # python csv module is unicode aware in py3k
csv_params = {'dialect': 'excel',
'quotechar': '"',
'delimiter': ';',
@@ -88,7 +91,7 @@
rows_by_type = {}
writer = self.csvwriter()
rowdef_by_type = {}
- for index in xrange(len(self.cw_rset)):
+ for index in range(len(self.cw_rset)):
entity = self.cw_rset.complete_entity(index)
if entity.e_schema not in rows_by_type:
rowdef_by_type[entity.e_schema] = [rs for rs, at in entity.e_schema.attribute_definitions()
@@ -98,8 +101,7 @@
rows = rows_by_type[entity.e_schema]
rows.append([entity.printable_value(rs.type, format='text/plain')
for rs in rowdef_by_type[entity.e_schema]])
- for rows in rows_by_type.itervalues():
+ for rows in rows_by_type.values():
writer.writerows(rows)
# use two empty lines as separator
writer.writerows([[], []])
-
diff -r 3a98422df969 -r 7ceb0971c694 web/views/cwproperties.py
--- a/web/views/cwproperties.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/cwproperties.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,7 +18,7 @@
"""Specific views for CWProperty (eg site/user preferences"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from logilab.mtconverter import xml_escape
@@ -119,10 +119,10 @@
_ = self._cw._
self.w(u'
%s
\n' % _(self.title))
for label, group, form in sorted((_(g), g, f)
- for g, f in mainforms.iteritems()):
+ for g, f in mainforms.items()):
self.wrap_main_form(group, label, form)
for label, group, objects in sorted((_(g), g, o)
- for g, o in groupedforms.iteritems()):
+ for g, o in groupedforms.items()):
self.wrap_grouped_form(group, label, objects)
@property
@@ -171,7 +171,7 @@
entity = self.cwprops_rset.get_entity(values[key], 0)
else:
entity = self._cw.vreg['etypes'].etype_class('CWProperty')(self._cw)
- entity.eid = self._cw.varmaker.next()
+ entity.eid = next(self._cw.varmaker)
entity.cw_attr_cache['pkey'] = key
entity.cw_attr_cache['value'] = self._cw.vreg.property_value(key)
return entity
@@ -224,7 +224,7 @@
(make_togglable_link('fieldset_' + group, label)))
self.w(u'
' % (group, status))
sorted_objects = sorted((self._cw.__('%s_%s' % (group, o)), o, f)
- for o, f in objects.iteritems())
+ for o, f in objects.items())
for label, oid, form in sorted_objects:
self.wrap_object_form(group, oid, label, form)
self.w(u'
')
diff -r 3a98422df969 -r 7ceb0971c694 web/views/cwsources.py
--- a/web/views/cwsources.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/cwsources.py Wed Dec 16 11:23:48 2015 +0100
@@ -20,20 +20,23 @@
"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
import logging
from itertools import repeat
+
+from six.moves import range
+
from logilab.mtconverter import xml_escape
from logilab.common.decorators import cachedproperty
from cubicweb import Unauthorized, tags
from cubicweb.utils import make_uid
from cubicweb.predicates import (is_instance, score_entity, has_related_entities,
- match_user_groups, match_kwargs, match_view)
+ match_user_groups, match_kwargs, match_view, one_line_rset)
from cubicweb.view import EntityView, StartupView
from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES, display_name
-from cubicweb.web import formwidgets as wdgs, facet
+from cubicweb.web import Redirect, formwidgets as wdgs, facet, action
from cubicweb.web.views import add_etype_button
from cubicweb.web.views import (uicfg, tabs, actions, ibreadcrumbs, navigation,
tableview, pyviews)
@@ -95,7 +98,7 @@
if hostconfig:
self.w(u'
%s
' % self._cw._('CWSourceHostConfig_plural'))
self._cw.view('table', hostconfig, w=self.w,
- displaycols=range(2),
+ displaycols=list(range(2)),
cellvids={1: 'editable-final'})
@@ -186,7 +189,7 @@
warning(_('relation %(rtype)s with %(etype)s as %(role)s is '
'supported but no target type supported') %
{'rtype': rschema, 'role': role, 'etype': etype})
- for rtype, rdefs in self.srelations.iteritems():
+ for rtype, rdefs in self.srelations.items():
if rdefs is None:
rschema = self.schema[rtype]
for subj, obj in rschema.rdefs:
@@ -223,6 +226,36 @@
layout_args = {'display_filter': 'top'}
+class CWSourceSyncAction(action.Action):
+ __regid__ = 'cw.source-sync'
+ __select__ = (action.Action.__select__ & match_user_groups('managers')
+ & one_line_rset() & is_instance('CWSource')
+ & score_entity(lambda x: x.name != 'system'))
+
+ title = _('synchronize')
+ category = 'mainactions'
+ order = 20
+
+ def url(self):
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ return entity.absolute_url(vid=self.__regid__)
+
+
+class CWSourceSyncView(EntityView):
+ __regid__ = 'cw.source-sync'
+ __select__ = (match_user_groups('managers')
+ & one_line_rset() & is_instance('CWSource')
+ & score_entity(lambda x: x.name != 'system'))
+
+ title = _('synchronize')
+
+ def entity_call(self, entity):
+ self._cw.call_service('source-sync', source_eid=entity.eid)
+ msg = self._cw._('Source has been synchronized')
+ url = entity.absolute_url(tab='cwsource-imports', __message=msg)
+ raise Redirect(url)
+
+
# sources management view ######################################################
diff -r 3a98422df969 -r 7ceb0971c694 web/views/cwuser.py
--- a/web/views/cwuser.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/cwuser.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,10 +18,13 @@
"""Specific views for users and groups"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from hashlib import sha1 # pylint: disable=E0611
+from six import text_type
+from six.moves import range
+
from logilab.mtconverter import xml_escape
from cubicweb import tags
@@ -64,7 +67,7 @@
'''% self._cw.encoding)
- for i in xrange(self.cw_rset.rowcount):
+ for i in range(self.cw_rset.rowcount):
self.cell_call(i, 0)
self.w(u'\n')
@@ -250,6 +253,6 @@
'group': tableview.MainEntityColRenderer(),
'nb_users': tableview.EntityTableColRenderer(
header=_('num. users'),
- renderfunc=lambda w,x: w(unicode(x.num_users())),
+ renderfunc=lambda w,x: w(text_type(x.num_users())),
sortfunc=lambda x: x.num_users()),
}
diff -r 3a98422df969 -r 7ceb0971c694 web/views/debug.py
--- a/web/views/debug.py Tue Dec 15 19:05:41 2015 +0100
+++ b/web/views/debug.py Wed Dec 16 11:23:48 2015 +0100
@@ -18,10 +18,12 @@
"""management and error screens"""
__docformat__ = "restructuredtext en"
-_ = unicode
+from cubicweb import _
from time import strftime, localtime
+from six import text_type
+
from logilab.mtconverter import xml_escape
from cubicweb.predicates import none_rset, match_user_groups
@@ -33,7 +35,7 @@
if dict:
w(u'