diff --git a/tests/test_workflows.py b/tests/test_workflows.py
index 456d209ef..de9391d6c 100644
--- a/tests/test_workflows.py
+++ b/tests/test_workflows.py
@@ -1361,7 +1361,7 @@ def test_sms_with_passerelle(pub):
item.body = 'my message'
with mock.patch('wcs.wscalls.get_secret_and_orig') as mocked_secret_and_orig:
mocked_secret_and_orig.return_value = ('secret', 'localhost')
- with mock.patch('wcs.wscalls.http_post_request') as mocked_http_post:
+ with mock.patch('qommon.misc.http_post_request') as mocked_http_post:
mocked_http_post.return_value = ('response', '200', 'data', 'headers')
item.perform(formdata)
url, payload = mocked_http_post.call_args[0]
diff --git a/tests/utilities.py b/tests/utilities.py
index 8ab5ffb19..488d8a138 100644
--- a/tests/utilities.py
+++ b/tests/utilities.py
@@ -7,6 +7,7 @@ import random
import psycopg2
import pytest
import shutil
+import sys
import threading
import urlparse
@@ -22,6 +23,7 @@ from wcs import publisher, compat
from wcs.qommon.http_request import HTTPRequest
from wcs.users import User
from wcs.tracking_code import TrackingCode
+import wcs.qommon.emails
import wcs.qommon.sms
import qommon.sms
from qommon.errors import ConnectionError
@@ -195,21 +197,19 @@ class EmailsMocking(object):
return len(self.emails)
def __enter__(self):
- import wcs.qommon.emails
- import qommon.emails
- self.wcs_create_smtp_server = wcs.qommon.emails.create_smtp_server
- self.qommon_create_smtp_server = qommon.emails.create_smtp_server
+ self.wcs_create_smtp_server = sys.modules['wcs.qommon.emails'].create_smtp_server
+ self.qommon_create_smtp_server = sys.modules['qommon.emails'].create_smtp_server
- wcs.qommon.emails.create_smtp_server = self.create_smtp_server
- qommon.emails.create_smtp_server = self.create_smtp_server
+ sys.modules['wcs.qommon.emails'].create_smtp_server = self.create_smtp_server
+ sys.modules['qommon.emails'].create_smtp_server = self.create_smtp_server
self.emails = {}
return self
def __exit__(self, exc_type, exc_value, tb):
del self.emails
- wcs.qommon.emails.create_smtp_server = self.wcs_create_smtp_server
- qommon.emails.create_smtp_server = self.qommon_create_smtp_server
+ sys.modules['wcs.qommon.emails'].create_smtp_server = self.wcs_create_smtp_server
+ sys.modules['qommon.emails'].create_smtp_server = self.qommon_create_smtp_server
class MockSubstitutionVariables(object):
diff --git a/wcs/__init__.py b/wcs/__init__.py
index 2ae0be35a..a9923b73c 100644
--- a/wcs/__init__.py
+++ b/wcs/__init__.py
@@ -18,7 +18,10 @@ import sys
import os
sys.path.insert(0, os.path.dirname(__file__))
+import monkeypatch
+
import qommon
+sys.modules['qommon'] = sys.modules['wcs.qommon']
import qommon.form
sys.modules['form'] = qommon.form
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 71dff5627..564cad1ff 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -41,11 +41,11 @@ from qommon.evalutils import make_datetime
from qommon.misc import C_, ellipsize
from qommon.afterjobs import AfterJob
from qommon import emails
+import qommon.sms
from qommon import errors
from qommon import ezt
from qommon import ods
from qommon.form import *
-from qommon.sms import SMS
from qommon.storage import (Equal, NotEqual, LessOrEqual, GreaterOrEqual, Or,
Intersects, ILike, FtsMatch, Contains, Null)
@@ -131,7 +131,7 @@ class SendCodeFormdefDirectory(Directory):
if get_publisher().use_sms_feature:
sms_cfg = get_cfg('sms', {})
mode = sms_cfg.get('mode', 'none')
- sms_class = SMS.get_sms_class(mode)
+ sms_class = qommon.sms.SMS.get_sms_class(mode)
if sms_class:
form.add(StringWidget, 'sms', title=_('SMS Number'), required=False)
form.add(RadiobuttonsWidget, 'method',
diff --git a/wcs/compat.py b/wcs/compat.py
index 47d541055..e8af60f81 100644
--- a/wcs/compat.py
+++ b/wcs/compat.py
@@ -36,21 +36,6 @@ from .publisher import WcsPublisher
from .qommon.http_request import HTTPRequest
from .qommon.http_response import HTTPResponse
-def init_publisher_if_needed():
- if get_publisher() is not None:
- return get_publisher()
- # initialize publisher in first request
- config = ConfigParser.ConfigParser()
- if settings.WCS_LEGACY_CONFIG_FILE:
- config.read(settings.WCS_LEGACY_CONFIG_FILE)
- if hasattr(settings, 'WCS_EXTRA_MODULES') and settings.WCS_EXTRA_MODULES:
- if not config.has_section('extra'):
- config.add_section('extra')
- for i, extra in enumerate(settings.WCS_EXTRA_MODULES):
- config.set('extra', 'cmd_line_extra_%d' % i, extra)
- CompatWcsPublisher.configure(config)
- return CompatWcsPublisher.create_publisher()
-
class TemplateWithFallbackView(TemplateView):
quixote_response = None
@@ -192,7 +177,8 @@ class CompatWcsPublisher(WcsPublisher):
quixote_lock = Lock()
def quixote(request):
- with quixote_lock:
+ #with quixote_lock:
+ if True:
pub = get_publisher()
compat_request = CompatHTTPRequest(request)
return pub.process_request(compat_request)
diff --git a/wcs/middleware.py b/wcs/middleware.py
index 11d47be1e..6c57c5398 100644
--- a/wcs/middleware.py
+++ b/wcs/middleware.py
@@ -14,12 +14,19 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see .
-from .compat import init_publisher_if_needed, CompatHTTPRequest
+import thread
+import threading
+
+from quixote import get_publisher
+from .compat import CompatHTTPRequest, CompatWcsPublisher
+
class PublisherInitialisationMiddleware(object):
'''Initializes the publisher according to the request server name.'''
def process_request(self, request):
- pub = init_publisher_if_needed()
+ pub = get_publisher()
+ if not pub:
+ pub = CompatWcsPublisher.create_publisher()
compat_request = CompatHTTPRequest(request)
pub.init_publish(compat_request)
pub._set_request(compat_request)
diff --git a/wcs/monkeypatch.py b/wcs/monkeypatch.py
new file mode 100644
index 000000000..cdde6f342
--- /dev/null
+++ b/wcs/monkeypatch.py
@@ -0,0 +1,90 @@
+# w.c.s. - web application for online forms
+# Copyright (C) 2005-2017 Entr'ouvert
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see .
+
+import threading
+import types
+import urlparse
+
+import quixote
+import quixote.publish
+
+_thread_local = threading.local()
+
+cleanup_orig = quixote.publish.cleanup
+PublisherOrig = quixote.publish.Publisher
+
+class Publisher(quixote.publish.Publisher):
+ def __init__(self, root_directory, *args, **kwargs):
+ try:
+ PublisherOrig.__init__(self, root_directory, *args, **kwargs)
+ except RuntimeError:
+ pass
+ _thread_local.publisher = self
+ self.root_directory = root_directory
+ self._request = None
+
+def get_publisher():
+ return getattr(_thread_local, 'publisher', None)
+
+def get_request():
+ return _thread_local.publisher.get_request()
+
+def get_response():
+ return _thread_local.publisher.get_request().response
+
+def get_field(name, default=None):
+ return _thread_local.publisher.get_request().get_field(name, default)
+
+def get_cookie(name, default=None):
+ return _thread_local.publisher.get_request().get_cookie(name, default)
+
+def get_path(n=0):
+ return _thread_local.publisher.get_request().get_path(n)
+
+def redirect(location, permanent=False):
+ """(location : string, permanent : boolean = false) -> string
+
+ Create a redirection response. If the location is relative, then it
+ will automatically be made absolute. The return value is an HTML
+ document indicating the new URL (useful if the client browser does
+ not honor the redirect).
+ """
+ request = _thread_local.publisher.get_request()
+ location = urlparse.urljoin(request.get_url(), str(location))
+ return request.response.redirect(location, permanent)
+
+def get_session():
+ return _thread_local.publisher.get_request().session
+
+def get_session_manager():
+ return _thread_local.publisher.session_manager
+
+def get_user():
+ session = _thread_local.publisher.get_request().session
+ if session is None:
+ return None
+ else:
+ return session.user
+
+def cleanup():
+ cleanup_orig()
+ _thread_local.publisher = None
+
+
+for key, value in locals().items():
+ if type(value) in (types.FunctionType, types.TypeType, types.ClassType):
+ setattr(quixote, key, value)
+ setattr(quixote.publish, key, value)
diff --git a/wcs/qommon/__init__.py b/wcs/qommon/__init__.py
index 3c7fc6566..ddce2fd35 100644
--- a/wcs/qommon/__init__.py
+++ b/wcs/qommon/__init__.py
@@ -14,6 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see .
+import ConfigParser
+import django.apps
+from django.conf import settings
from quixote import get_publisher
try:
@@ -33,7 +36,30 @@ def ngettext(*args):
return message
return unicode(pub.ngettext(*args), 'utf-8').encode(pub.site_charset)
-from publisher import get_cfg, get_logger
+from publisher import get_cfg, get_logger, get_publisher_class
+import publisher
+publisher._ = _
+
+
+class AppConfig(django.apps.AppConfig):
+ name = 'wcs.qommon'
+
+ def ready(self):
+ config = ConfigParser.ConfigParser()
+ if settings.WCS_LEGACY_CONFIG_FILE:
+ config.read(settings.WCS_LEGACY_CONFIG_FILE)
+ if hasattr(settings, 'WCS_EXTRA_MODULES') and settings.WCS_EXTRA_MODULES:
+ if not config.has_section('extra'):
+ config.add_section('extra')
+ for i, extra in enumerate(settings.WCS_EXTRA_MODULES):
+ config.set('extra', 'cmd_line_extra_%d' % i, extra)
+
+ get_publisher_class().configure(config)
+ get_publisher_class().register_tld_names = True
+ get_publisher_class().init_publisher_class()
+
+default_app_config = 'wcs.qommon.AppConfig'
+
if lasso:
if not hasattr(lasso, 'SAML2_SUPPORT'):
diff --git a/wcs/qommon/errors.py b/wcs/qommon/errors.py
index b2ec8a13b..7572b2a43 100644
--- a/wcs/qommon/errors.py
+++ b/wcs/qommon/errors.py
@@ -21,7 +21,6 @@ import quixote
from quixote.errors import *
from quixote.html import TemplateIO, htmltext
-from qommon import _
import template
@@ -31,6 +30,7 @@ class AccessForbiddenError(AccessError):
self.location_hint = location_hint
def render(self):
+ from qommon import _
if self.public_msg:
return template.error_page(self.public_msg, _('Access Forbidden'),
continue_to = (get_publisher().get_root_url(), _('the homepage')),
@@ -63,6 +63,7 @@ class EmailError(Exception):
class InternalServerError(object):
def render(self):
+ from qommon import _
template.html_top(_('Oops, the server borked severely'))
r = TemplateIO(html=True)
@@ -115,6 +116,7 @@ TraversalError.description = N_(
def format_publish_error(exc):
+ from qommon import _
if getattr(exc, 'public_msg', None):
return template.error_page(exc.format(), _(exc.title))
else:
diff --git a/wcs/qommon/ident/password.py b/wcs/qommon/ident/password.py
index ecf22b805..3e56b32f6 100644
--- a/wcs/qommon/ident/password.py
+++ b/wcs/qommon/ident/password.py
@@ -1187,7 +1187,7 @@ class PasswordAuthMethod(AuthMethod):
@classmethod
def register(cls):
- rdb = get_publisher().backoffice_directory_class
+ rdb = get_publisher_class().backoffice_directory_class
if rdb:
rdb.register_directory('accounts', AccountsDirectory())
diff --git a/wcs/qommon/publisher.py b/wcs/qommon/publisher.py
index e8d17702c..4356d9e1c 100644
--- a/wcs/qommon/publisher.py
+++ b/wcs/qommon/publisher.py
@@ -57,8 +57,6 @@ import storage
import strftime
import urllib
-from qommon import _
-
class ImmediateRedirectException(Exception):
def __init__(self, location):
self.location = location
@@ -802,16 +800,25 @@ class QommonPublisher(Publisher, object):
cls.register_cronjob(CronJob(cls.clean_afterjobs, minutes=[random.randint(0, 59)]))
cls.register_cronjob(CronJob(cls.clean_tempfiles, minutes=[random.randint(0, 59)]))
+ register_cron = False
+ register_tld_names = False
+
+ _initialized = False
@classmethod
- def create_publisher(cls, register_cron=True, register_tld_names=True):
+ def init_publisher_class(cls):
+ if cls._initialized:
+ return
+ cls._initialized = True
cls.load_extra_dirs()
cls.load_translations()
- if register_cron:
+ if cls.register_cron:
cls.register_cronjobs()
- if register_tld_names:
+ if cls.register_tld_names:
cls.load_effective_tld_names()
+ @classmethod
+ def create_publisher(cls, **kwargs):
publisher = cls(cls.root_directory_class(),
session_cookie_name = cls.APP_NAME,
session_cookie_path = '/',
diff --git a/wcs/qommon/substitution.py b/wcs/qommon/substitution.py
index f37b438ab..4e6c7e211 100644
--- a/wcs/qommon/substitution.py
+++ b/wcs/qommon/substitution.py
@@ -15,7 +15,6 @@
# along with this program; if not, see .
from quixote.html import htmltext, TemplateIO
-from qommon import _
class Substitutions(object):
substitutions_dict = {}
@@ -61,6 +60,7 @@ class Substitutions(object):
@classmethod
def get_substitution_html_table(cls):
+ from qommon import _
r = TemplateIO(html=True)
r += htmltext('
')
r += htmltext('%s | %s | %s |
' % (
diff --git a/wcs/qommon/template.py b/wcs/qommon/template.py
index db75a3ea9..9a07ecd90 100644
--- a/wcs/qommon/template.py
+++ b/wcs/qommon/template.py
@@ -24,7 +24,6 @@ from quixote.directory import Directory
from quixote.util import StaticDirectory, StaticFile
from quixote.html import htmltext, htmlescape, TemplateIO
-from qommon import _
import errors
import ezt
@@ -227,6 +226,7 @@ def html_top(title=None, default_org=None):
def error_page(error_message, error_title = None, exception = None, continue_to = None,
location_hint = None):
+ from qommon import _
if not error_title:
error_title = _('Error')
if exception:
diff --git a/wcs/wscalls.py b/wcs/wscalls.py
index b6cbbab51..852e88496 100644
--- a/wcs/wscalls.py
+++ b/wcs/wscalls.py
@@ -24,11 +24,11 @@ import xml.etree.ElementTree as ET
from quixote import get_publisher
from qommon import _
-from qommon.misc import (simplify, http_get_page, http_post_request,
- get_variadic_url, JSONEncoder, json_loads)
+from qommon.misc import simplify, get_variadic_url, JSONEncoder, json_loads
from qommon.xml_storage import XmlStorableObject
from qommon.form import (CompositeWidget, StringWidget, WidgetDict,
ComputedExpressionWidget, RadiobuttonsWidget, CheckboxWidget)
+import qommon.misc
from wcs.api_utils import sign_url, get_secret_and_orig, MissingSecret
from wcs.workflows import WorkflowStatusItem
@@ -106,10 +106,10 @@ def call_webservice(url, qs_data=None, request_signature_key=None,
# increase timeout for huge loads, one second every 65536
# bytes, to match a country 512kbps DSL line.
timeout += len(payload) / 65536
- response, status, data, auth_header = http_post_request(
+ response, status, data, auth_header = qommon.misc.http_post_request(
url, payload, headers=headers, timeout=timeout)
else:
- response, status, data, auth_header = http_get_page(
+ response, status, data, auth_header = qommon.misc.http_get_page(
url, headers=headers, timeout=TIMEOUT)
return (response, status, data)