--- a/__init__.py Fri Jul 18 16:44:44 2014 +0200
+++ b/__init__.py Fri Jul 18 17:35:25 2014 +0200
@@ -44,10 +44,8 @@
from logilab.common.logging_ext import set_log_methods
from yams.constraints import BASE_CONVERTERS
-if os.environ.get('APYCOT_ROOT'):
- logging.basicConfig(level=logging.CRITICAL)
-else:
- logging.basicConfig()
+# pre python 2.7.2 safety
+logging.basicConfig()
from cubicweb.__pkginfo__ import version as __version__
--- a/cwconfig.py Fri Jul 18 16:44:44 2014 +0200
+++ b/cwconfig.py Fri Jul 18 17:35:25 2014 +0200
@@ -827,13 +827,6 @@
else:
_INSTANCES_DIR = join(_INSTALL_PREFIX, 'etc', 'cubicweb.d')
- if os.environ.get('APYCOT_ROOT'):
- _cubes_init = join(CubicWebNoAppConfiguration.CUBES_DIR, '__init__.py')
- if not exists(_cubes_init):
- file(join(_cubes_init), 'w').close()
- if not exists(_INSTANCES_DIR):
- os.makedirs(_INSTANCES_DIR)
-
# set to true during repair (shell, migration) to allow some things which
# wouldn't be possible otherwise
repairing = False
--- a/cwctl.py Fri Jul 18 16:44:44 2014 +0200
+++ b/cwctl.py Fri Jul 18 17:35:25 2014 +0200
@@ -835,6 +835,8 @@
config = cwcfg.config_for(appid)
# should not raise error if db versions don't match fs versions
config.repairing = True
+ # no need to load all appobjects and schema
+ config.quick_start = True
if hasattr(config, 'set_sources_mode'):
config.set_sources_mode(('migration',))
repo = config.migration_handler().repo_connect()
--- a/entities/wfobjs.py Fri Jul 18 16:44:44 2014 +0200
+++ b/entities/wfobjs.py Fri Jul 18 17:35:25 2014 +0200
@@ -27,7 +27,6 @@
from logilab.common.decorators import cached, clear_cache
from logilab.common.deprecation import deprecated
-from logilab.common.compat import any
from cubicweb.entities import AnyEntity, fetch_config
from cubicweb.view import EntityAdapter
--- a/hooks/email.py Fri Jul 18 16:44:44 2014 +0200
+++ b/hooks/email.py Fri Jul 18 17:35:25 2014 +0200
@@ -21,8 +21,6 @@
from cubicweb.server import hook
-from logilab.common.compat import any
-
class SetUseEmailRelationOp(hook.Operation):
"""delay this operation to commit to avoid conflict with a late rql query
--- a/migration.py Fri Jul 18 16:44:44 2014 +0200
+++ b/migration.py Fri Jul 18 17:35:25 2014 +0200
@@ -247,12 +247,13 @@
local_ctx = self._create_context()
try:
import readline
- from rlcompleter import Completer
+ from cubicweb.toolsutils import CWShellCompleter
except ImportError:
# readline not available
pass
else:
- readline.set_completer(Completer(local_ctx).complete)
+ rql_completer = CWShellCompleter(local_ctx)
+ readline.set_completer(rql_completer.complete)
readline.parse_and_bind('tab: complete')
home_key = 'HOME'
if sys.platform == 'win32':
--- a/predicates.py Fri Jul 18 16:44:44 2014 +0200
+++ b/predicates.py Fri Jul 18 17:35:25 2014 +0200
@@ -188,7 +188,6 @@
from warnings import warn
from operator import eq
-from logilab.common.compat import all, any
from logilab.common.interface import implements as implements_iface
from logilab.common.registry import Predicate, objectify_predicate, yes
--- a/req.py Fri Jul 18 16:44:44 2014 +0200
+++ b/req.py Fri Jul 18 17:35:25 2014 +0200
@@ -485,12 +485,16 @@
raise ValueError(self._('can\'t parse %(value)r (expected %(format)s)')
% {'value': value, 'format': format})
+ def _base_url(self, secure=None):
+ if secure:
+ return self.vreg.config.get('https-url') or self.vreg.config['base-url']
+ return self.vreg.config['base-url']
+
def base_url(self, secure=None):
"""return the root url of the instance
"""
- if secure:
- return self.vreg.config.get('https-url') or self.vreg.config['base-url']
- return self.vreg.config['base-url']
+ url = self._base_url(secure=secure)
+ return url if url is None else url.rstrip('/') + '/'
# abstract methods to override according to the web front-end #############
--- a/schema.py Fri Jul 18 16:44:44 2014 +0200
+++ b/schema.py Fri Jul 18 17:35:25 2014 +0200
@@ -31,7 +31,6 @@
from logilab.common.deprecation import deprecated, class_moved, moved
from logilab.common.textutils import splitstrip
from logilab.common.graph import get_cycles
-from logilab.common.compat import any
from yams import BadSchemaDefinition, buildobjs as ybo
from yams.schema import Schema, ERSchema, EntitySchema, RelationSchema, \
--- a/server/querier.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/querier.py Fri Jul 18 17:35:25 2014 +0200
@@ -22,7 +22,6 @@
from itertools import repeat
-from logilab.common.compat import any
from rql import RQLSyntaxError, CoercionError
from rql.stmts import Union
from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj, Relation, Exists, Not
--- a/server/rqlannotation.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/rqlannotation.py Fri Jul 18 17:35:25 2014 +0200
@@ -21,8 +21,6 @@
__docformat__ = "restructuredtext en"
-from logilab.common.compat import any
-
from rql import BadRQLQuery
from rql.nodes import Relation, VariableRef, Constant, Variable, Or, Exists
from rql.utils import common_parent
--- a/server/schemaserial.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/schemaserial.py Fri Jul 18 17:35:25 2014 +0200
@@ -309,19 +309,14 @@
"""synchronize schema and permissions in the database according to
current schema
"""
- quiet = os.environ.get('APYCOT_ROOT')
- if not quiet:
- _title = '-> storing the schema in the database '
- print _title,
+ _title = '-> storing the schema in the database '
+ print _title,
execute = cnx.execute
eschemas = schema.entities()
- if not quiet:
- pb_size = (len(eschemas + schema.relations())
- + len(CONSTRAINTS)
- + len([x for x in eschemas if x.specializes()]))
- pb = ProgressBar(pb_size, title=_title)
- else:
- pb = None
+ pb_size = (len(eschemas + schema.relations())
+ + len(CONSTRAINTS)
+ + len([x for x in eschemas if x.specializes()]))
+ pb = ProgressBar(pb_size, title=_title)
groupmap = group_mapping(cnx, interactive=False)
# serialize all entity types, assuring CWEType is serialized first for proper
# is / is_instance_of insertion
@@ -366,8 +361,7 @@
execute(rql, kwargs, build_descr=False)
if pb is not None:
pb.update()
- if not quiet:
- print
+ print
# high level serialization functions
--- a/server/sources/__init__.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/sources/__init__.py Fri Jul 18 17:35:25 2014 +0200
@@ -105,7 +105,7 @@
self.support_relations['identity'] = False
self.eid = eid
self.public_config = source_config.copy()
- self.public_config.setdefault('use-cwuri-as-url', self.use_cwuri_as_url)
+ self.public_config['use-cwuri-as-url'] = self.use_cwuri_as_url
self.remove_sensitive_information(self.public_config)
self.uri = source_config.pop('uri')
set_log_methods(self, getLogger('cubicweb.sources.'+self.uri))
--- a/server/sources/datafeed.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/sources/datafeed.py Fri Jul 18 17:35:25 2014 +0200
@@ -83,6 +83,13 @@
'help': ('Timeout of HTTP GET requests, when synchronizing a source.'),
'group': 'datafeed-source', 'level': 2,
}),
+ ('use-cwuri-as-url',
+ {'type': 'yn',
+ 'default': None, # explicitly unset
+ 'help': ('Use cwuri (i.e. external URL) for link to the entity '
+ 'instead of its local URL.'),
+ 'group': 'datafeed-source', 'level': 1,
+ }),
)
def check_config(self, source_entity):
@@ -107,6 +114,12 @@
self.synchro_interval = timedelta(seconds=typed_config['synchronization-interval'])
self.max_lock_lifetime = timedelta(seconds=typed_config['max-lock-lifetime'])
self.http_timeout = typed_config['http-timeout']
+ # if typed_config['use-cwuri-as-url'] is set, we have to update
+ # use_cwuri_as_url attribute and public configuration dictionary
+ # accordingly
+ if typed_config['use-cwuri-as-url'] is not None:
+ self.use_cwuri_as_url = typed_config['use-cwuri-as-url']
+ self.public_config['use-cwuri-as-url'] = self.use_cwuri_as_url
def init(self, activated, source_entity):
super(DataFeedSource, self).init(activated, source_entity)
@@ -285,12 +298,39 @@
self.stats = {'created': set(), 'updated': set(), 'checked': set()}
def normalize_url(self, url):
- from cubicweb.sobjects import URL_MAPPING # available after registration
+ """Normalize an url by looking if there is a replacement for it in
+ `cubicweb.sobjects.URL_MAPPING`.
+
+ This dictionary allow to redirect from one host to another, which may be
+ useful for example in case of test instance using production data, while
+ you don't want to load the external source nor to hack your `/etc/hosts`
+ file.
+ """
+ # local import mandatory, it's available after registration
+ from cubicweb.sobjects import URL_MAPPING
for mappedurl in URL_MAPPING:
if url.startswith(mappedurl):
return url.replace(mappedurl, URL_MAPPING[mappedurl], 1)
return url
+ def retrieve_url(self, url, data=None, headers=None):
+ """Return stream linked by the given url:
+ * HTTP urls will be normalized (see :meth:`normalize_url`)
+ * handle file:// URL
+ * other will be considered as plain content, useful for testing purpose
+ """
+ if url.startswith('http'):
+ url = self.normalize_url(url)
+ if data:
+ self.source.info('POST %s %s', url, data)
+ else:
+ self.source.info('GET %s', url)
+ req = urllib2.Request(url, data, headers)
+ return _OPENER.open(req, timeout=self.source.http_timeout)
+ if url.startswith('file://'):
+ return URLLibResponseAdapter(open(url[7:]), url)
+ return URLLibResponseAdapter(StringIO.StringIO(url), url)
+
def add_schema_config(self, schemacfg, checkonly=False):
"""added CWSourceSchemaConfig, modify mapping accordingly"""
msg = schemacfg._cw._("this parser doesn't use a mapping")
@@ -427,14 +467,7 @@
return error
def parse(self, url):
- if url.startswith('http'):
- url = self.normalize_url(url)
- self.source.info('GET %s', url)
- stream = _OPENER.open(url, timeout=self.source.http_timeout)
- elif url.startswith('file://'):
- stream = open(url[7:])
- else:
- stream = StringIO.StringIO(url)
+ stream = self.retrieve_url(url)
return self.parse_etree(etree.parse(stream).getroot())
def parse_etree(self, document):
@@ -455,6 +488,27 @@
return exists(extid[7:])
return False
+
+class URLLibResponseAdapter(object):
+ """Thin wrapper to be used to fake a value returned by urllib2.urlopen"""
+ def __init__(self, stream, url, code=200):
+ self._stream = stream
+ self._url = url
+ self.code = code
+
+ def read(self, *args):
+ return self._stream.read(*args)
+
+ def geturl(self):
+ return self._url
+
+ def getcode(self):
+ return self.code
+
+ def info(self):
+ from mimetools import Message
+ return Message(StringIO.StringIO())
+
# use a cookie enabled opener to use session cookie if any
_OPENER = urllib2.build_opener()
try:
--- a/server/sources/native.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/sources/native.py Fri Jul 18 17:35:25 2014 +0200
@@ -42,7 +42,6 @@
import logging
import sys
-from logilab.common.compat import any
from logilab.common.decorators import cached, clear_cache
from logilab.common.configuration import Method
from logilab.common.shellutils import getlogin
@@ -323,10 +322,16 @@
'want trusted authentication for the database connection',
'group': 'native-source', 'level': 2,
}),
+ ('db-statement-timeout',
+ {'type': 'int',
+ 'default': 0,
+ 'help': 'sql statement timeout, in milliseconds (postgres only)',
+ 'group': 'native-source', 'level': 2,
+ }),
)
def __init__(self, repo, source_config, *args, **kwargs):
- SQLAdapterMixIn.__init__(self, source_config)
+ SQLAdapterMixIn.__init__(self, source_config, repairing=repo.config.repairing)
self.authentifiers = [LoginPasswordAuthentifier(self)]
if repo.config['allow-email-login']:
self.authentifiers.insert(0, EmailPasswordAuthentifier(self))
--- a/server/sqlutils.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/sqlutils.py Fri Jul 18 17:35:25 2014 +0200
@@ -47,7 +47,7 @@
return subprocess.call(cmd)
-def sqlexec(sqlstmts, cursor_or_execute, withpb=not os.environ.get('APYCOT_ROOT'),
+def sqlexec(sqlstmts, cursor_or_execute, withpb=True,
pbtitle='', delimiter=';', cnx=None):
"""execute sql statements ignoring DROP/ CREATE GROUP or USER statements
error.
@@ -299,7 +299,7 @@
"""
cnx_wrap = ConnectionWrapper
- def __init__(self, source_config):
+ def __init__(self, source_config, repairing=False):
try:
self.dbdriver = source_config['db-driver'].lower()
dbname = source_config['db-name']
@@ -328,6 +328,14 @@
if self.dbdriver == 'sqlite':
self.cnx_wrap = SqliteConnectionWrapper
self.dbhelper.dbname = abspath(self.dbhelper.dbname)
+ if not repairing:
+ statement_timeout = int(source_config.get('db-statement-timeout', 0))
+ if statement_timeout > 0:
+ def set_postgres_timeout(cnx):
+ cnx.cursor().execute('SET statement_timeout to %d' % statement_timeout)
+ cnx.commit()
+ postgres_hooks = SQL_CONNECT_HOOKS['postgres']
+ postgres_hooks.append(set_postgres_timeout)
def wrapped_connection(self):
"""open and return a connection to the database, wrapped into a class
--- a/server/test/unittest_datafeed.py Fri Jul 18 16:44:44 2014 +0200
+++ b/server/test/unittest_datafeed.py Fri Jul 18 17:35:25 2014 +0200
@@ -16,7 +16,9 @@
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+import mimetools
from datetime import timedelta
+from contextlib import contextmanager
from cubicweb.devtools.testlib import CubicWebTC
from cubicweb.server.sources import datafeed
@@ -25,19 +27,14 @@
class DataFeedTC(CubicWebTC):
def setup_database(self):
with self.admin_access.repo_cnx() as cnx:
- cnx.create_entity('CWSource', name=u'myfeed', type=u'datafeed',
- parser=u'testparser', url=u'ignored',
- config=u'synchronization-interval=1min')
- cnx.commit()
+ with self.base_parser(cnx):
+ cnx.create_entity('CWSource', name=u'myfeed', type=u'datafeed',
+ parser=u'testparser', url=u'ignored',
+ config=u'synchronization-interval=1min')
+ cnx.commit()
- def test(self):
- self.assertIn('myfeed', self.repo.sources_by_uri)
- dfsource = self.repo.sources_by_uri['myfeed']
- self.assertEqual(dfsource.latest_retrieval, None)
- self.assertEqual(dfsource.synchro_interval, timedelta(seconds=60))
- self.assertFalse(dfsource.fresh())
-
-
+ @contextmanager
+ def base_parser(self, session):
class AParser(datafeed.DataFeedParser):
__regid__ = 'testparser'
def process(self, url, raise_on_error=False):
@@ -50,7 +47,24 @@
entity.cw_edited.update(sourceparams['item'])
with self.temporary_appobjects(AParser):
- with self.repo.internal_cnx() as cnx:
+ if 'myfeed' in self.repo.sources_by_uri:
+ yield self.repo.sources_by_uri['myfeed']._get_parser(session)
+ else:
+ yield
+
+ def test(self):
+ self.assertIn('myfeed', self.repo.sources_by_uri)
+ dfsource = self.repo.sources_by_uri['myfeed']
+ self.assertNotIn('use_cwuri_as_url', dfsource.__dict__)
+ self.assertEqual({'type': u'datafeed', 'uri': u'myfeed', 'use-cwuri-as-url': True},
+ dfsource.public_config)
+ self.assertEqual(dfsource.use_cwuri_as_url, True)
+ self.assertEqual(dfsource.latest_retrieval, None)
+ self.assertEqual(dfsource.synchro_interval, timedelta(seconds=60))
+ self.assertFalse(dfsource.fresh())
+
+ with self.repo.internal_cnx() as cnx:
+ with self.base_parser(cnx):
stats = dfsource.pull_data(cnx, force=True)
cnx.commit()
# test import stats
@@ -119,6 +133,28 @@
self.assertFalse(cnx.execute('Card X WHERE X title "cubicweb.org"'))
self.assertFalse(cnx.execute('Any X WHERE X has_text "cubicweb.org"'))
+ def test_parser_retrieve_url_local(self):
+ with self.admin_access.repo_cnx() as cnx:
+ with self.base_parser(cnx) as parser:
+ value = parser.retrieve_url('a string')
+ self.assertEqual(200, value.getcode())
+ self.assertEqual('a string', value.geturl())
+ self.assertIsInstance(value.info(), mimetools.Message)
+
+
+class DataFeedConfigTC(CubicWebTC):
+
+ def test_use_cwuri_as_url_override(self):
+ with self.admin_access.client_cnx() as cnx:
+ cnx.create_entity('CWSource', name=u'myfeed', type=u'datafeed',
+ parser=u'testparser', url=u'ignored',
+ config=u'use-cwuri-as-url=no')
+ cnx.commit()
+ dfsource = self.repo.sources_by_uri['myfeed']
+ self.assertEqual(dfsource.use_cwuri_as_url, False)
+ self.assertEqual({'type': u'datafeed', 'uri': u'myfeed', 'use-cwuri-as-url': False},
+ dfsource.public_config)
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()
--- a/sobjects/notification.py Fri Jul 18 16:44:44 2014 +0200
+++ b/sobjects/notification.py Fri Jul 18 17:35:25 2014 +0200
@@ -80,15 +80,8 @@
# this is usually the method to call
def render_and_send(self, **kwargs):
- """generate and send an email message for this view"""
- delayed = kwargs.pop('delay_to_commit', None)
- for recipients, msg in self.render_emails(**kwargs):
- if delayed is None:
- self.send(recipients, msg)
- elif delayed:
- self.send_on_commit(recipients, msg)
- else:
- self.send_now(recipients, msg)
+ """generate and send email messages for this view"""
+ self._cw.vreg.config.sendmails(self.render_emails(**kwargs))
def cell_call(self, row, col=0, **kwargs):
self.w(self._cw._(self.content) % self.context(**kwargs))
@@ -146,16 +139,11 @@
continue
msg = format_mail(self.user_data, [emailaddr], content, subject,
config=self._cw.vreg.config, msgid=msgid, references=refs)
- yield [emailaddr], msg
+ yield msg, [emailaddr]
finally:
- # ensure we have a cnxset since commit will fail if there is
- # some operation but no cnxset. This may occurs in this very
- # specific case (eg SendMailOp)
- with cnx.ensure_cnx_set:
- cnx.commit()
self._cw = req
- # recipients / email sending ###############################################
+ # recipients handling ######################################################
def recipients(self):
"""return a list of either 2-uple (email, language) or user entity to
@@ -166,13 +154,6 @@
row=self.cw_row or 0, col=self.cw_col or 0)
return finder.recipients()
- def send_now(self, recipients, msg):
- self._cw.vreg.config.sendmails([(msg, recipients)])
-
- def send_on_commit(self, recipients, msg):
- SendMailOp(self._cw, recipients=recipients, msg=msg)
- send = send_on_commit
-
# email generation helpers #################################################
def construct_message_id(self, eid):
--- a/test/unittest_dbapi.py Fri Jul 18 16:44:44 2014 +0200
+++ b/test/unittest_dbapi.py Fri Jul 18 17:35:25 2014 +0200
@@ -78,7 +78,7 @@
with tempattr(cnx.vreg, 'config', config):
cnx.use_web_compatible_requests('http://perdu.com')
req = cnx.request()
- self.assertEqual(req.base_url(), 'http://perdu.com')
+ self.assertEqual(req.base_url(), 'http://perdu.com/')
self.assertEqual(req.from_controller(), 'view')
self.assertEqual(req.relative_path(), '')
req.ajax_replace_url('domid') # don't crash
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/unittest_toolsutils.py Fri Jul 18 17:35:25 2014 +0200
@@ -0,0 +1,57 @@
+# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+
+
+from logilab.common.testlib import TestCase, unittest_main
+
+from cubicweb.toolsutils import RQLExecuteMatcher
+
+
+class RQLExecuteMatcherTests(TestCase):
+ def matched_query(self, text):
+ match = RQLExecuteMatcher.match(text)
+ if match is None:
+ return None
+ return match['rql_query']
+
+ def test_unknown_function_dont_match(self):
+ self.assertIsNone(self.matched_query('foo'))
+ self.assertIsNone(self.matched_query('rql('))
+ self.assertIsNone(self.matched_query('hell("")'))
+ self.assertIsNone(self.matched_query('eval("rql(\'bla\''))
+
+ def test_rql_other_parameters_dont_match(self):
+ self.assertIsNone(self.matched_query('rql("Any X WHERE X eid %(x)s")'))
+ self.assertIsNone(self.matched_query('rql("Any X WHERE X eid %(x)s", {'))
+ self.assertIsNone(self.matched_query('session.execute("Any X WHERE X eid %(x)s")'))
+ self.assertIsNone(self.matched_query('session.execute("Any X WHERE X eid %(x)s", {'))
+
+ def test_rql_function_match(self):
+ for func_expr in ('rql', 'session.execute'):
+ query = self.matched_query('%s("Any X WHERE X is ' % func_expr)
+ self.assertEqual(query, 'Any X WHERE X is ')
+
+ def test_offseted_rql_function_match(self):
+ """check indentation is allowed"""
+ for func_expr in (' rql', ' session.execute'):
+ query = self.matched_query('%s("Any X WHERE X is ' % func_expr)
+ self.assertEqual(query, 'Any X WHERE X is ')
+
+
+if __name__ == '__main__':
+ unittest_main()
--- a/toolsutils.py Fri Jul 18 16:44:44 2014 +0200
+++ b/toolsutils.py Fri Jul 18 17:35:25 2014 +0200
@@ -25,7 +25,12 @@
import subprocess
from os import listdir, makedirs, environ, chmod, walk, remove
from os.path import exists, join, abspath, normpath
-
+import re
+from rlcompleter import Completer
+try:
+ import readline
+except ImportError: # readline not available, no completion
+ pass
try:
from os import symlink
except ImportError:
@@ -33,7 +38,6 @@
raise NotImplementedError
from logilab.common.clcommands import Command as BaseCommand
-from logilab.common.compat import any
from logilab.common.shellutils import ASK
from cubicweb import warning # pylint: disable=E0611
@@ -264,3 +268,155 @@
password = getpass('password: ')
return connect(login=user, password=password, host=optconfig.host, database=appid)
+
+## cwshell helpers #############################################################
+
+class AbstractMatcher(object):
+ """Abstract class for CWShellCompleter's matchers.
+
+ A matcher should implement a ``possible_matches`` method. This
+ method has to return the list of possible completions for user's input.
+ Because of the python / readline interaction, each completion should
+ be a superset of the user's input.
+
+ NOTE: readline tokenizes user's input and only passes last token to
+ completers.
+ """
+
+ def possible_matches(self, text):
+ """return possible completions for user's input.
+
+ Parameters:
+ text: the user's input
+
+ Return:
+ a list of completions. Each completion includes the original input.
+ """
+ raise NotImplementedError()
+
+
+class RQLExecuteMatcher(AbstractMatcher):
+ """Custom matcher for rql queries.
+
+ If user's input starts with ``rql(`` or ``session.execute(`` and
+ the corresponding rql query is incomplete, suggest some valid completions.
+ """
+ query_match_rgx = re.compile(
+ r'(?P<func_prefix>\s*(?:rql)' # match rql, possibly indented
+ r'|' # or
+ r'\s*(?:\w+\.execute))' # match .execute, possibly indented
+ # end of <func_prefix>
+ r'\(' # followed by a parenthesis
+ r'(?P<quote_delim>["\'])' # a quote or double quote
+ r'(?P<parameters>.*)') # and some content
+
+ def __init__(self, local_ctx, req):
+ self.local_ctx = local_ctx
+ self.req = req
+ self.schema = req.vreg.schema
+ self.rsb = req.vreg['components'].select('rql.suggestions', req)
+
+ @staticmethod
+ def match(text):
+ """check if ``text`` looks like a call to ``rql`` or ``session.execute``
+
+ Parameters:
+ text: the user's input
+
+ Returns:
+ None if it doesn't match, the query structure otherwise.
+ """
+ query_match = RQLExecuteMatcher.query_match_rgx.match(text)
+ if query_match is None:
+ return None
+ parameters_text = query_match.group('parameters')
+ quote_delim = query_match.group('quote_delim')
+ # first parameter is fully specified, no completion needed
+ if re.match(r"(.*?)%s" % quote_delim, parameters_text) is not None:
+ return None
+ func_prefix = query_match.group('func_prefix')
+ return {
+ # user's input
+ 'text': text,
+ # rql( or session.execute(
+ 'func_prefix': func_prefix,
+ # offset of rql query
+ 'rql_offset': len(func_prefix) + 2,
+ # incomplete rql query
+ 'rql_query': parameters_text,
+ }
+
+ def possible_matches(self, text):
+ """call ``rql.suggestions`` component to complete user's input.
+ """
+ # readline will only send last token, but we need the entire user's input
+ user_input = readline.get_line_buffer()
+ query_struct = self.match(user_input)
+ if query_struct is None:
+ return []
+ else:
+ # we must only send completions of the last token => compute where it
+ # starts relatively to the rql query itself.
+ completion_offset = readline.get_begidx() - query_struct['rql_offset']
+ rql_query = query_struct['rql_query']
+ return [suggestion[completion_offset:]
+ for suggestion in self.rsb.build_suggestions(rql_query)]
+
+
+class DefaultMatcher(AbstractMatcher):
+ """Default matcher: delegate to standard's `rlcompleter.Completer`` class
+ """
+ def __init__(self, local_ctx):
+ self.completer = Completer(local_ctx)
+
+ def possible_matches(self, text):
+ if "." in text:
+ return self.completer.attr_matches(text)
+ else:
+ return self.completer.global_matches(text)
+
+
+class CWShellCompleter(object):
+ """Custom auto-completion helper for cubicweb-ctl shell.
+
+ ``CWShellCompleter`` provides a ``complete`` method suitable for
+ ``readline.set_completer``.
+
+ Attributes:
+ matchers: the list of ``AbstractMatcher`` instances that will suggest
+ possible completions
+
+ The completion process is the following:
+
+ - readline calls the ``complete`` method with user's input,
+ - the ``complete`` method asks for each known matchers if
+ it can suggest completions for user's input.
+ """
+
+ def __init__(self, local_ctx):
+ # list of matchers to ask for possible matches on completion
+ self.matchers = [DefaultMatcher(local_ctx)]
+ self.matchers.insert(0, RQLExecuteMatcher(local_ctx, local_ctx['session']))
+
+ def complete(self, text, state):
+ """readline's completer method
+
+ cf http://docs.python.org/2/library/readline.html#readline.set_completer
+ for more details.
+
+ Implementation inspired by `rlcompleter.Completer`
+ """
+ if state == 0:
+ # reset self.matches
+ self.matches = []
+ for matcher in self.matchers:
+ matches = matcher.possible_matches(text)
+ if matches:
+ self.matches = matches
+ break
+ else:
+ return None # no matcher able to handle `text`
+ try:
+ return self.matches[state]
+ except IndexError:
+ return None
--- a/view.py Fri Jul 18 16:44:44 2014 +0200
+++ b/view.py Fri Jul 18 17:35:25 2014 +0200
@@ -501,28 +501,6 @@
class ReloadableMixIn(object):
"""simple mixin for reloadable parts of UI"""
- def user_callback(self, cb, args, msg=None, nonify=False):
- """register the given user callback and return a URL to call it ready to be
- inserted in html
- """
- self._cw.add_js('cubicweb.ajax.js')
- if nonify:
- _cb = cb
- def cb(*args):
- _cb(*args)
- cbname = self._cw.register_onetime_callback(cb, *args)
- return self.build_js(cbname, xml_escape(msg or ''))
-
- def build_update_js_call(self, cbname, msg):
- rql = self.cw_rset.printable_rql()
- return "javascript: %s" % js.userCallbackThenUpdateUI(
- cbname, self.__regid__, rql, msg, self.__registry__, self.domid)
-
- def build_reload_js_call(self, cbname, msg):
- return "javascript: %s" % js.userCallbackThenReloadPage(cbname, msg)
-
- build_js = build_update_js_call # expect updatable component by default
-
@property
def domid(self):
return domid(self.__regid__)
--- a/web/application.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/application.py Fri Jul 18 17:35:25 2014 +0200
@@ -23,6 +23,7 @@
from time import clock, time
from contextlib import contextmanager
from warnings import warn
+import json
import httplib
@@ -589,8 +590,10 @@
status = httplib.INTERNAL_SERVER_ERROR
if isinstance(ex, PublishException) and ex.status is not None:
status = ex.status
- req.status_out = status
- json_dumper = getattr(ex, 'dumps', lambda : unicode(ex))
+ 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()
# special case handling
--- a/web/data/cubicweb.ajax.js Fri Jul 18 16:44:44 2014 +0200
+++ b/web/data/cubicweb.ajax.js Fri Jul 18 17:35:25 2014 +0200
@@ -88,8 +88,8 @@
});
var AJAX_PREFIX_URL = 'ajax';
-var JSON_BASE_URL = baseuri() + 'json?';
-var AJAX_BASE_URL = baseuri() + AJAX_PREFIX_URL + '?';
+var JSON_BASE_URL = BASE_URL + 'json?';
+var AJAX_BASE_URL = BASE_URL + AJAX_PREFIX_URL + '?';
jQuery.extend(cw.ajax, {
@@ -122,9 +122,7 @@
* (e.g. http://..../data??resource1.js,resource2.js)
*/
_modconcatLikeUrl: function(url) {
- var base = baseuri();
- if (!base.endswith('/')) { base += '/'; }
- var modconcat_rgx = new RegExp('(' + base + 'data/([a-z0-9]+/)?)\\?\\?(.+)');
+ var modconcat_rgx = new RegExp('(' + BASE_URL + 'data/([a-z0-9]+/)?)\\?\\?(.+)');
return modconcat_rgx.exec(url);
},
@@ -379,8 +377,8 @@
* dictionary, `reqtype` the HTTP request type (get 'GET' or 'POST').
*/
function loadRemote(url, form, reqtype, sync) {
- if (!url.toLowerCase().startswith(baseuri().toLowerCase())) {
- url = baseuri() + url;
+ if (!url.toLowerCase().startswith(BASE_URL.toLowerCase())) {
+ url = BASE_URL + url;
}
if (!sync) {
var deferred = new Deferred();
@@ -601,7 +599,7 @@
var fck = new FCKeditor(this.id);
fck.Config['CustomConfigurationsPath'] = fckconfigpath;
fck.Config['DefaultLanguage'] = fcklang;
- fck.BasePath = baseuri() + "fckeditor/";
+ fck.BasePath = BASE_URL + "fckeditor/";
fck.ReplaceTextarea();
} else {
cw.log('fckeditor could not be found.');
--- a/web/data/cubicweb.edition.js Fri Jul 18 16:44:44 2014 +0200
+++ b/web/data/cubicweb.edition.js Fri Jul 18 17:35:25 2014 +0200
@@ -67,7 +67,7 @@
rql: rql_for_eid(eid),
'__notemplate': 1
};
- var d = jQuery('#unrelatedDivs_' + eid).loadxhtml(baseuri() + 'view', args, 'post', 'append');
+ var d = jQuery('#unrelatedDivs_' + eid).loadxhtml(BASE_URL + 'view', args, 'post', 'append');
d.addCallback(function() {
_showMatchingSelect(eid, jQuery('#' + divId));
});
--- a/web/data/cubicweb.facets.js Fri Jul 18 16:44:44 2014 +0200
+++ b/web/data/cubicweb.facets.js Fri Jul 18 17:35:25 2014 +0200
@@ -69,7 +69,7 @@
}
var $focusLink = $('#focusLink');
if ($focusLink.length) {
- var url = baseuri()+ 'view?rql=' + encodeURIComponent(rql);
+ var url = BASE_URL + 'view?rql=' + encodeURIComponent(rql);
if (vid) {
url += '&vid=' + encodeURIComponent(vid);
}
--- a/web/data/cubicweb.htmlhelpers.js Fri Jul 18 16:44:44 2014 +0200
+++ b/web/data/cubicweb.htmlhelpers.js Fri Jul 18 17:35:25 2014 +0200
@@ -12,20 +12,13 @@
/**
* .. function:: baseuri()
*
- * returns the document's baseURI. (baseuri() uses document.baseURI if
- * available and inspects the <base> tag manually otherwise.)
+ * returns the document's baseURI.
*/
-function baseuri() {
- if (typeof BASE_URL === 'undefined') {
- // backward compatibility, BASE_URL might be undefined
- var uri = document.baseURI;
- if (uri) { // some browsers don't define baseURI
- return uri.toLowerCase();
- }
- return jQuery('base').attr('href').toLowerCase();
- }
- return BASE_URL;
-}
+baseuri = cw.utils.deprecatedFunction(
+ "[3.20] baseuri() is deprecated, use BASE_URL instead",
+ function () {
+ return BASE_URL;
+ });
/**
* .. function:: setProgressCursor()
@@ -107,18 +100,6 @@
}
/**
- * .. function:: popupLoginBox()
- *
- * toggles visibility of login popup div
- */
-// XXX used exactly ONCE in basecomponents
-popupLoginBox = cw.utils.deprecatedFunction(
- function() {
- $('#popupLoginBox').toggleClass('hidden');
- jQuery('#__login:visible').focus();
-});
-
-/**
* .. function getElementsMatching(tagName, properties, \/* optional \*\/ parent)
*
* returns the list of elements in the document matching the tag name
--- a/web/data/cubicweb.js Fri Jul 18 16:44:44 2014 +0200
+++ b/web/data/cubicweb.js Fri Jul 18 17:35:25 2014 +0200
@@ -208,91 +208,40 @@
},
/**
- * .. function:: formContents(elem \/* = document.body *\/)
+ * .. function:: formContents(elem)
*
- * this implementation comes from MochiKit
+ * cannot use jQuery.serializeArray() directly because of FCKeditor
*/
- formContents: function (elem /* = document.body */ ) {
- var names = [];
- var values = [];
- if (typeof(elem) == "undefined" || elem === null) {
- elem = document.body;
- } else {
- elem = cw.getNode(elem);
- }
- cw.utils.nodeWalkDepthFirst(elem, function (elem) {
- var name = elem.name;
- if (name && name.length) {
- if (elem.disabled) {
- return null;
- }
- var tagName = elem.tagName.toUpperCase();
- if (tagName === "INPUT" && (elem.type == "radio" || elem.type == "checkbox") && !elem.checked) {
- return null;
- }
- if (tagName === "SELECT") {
- if (elem.type == "select-one") {
- if (elem.selectedIndex >= 0) {
- var opt = elem.options[elem.selectedIndex];
- var v = opt.value;
- if (!v) {
- var h = opt.outerHTML;
- // internet explorer sure does suck.
- if (h && !h.match(/^[^>]+\svalue\s*=/i)) {
- v = opt.text;
- }
- }
- names.push(name);
- values.push(v);
+ formContents: function (elem) {
+ var $elem, array, names, values;
+ $elem = cw.jqNode(elem);
+ array = $elem.serializeArray();
+
+ if (typeof FCKeditor !== 'undefined') {
+ $elem.find('textarea').each(function (idx, textarea) {
+ var fck = FCKeditorAPI.GetInstance(textarea.id);
+ if (fck) {
+ array = jQuery.map(array, function (dict) {
+ if (dict.name === textarea.name) {
+ // filter out the textarea's - likely empty - value ...
return null;
}
- // no form elements?
- names.push(name);
- values.push("");
- return null;
- } else {
- var opts = elem.options;
- if (!opts.length) {
- names.push(name);
- values.push("");
- return null;
- }
- for (var i = 0; i < opts.length; i++) {
- var opt = opts[i];
- if (!opt.selected) {
- continue;
- }
- var v = opt.value;
- if (!v) {
- var h = opt.outerHTML;
- // internet explorer sure does suck.
- if (h && !h.match(/^[^>]+\svalue\s*=/i)) {
- v = opt.text;
- }
- }
- names.push(name);
- values.push(v);
- }
- return null;
- }
+ return dict;
+ });
+ // ... so we can put the HTML coming from FCKeditor instead.
+ array.push({
+ name: textarea.name,
+ value: fck.GetHTML()
+ });
}
- if (tagName === "FORM" || tagName === "P" || tagName === "SPAN" || tagName === "DIV") {
- return elem.childNodes;
- }
- var value = elem.value;
- if (tagName === "TEXTAREA") {
- if (typeof(FCKeditor) != 'undefined') {
- var fck = FCKeditorAPI.GetInstance(elem.id);
- if (fck) {
- value = fck.GetHTML();
- }
- }
- }
- names.push(name);
- values.push(value || '');
- return null;
- }
- return elem.childNodes;
+ });
+ }
+
+ names = [];
+ values = [];
+ jQuery.each(array, function (idx, dict) {
+ names.push(dict.name);
+ values.push(dict.value);
});
return [names, values];
},
--- a/web/data/cubicweb.timeline-bundle.js Fri Jul 18 16:44:44 2014 +0200
+++ b/web/data/cubicweb.timeline-bundle.js Fri Jul 18 17:35:25 2014 +0200
@@ -3,8 +3,8 @@
* :organization: Logilab
*/
-var SimileAjax_urlPrefix = baseuri() + 'data/';
-var Timeline_urlPrefix = baseuri() + 'data/';
+var SimileAjax_urlPrefix = BASE_URL + 'data/';
+var Timeline_urlPrefix = BASE_URL + 'data/';
/*
* Simile Ajax API
--- a/web/facet.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/facet.py Fri Jul 18 17:35:25 2014 +0200
@@ -61,7 +61,6 @@
from logilab.common.graph import has_path
from logilab.common.decorators import cached, cachedproperty
from logilab.common.date import datetime2ticks, ustrftime, ticks2datetime
-from logilab.common.compat import all
from logilab.common.deprecation import deprecated
from logilab.common.registry import yes
--- a/web/formfields.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/formfields.py Fri Jul 18 17:35:25 2014 +0200
@@ -529,6 +529,7 @@
"""
widget = fw.TextArea
size = 45
+ placeholder = None
def __init__(self, name=None, max_length=None, **kwargs):
self.max_length = max_length # must be set before super call
@@ -547,6 +548,9 @@
elif isinstance(self.widget, fw.TextInput):
self.init_text_input(self.widget)
+ if self.placeholder:
+ self.widget.attrs.setdefault('placeholder', self.placeholder)
+
def init_text_input(self, widget):
if self.max_length:
widget.attrs.setdefault('size', min(self.size, self.max_length))
@@ -557,6 +561,11 @@
widget.attrs.setdefault('cols', 60)
widget.attrs.setdefault('rows', 5)
+ def set_placeholder(self, placeholder):
+ self.placeholder = placeholder
+ if self.widget and self.placeholder:
+ self.widget.attrs.setdefault('placeholder', self.placeholder)
+
class PasswordField(StringField):
"""Use this field to edit password (`Password` yams type, encoded python
--- a/web/formwidgets.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/formwidgets.py Fri Jul 18 17:35:25 2014 +0200
@@ -210,6 +210,8 @@
attrs['id'] = field.dom_id(form, self.suffix)
if self.settabindex and not 'tabindex' in attrs:
attrs['tabindex'] = form._cw.next_tabindex()
+ if 'placeholder' in attrs:
+ attrs['placeholder'] = form._cw._(attrs['placeholder'])
return attrs
def values(self, form, field):
--- a/web/http_headers.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/http_headers.py Fri Jul 18 17:35:25 2014 +0200
@@ -1324,6 +1324,9 @@
h = self._headers.get(name, None)
r = self.handler.generate(name, h)
if r is not None:
+ assert isinstance(r, list)
+ for v in r:
+ assert isinstance(v, str)
self._raw_headers[name] = r
return r
@@ -1362,6 +1365,9 @@
Value should be a list of strings, each being one header of the
given name.
"""
+ assert isinstance(value, list)
+ for v in value:
+ assert isinstance(v, str)
name = name.lower()
self._raw_headers[name] = value
self._headers[name] = _RecalcNeeded
--- a/web/request.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/request.py Fri Jul 18 17:35:25 2014 +0200
@@ -179,7 +179,7 @@
self.ajax_request = value
json_request = property(_get_json_request, _set_json_request)
- def base_url(self, secure=None):
+ def _base_url(self, secure=None):
"""return the root url of the instance
secure = False -> base-url
@@ -192,7 +192,7 @@
if secure:
base_url = self.vreg.config.get('https-url')
if base_url is None:
- base_url = super(_CubicWebRequestBase, self).base_url()
+ base_url = super(_CubicWebRequestBase, self)._base_url()
return base_url
@property
@@ -786,10 +786,6 @@
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')
- if self.http_method() == 'HEAD':
- self.status_out = 200
- # XXX replace by True once validate_cache bw compat method is dropped
- return 200
# /!\ no raise, the function returns and we keep processing the request
else:
# overwrite headers_out to forge a brand new not-modified response
--- a/web/test/unittest_form.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/test/unittest_form.py Fri Jul 18 17:35:25 2014 +0200
@@ -19,7 +19,6 @@
from xml.etree.ElementTree import fromstring
from logilab.common.testlib import unittest_main, mock_object
-from logilab.common.compat import any
from cubicweb import Binary, ValidationError
from cubicweb.devtools.testlib import CubicWebTC
--- a/web/test/unittest_http.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/test/unittest_http.py Fri Jul 18 17:35:25 2014 +0200
@@ -227,7 +227,7 @@
hout = [('etag', 'rhino/really-not-babar'),
]
req = _test_cache(hin, hout, method='HEAD')
- self.assertCache(200, req.status_out, 'modifier HEAD verb')
+ self.assertCache(None, req.status_out, 'modifier HEAD verb')
# not modified
hin = [('if-none-match', 'babar'),
]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/test/unittest_views_forms.py Fri Jul 18 17:35:25 2014 +0200
@@ -0,0 +1,36 @@
+# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+
+from cubicweb.devtools.testlib import CubicWebTC
+
+class InlinedFormTC(CubicWebTC):
+
+ def test_linked_to(self):
+ req = self.request()
+ formview = req.vreg['views'].select(
+ 'inline-creation', req,
+ etype='File', rtype='described_by_test', role='subject',
+ peid=123,
+ petype='Salesterm')
+ self.assertEqual({('described_by_test', 'object'): [123]},
+ formview.form.linked_to)
+
+if __name__ == '__main__':
+ from logilab.common.testlib import unittest_main
+ unittest_main()
+
--- a/web/test/unittest_web.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/test/unittest_web.py Fri Jul 18 17:35:25 2014 +0200
@@ -94,6 +94,7 @@
self.assertEqual(webreq.status_code, 200)
self.assertDictEqual(expect, loads(webreq.content))
+
class LanguageTC(CubicWebServerTC):
def test_language_neg(self):
@@ -104,6 +105,19 @@
webreq = self.web_request(headers=headers)
self.assertIn('lang="en"', webreq.read())
+ def test_response_codes(self):
+ with self.admin_access.client_cnx() as cnx:
+ admin_eid = cnx.user.eid
+ # guest can't see admin
+ webreq = self.web_request('/%d' % admin_eid)
+ self.assertEqual(webreq.status, 403)
+
+ # but admin can
+ self.web_login()
+ webreq = self.web_request('/%d' % admin_eid)
+ self.assertEqual(webreq.status, 200)
+
+
class LogQueriesTC(CubicWebServerTC):
@classmethod
def init_config(cls, config):
--- a/web/views/ajaxedit.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/views/ajaxedit.py Fri Jul 18 17:35:25 2014 +0200
@@ -36,8 +36,6 @@
cw_property_defs = {} # don't want to inherit this from Box
expected_kwargs = form_params = ('rtype', 'target')
- build_js = component.EditRelationMixIn.build_reload_js_call
-
def cell_call(self, row, col, rtype=None, target=None, etype=None):
self.rtype = rtype or self._cw.form['rtype']
self.target = target or self._cw.form['target']
--- a/web/views/forms.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/views/forms.py Fri Jul 18 17:35:25 2014 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -41,17 +41,17 @@
but you'll use this one rarely.
"""
+
__docformat__ = "restructuredtext en"
from warnings import warn
from logilab.common import dictattr, tempattr
from logilab.common.decorators import iclassmethod, cached
-from logilab.common.compat import any
from logilab.common.textutils import splitstrip
from logilab.common.deprecation import deprecated
-from cubicweb import ValidationError
+from cubicweb import ValidationError, neg_role
from cubicweb.utils import support_args
from cubicweb.predicates import non_final_entity, match_kwargs, one_line_rset
from cubicweb.web import RequestError, ProcessFormError
@@ -400,12 +400,21 @@
@property
@cached
def linked_to(self):
- # if current form is not the main form, exit immediately
+ linked_to = {}
+ # case where this is an embeded creation form
+ try:
+ eid = int(self.cw_extra_kwargs['peid'])
+ except KeyError:
+ pass
+ else:
+ ltrtype = self.cw_extra_kwargs['rtype']
+ ltrole = neg_role(self.cw_extra_kwargs['role'])
+ linked_to[(ltrtype, ltrole)] = [eid]
+ # now consider __linkto if the current form is the main form
try:
self.field_by_name('__maineid')
except form.FieldNotFound:
- return {}
- linked_to = {}
+ return linked_to
for linkto in self._cw.list_form_param('__linkto'):
ltrtype, eid, ltrole = linkto.split(':')
linked_to.setdefault((ltrtype, ltrole), []).append(int(eid))
--- a/web/views/uicfg.py Fri Jul 18 16:44:44 2014 +0200
+++ b/web/views/uicfg.py Fri Jul 18 17:35:25 2014 +0200
@@ -57,8 +57,6 @@
from warnings import warn
-from logilab.common.compat import any
-
from cubicweb import neg_role
from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
RelationTagsDict, NoTargetRelationTagsDict,
--- a/wsgi/request.py Fri Jul 18 16:44:44 2014 +0200
+++ b/wsgi/request.py Fri Jul 18 17:35:25 2014 +0200
@@ -59,7 +59,7 @@
headers_in = dict((normalize_header(k[5:]), v) for k, v in self.environ.items()
if k.startswith('HTTP_'))
- https = environ.get("HTTPS") in ('yes', 'on', '1')
+ https = self.is_secure()
post, files = self.get_posted_data()
super(CubicWebWsgiRequest, self).__init__(vreg, https, post,
@@ -104,32 +104,8 @@
## wsgi request helpers ###################################################
- def instance_uri(self):
- """Return the instance's base URI (no PATH_INFO or QUERY_STRING)
-
- see python2.5's wsgiref.util.instance_uri code
- """
- environ = self.environ
- url = environ['wsgi.url_scheme'] + '://'
- if environ.get('HTTP_HOST'):
- url += environ['HTTP_HOST']
- else:
- url += environ['SERVER_NAME']
- if environ['wsgi.url_scheme'] == 'https':
- if environ['SERVER_PORT'] != '443':
- url += ':' + environ['SERVER_PORT']
- else:
- if environ['SERVER_PORT'] != '80':
- url += ':' + environ['SERVER_PORT']
- url += quote(environ.get('SCRIPT_NAME') or '/')
- return url
-
- def get_full_path(self):
- return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '')
-
def is_secure(self):
- return 'wsgi.url_scheme' in self.environ \
- and self.environ['wsgi.url_scheme'] == 'https'
+ return self.environ['wsgi.url_scheme'] == 'https'
def get_posted_data(self):
# The WSGI spec says 'QUERY_STRING' may be absent.