diff --git a/.gitignore b/.gitignore
index 0d20b6487..b6eb99e54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
*.pyc
+local_settings.py
diff --git a/INSTALL b/INSTALL
index cb88c6b95..ef2228456 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,57 +1,4 @@
w.c.s. - Installation Instructions
==================================
-Prerequisites
--------------
-
-- Python 2.5
-- Quixote 2.5
-- Lasso 2.2 for Liberty/SAML support
-
-
-Installation
-------------
-
-w.c.s. uses the standard distutils Python package; just run::
-
- python setup.py install
-
-and you're done.
-
-
-Configuration
--------------
-
-w.c.s. has been tested with Quixote configured through SCGI with Apache and 2
-The recommended configuration is to use SCGI version 1.8 or later (as it has
-the SCGIMount directive).
-
-SCGI usage requires an additional server to run, 'wcsctl.py start', the Debian
-package installs a init.d script automatically.
-
-
- ServerAdmin webmaster@example.com
- ServerName www.example.com
- DocumentRoot /usr/share/wcs/web/
-
- SCGIMount / 127.0.0.1:3001
-
- # this part allows serving static files directly from Apache, but it
- # requires to run wcsctl.py collectstatic first.
- Alias /static/ /var/lib/wcs/collectstatic/
-
- SCGIHandler off
-
-
- # this part allows serving the themes directly from Apache, but it
- # requires mod_rewrite
- Alias /themes/ /usr/share/wcs/themes/
- RewriteEngine On
- RewriteCond /usr/share/wcs/%{REQUEST_URI} !-f
- RewriteCond /usr/share/wcs/%{REQUEST_URI} !-d
- RewriteRule ^/themes/(.*)$ /var/lib/wcs/%{HTTP_HOST}/themes/$1
-
- CustomLog /var/log/apache2/wcs-access.log combined
- ErrorLog /var/log/apache2/wcs-error.log
-
-
+We recommend using .deb packages from https://deb.entrouvert.org/
diff --git a/manage.py b/manage.py
new file mode 100755
index 000000000..a166b0433
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcs.settings")
+
+ from django.core.management import execute_from_command_line
+
+ execute_from_command_line(sys.argv)
diff --git a/tests/test_ctl.py b/tests/test_ctl.py
index ca6309b1e..8ce5785f9 100644
--- a/tests/test_ctl.py
+++ b/tests/test_ctl.py
@@ -29,7 +29,7 @@ def teardown_module(module):
def test_loading():
ctl = wcs.qommon.ctl.Ctl(cmd_prefixes=['wcs.ctl'])
ctl.load_all_commands(ignore_errors=False)
- assert 'start' in ctl.get_commands().keys()
+ assert 'export_settings' in ctl.get_commands().keys()
# call all __init__() methods
for cmd in ctl.get_commands().values():
cmd()
diff --git a/tests/test_publisher.py b/tests/test_publisher.py
index 7f39ba661..bb525b4b3 100644
--- a/tests/test_publisher.py
+++ b/tests/test_publisher.py
@@ -89,7 +89,7 @@ def test_finish_interrupted_request():
'CONTENT_LENGTH': 'aaa',
})
response = pub.process_request(req)
- assert 'invalid content-length header' in response.body
+ assert 'invalid content-length header' in str(response)
req = HTTPRequest(StringIO.StringIO(''), {
'SERVER_NAME': 'example.net',
'SCRIPT_NAME': '',
@@ -97,7 +97,7 @@ def test_finish_interrupted_request():
'CONTENT_LENGTH': '1',
})
response = pub.process_request(req)
- assert 'Invalid request: unexpected end of request body' in response.body
+ assert 'Invalid request: unexpected end of request body' in str(response)
req = HTTPRequest(StringIO.StringIO(''), {
'SERVER_NAME': 'example.net',
'SCRIPT_NAME': '',
@@ -105,14 +105,14 @@ def test_finish_interrupted_request():
'CONTENT_LENGTH': '1',
})
response = pub.process_request(req)
- assert 'Invalid request: multipart/form-data missing boundary' in response.body
+ assert 'Invalid request: multipart/form-data missing boundary' in str(response)
req = HTTPRequest(StringIO.StringIO(''), {
'SERVER_NAME': 'example.net',
'SCRIPT_NAME': '',
'PATH_INFO': '/gloubiboulga',
})
response = pub.process_request(req)
- assert '
The requested link' in response.body
+ assert '
The requested link' in str(response)
def test_get_tenants():
pub = create_temporary_pub()
diff --git a/tests/utilities.py b/tests/utilities.py
index 51788936d..8ab5ffb19 100644
--- a/tests/utilities.py
+++ b/tests/utilities.py
@@ -14,9 +14,11 @@ from wcs import sql
from webtest import TestApp
from quixote import cleanup, get_publisher
+from django.conf import settings
import wcs
-from wcs import publisher
+import wcs.wsgi
+from wcs import publisher, compat
from wcs.qommon.http_request import HTTPRequest
from wcs.users import User
from wcs.tracking_code import TrackingCode
@@ -24,28 +26,6 @@ import wcs.qommon.sms
import qommon.sms
from qommon.errors import ConnectionError
-class QWIP:
- # copy of quixote original QWIP code, adapted to use our own HTTPRequest
- # object and to process after jobs.
- request_class = HTTPRequest
-
- def __init__(self, publisher):
- self.publisher = publisher
-
- def __call__(self, env, start_response):
- """I am called for each request."""
- if 'REQUEST_URI' not in env:
- env['REQUEST_URI'] = env['SCRIPT_NAME'] + env['PATH_INFO']
- input = env['wsgi.input']
- request = self.request_class(input, env)
- response = self.publisher.process_request(request)
- status = "%03d %s" % (response.status_code, response.reason_phrase)
- headers = response.generate_headers()
- start_response(status, headers)
- result = list(response.generate_body_chunks()) # Iterable object.
- response.process_after_jobs()
- return result
-
class KnownElements(object):
pickle_app_dir = None
sql_app_dir = None
@@ -72,11 +52,11 @@ def create_temporary_pub(sql_mode=False):
elif sql_mode is False:
known_elements.pickle_app_dir = APP_DIR
- publisher.WcsPublisher.APP_DIR = APP_DIR
- publisher.WcsPublisher.DATA_DIR = os.path.abspath(
+ compat.CompatWcsPublisher.APP_DIR = APP_DIR
+ compat.CompatWcsPublisher.DATA_DIR = os.path.abspath(
os.path.join(os.path.dirname(wcs.__file__), '..', 'data'))
- publisher.WcsPublisher.cronjobs = None
- pub = publisher.WcsPublisher.create_publisher()
+ compat.CompatWcsPublisher.cronjobs = None
+ pub = compat.CompatWcsPublisher.create_publisher()
# allow saving the user
pub.app_dir = os.path.join(APP_DIR, 'example.net')
pub.site_charset = 'utf-8'
@@ -163,8 +143,11 @@ def clean_temporary_pub():
def get_app(pub, https=False):
extra_environ = {'HTTP_HOST': 'example.net', 'REMOTE_ADDR': '127.0.0.1'}
if https:
+ settings.SECURE_PROXY_SSL_HEADER = ('HTTPS', 'on')
extra_environ['HTTPS'] = 'on'
- return TestApp(QWIP(pub), extra_environ=extra_environ)
+ else:
+ extra_environ['HTTPS'] = 'off'
+ return TestApp(wcs.wsgi.application, extra_environ=extra_environ)
def login(app, username='admin', password='admin'):
login_page = app.get('/login/')
diff --git a/wcs/compat.py b/wcs/compat.py
new file mode 100644
index 000000000..38a9944f9
--- /dev/null
+++ b/wcs/compat.py
@@ -0,0 +1,141 @@
+# w.c.s. - web application for online forms
+# Copyright (C) 2005-2013 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 ConfigParser
+import os
+
+from threading import Lock
+
+from quixote import get_publisher
+from quixote.errors import PublishError
+from quixote.http_request import Upload
+
+from django.http import HttpResponse
+from django.conf import settings
+
+from .qommon import template
+from .qommon.publisher import get_cfg, set_publisher_class
+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
+ # 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)
+
+
+class CompatHTTPRequest(HTTPRequest):
+ def __init__(self, request):
+ self.django_request = request
+ self.response = None
+ request.environ['SCRIPT_NAME'] = str(request.environ['SCRIPT_NAME'])
+ request.environ['PATH_INFO'] = str(request.environ['PATH_INFO'])
+ self.META = self.django_request.META
+ HTTPRequest.__init__(self, None, request.environ)
+ self.scheme = str(self.django_request.scheme)
+
+ def _process_urlencoded(self, length, params):
+ return self._process_multipart(length, params)
+
+ def _process_multipart(self, length, params):
+ # Make sure request.form doesn't contain unicode strings, converting
+ # them all to strings in the site charset; it would contain unicode
+ # strings when the user agent specifies a charset in a mime content
+ # part, such a behaviour appears with some Nokia phones (6020, 6300)
+ site_charset = get_publisher().site_charset
+ # parse multipart data with the charset of the website
+ if 'charset' not in params:
+ params['charset'] = site_charset
+ if not self.form:
+ self.form = {}
+ for k, v in self.django_request.POST.items():
+ if type(v) is unicode:
+ self.form[str(k)] = v.encode(site_charset)
+ else:
+ self.form[str(k)] = v
+
+ for k, upload_file in self.django_request.FILES.items():
+ upload = Upload(upload_file.name.encode('utf-8'),
+ upload_file.content_type.encode('utf-8'),
+ upload_file.charset)
+ upload.fp = upload_file.file
+ self.form[str(k)] = upload
+
+ def build_absolute_uri(self):
+ return self.django_request.build_absolute_uri()
+
+
+class CompatWcsPublisher(WcsPublisher):
+ def filter_output(self, request, output):
+ response = self.get_request().response
+ if response.status_code == 304:
+ # clients don't like to receive content with a 304
+ return ''
+ if response.content_type != 'text/html':
+ return output
+ if not hasattr(response, 'filter') or not response.filter:
+ return output
+ return self.render_response(output)
+
+ def process_request(self, request):
+ self._set_request(request)
+ try:
+ self.parse_request(request)
+ output = self.try_publish(request)
+ except PublishError, exc:
+ output = self.finish_interrupted_request(exc)
+ except Exception, exc:
+ output = self.finish_failed_request()
+ response = request.response
+
+ output = self.filter_output(request, output)
+
+ content = output
+ django_response = HttpResponse(content,
+ content_type=response.content_type,
+ status=response.status_code,
+ reason=response.reason_phrase)
+
+ for name, value in response.generate_headers():
+ if name == 'Content-Length':
+ continue
+ django_response[name] = value
+
+ self._clear_request()
+ return django_response
+
+
+# keep a lock during quixote processing as it's not meant to work with threads;
+# the publisher instance can't be shared for concurrent requests.
+quixote_lock = Lock()
+
+def quixote(request):
+ with quixote_lock:
+ pub = get_publisher()
+ compat_request = CompatHTTPRequest(request)
+ return pub.process_request(compat_request)
+
+set_publisher_class(CompatWcsPublisher)
diff --git a/wcs/ctl/start.py b/wcs/ctl/start.py
deleted file mode 100644
index 54d2facc1..000000000
--- a/wcs/ctl/start.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# w.c.s. - web application for online forms
-# Copyright (C) 2005-2010 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 os
-import socket
-import sys
-import qommon.scgi_server
-import quixote
-import quixote.server.simple_server
-
-from qommon.ctl import Command, make_option
-
-class CmdStart(Command):
- name = 'start'
-
- def __init__(self):
- Command.__init__(self, [
- make_option('--port', metavar='PORT', action='store',
- dest='port', default=3001),
- make_option('--handler-connection-limit', metavar='LIMIT',
- action='store',
- dest='handler_connection_limit', default=None),
- make_option('--script-name', metavar='NAME', action='store',
- dest='script_name', default=None),
- make_option('--http', action='store_true',
- dest='http', default=False),
- make_option('--silent', action='store_true',
- dest='silent', default=False),
- make_option('--daemonize', action='store_true',
- dest='daemonize', default=False),
- make_option('--pidfile', metavar='FILENAME',
- dest='pidfile'),
- make_option('--max-children', metavar='MAX_CHILDREN',
- type='int', action='store', dest='max_children')
- ])
-
- def make_silent(self):
- sys.stdout.flush()
- sys.stderr.flush()
- si = file('/dev/null', 'r')
- so = file('/dev/null', 'a+')
- se = file('/dev/null', 'a+', 0)
- os.dup2(si.fileno(), sys.stdin.fileno())
- os.dup2(so.fileno(), sys.stdout.fileno())
- os.dup2(se.fileno(), sys.stderr.fileno())
-
- def del_pid(self):
- if not hasattr(self, 'pidfile'):
- return
- try:
- os.remove(self.pidfile)
- except OSError:
- pass
-
- def execute(self, base_options, sub_options, args):
- import publisher
-
- run_kwargs = {}
- run_kwargs['port'] = int(sub_options.port)
- run_kwargs['spawn_cron'] = True
- run_function = qommon.scgi_server.run
- publisher.WcsPublisher.configure(self.config)
- if sub_options.handler_connection_limit:
- run_kwargs['handler_connection_limit'] = int(sub_options.handler_connection_limit)
- elif self.config.has_option('main', 'handler_connection_limit'):
- run_kwargs['handler_connection_limit'] = self.config.getint('main', 'handler_connection_limit')
- if sub_options.script_name:
- run_kwargs['script_name'] = sub_options.script_name
- if sub_options.max_children:
- run_kwargs['max_children'] = sub_options.max_children
- elif self.config.has_option('main', 'max_children'):
- run_kwargs['max_children'] = self.config.getint('main', 'max_children')
- if sub_options.http:
- run_function = qommon.scgi_server.http_run
- if sub_options.silent:
- self.make_silent()
-
- # iterate over all tenants to execute required SQL migrations
- # beforehand.
- pub = publisher.WcsPublisher.create_publisher(register_cron=False)
- quixote.cleanup()
- base_app_dir = pub.app_dir
- for hostname in publisher.WcsPublisher.get_tenants():
- tenant_path = os.path.join(base_app_dir, hostname)
- if not os.path.exists(os.path.join(tenant_path, 'config.pck')):
- continue
- pub = publisher.WcsPublisher.create_publisher(register_cron=False)
- pub.app_dir = tenant_path
- pub.set_config()
- if pub.is_using_postgresql():
- pub.migrate_sql()
- pub.cleanup()
- quixote.cleanup()
-
- if sub_options.daemonize:
- try:
- pid = os.fork()
- if pid > 0: # exit first parent
- sys.exit(0)
- except OSError, e:
- print >> sys.stderr, 'failure in first fork (%s)' % e.strerror
- sys.exit(1)
-
- os.chdir("/")
- os.setsid()
- os.umask(0)
-
- # do second fork
- try:
- pid = os.fork()
- if pid > 0: # exit from second parent
- sys.exit(0)
- except OSError, e:
- print >> sys.stderr, 'failure in second fork (%s)' % e.strerror
- sys.exit(1)
-
- if not sub_options.silent:
- self.make_silent()
-
- if sub_options.pidfile:
- self.pidfile = sub_options.pidfile
- file(self.pidfile, 'w+').write("%s\n" % os.getpid())
-
- try:
- run_function(publisher.WcsPublisher.create_publisher, **run_kwargs)
- except socket.error, e:
- self.del_pid()
- if e[0] == 98:
- print >> sys.stderr, 'address already in use'
- sys.exit(1)
- raise
- except KeyboardInterrupt:
- self.del_pid()
- sys.exit(1)
-
- self.del_pid()
-
-CmdStart.register()
-
diff --git a/wcs/ctl/stop.py b/wcs/ctl/stop.py
deleted file mode 100644
index 7203e5b84..000000000
--- a/wcs/ctl/stop.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# w.c.s. - web application for online forms
-# Copyright (C) 2005-2013 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 errno
-import os
-import signal
-import sys
-import time
-
-from qommon.ctl import Command, make_option
-
-class CmdStop(Command):
- name = 'stop'
-
- def __init__(self):
- Command.__init__(self, [
- make_option('--pidfile', metavar='FILENAME',
- dest='pidfile')
- ])
-
- def execute(self, base_options, sub_options, args):
- self.pidfile = sub_options.pidfile
- if not self.pidfile:
- print >> sys.stderr, 'you must specificy --pidfile'
- sys.exit(1)
- try:
- pf = file(self.pidfile, 'r')
- pid = int(pf.read().strip())
- pf.close()
- except IOError:
- pid = None
-
- if not pid:
- print >> sys.stderr, 'pidfile does not exist. Daemon not running?'
- sys.exit(1)
-
- # Try killing the daemon process...
- try:
- while 1:
- os.kill(pid, signal.SIGTERM)
- time.sleep(0.1)
- except OSError, err:
- if err.errno == errno.ESRCH:
- if os.path.exists(self.pidfile):
- os.remove(self.pidfile)
- else:
- print str(err)
- sys.exit(1)
-
-CmdStop.register()
diff --git a/wcs/middleware.py b/wcs/middleware.py
new file mode 100644
index 000000000..11d47be1e
--- /dev/null
+++ b/wcs/middleware.py
@@ -0,0 +1,26 @@
+# w.c.s. - web application for online forms
+# Copyright (C) 2005-2013 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 .
+
+from .compat import init_publisher_if_needed, CompatHTTPRequest
+
+class PublisherInitialisationMiddleware(object):
+ '''Initializes the publisher according to the request server name.'''
+ def process_request(self, request):
+ pub = init_publisher_if_needed()
+ compat_request = CompatHTTPRequest(request)
+ pub.init_publish(compat_request)
+ pub._set_request(compat_request)
+ request._publisher = pub
diff --git a/wcs/qommon/errors.py b/wcs/qommon/errors.py
index eef654f01..b2ec8a13b 100644
--- a/wcs/qommon/errors.py
+++ b/wcs/qommon/errors.py
@@ -56,7 +56,7 @@ class AccessUnauthorizedError(AccessForbiddenError):
session.message = ('error', self.public_msg)
login_url = get_publisher().get_root_url() + 'login/'
login_url += '?' + urllib.urlencode({'next': request.get_frontoffice_url()})
- quixote.redirect(login_url)
+ return quixote.redirect(login_url)
class EmailError(Exception):
pass
diff --git a/wcs/qommon/http_request.py b/wcs/qommon/http_request.py
index dd1afdfe0..dc0e55b24 100644
--- a/wcs/qommon/http_request.py
+++ b/wcs/qommon/http_request.py
@@ -111,7 +111,7 @@ class HTTPRequest(quixote.http_request.HTTPRequest):
if ctype == 'application/json':
from .misc import json_loads
length = int(self.environ.get('CONTENT_LENGTH') or '0')
- payload = self.stdin.read(length)
+ payload = self.django_request.read(length)
try:
self.json = json_loads(payload)
except ValueError, e:
@@ -147,3 +147,7 @@ class HTTPRequest(quixote.http_request.HTTPRequest):
def is_in_backoffice(self):
return self.get_path().startswith('/backoffice/')
+
+ @property
+ def META(self):
+ return self.environ
diff --git a/wcs/qommon/publisher.py b/wcs/qommon/publisher.py
index b25f62cc5..9e4d18bbb 100644
--- a/wcs/qommon/publisher.py
+++ b/wcs/qommon/publisher.py
@@ -1112,14 +1112,12 @@ def get_cfg(key, default = None):
def get_logger():
return get_publisher().get_app_logger()
-_publisher_class = None
def set_publisher_class(klass):
- global _publisher_class
- _publisher_class = klass
+ __builtin__.__dict__['__publisher_class'] = klass
def get_publisher_class():
- return _publisher_class
+ return __builtin__.__dict__.get('__publisher_class')
Substitutions.register('site_name', category=N_('General'), comment=N_('Site Name'))
diff --git a/wcs/qommon/scgi_server.py b/wcs/qommon/scgi_server.py
deleted file mode 100644
index 24c3a9e34..000000000
--- a/wcs/qommon/scgi_server.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# w.c.s. - web application for online forms
-# Copyright (C) 2005-2010 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 sys
-import os
-import signal
-
-from scgi import scgi_server
-
-import quixote.server.scgi_server
-
-import cron
-import quixote.server.simple_server as simple_server
-
-
-class QommonHandler(quixote.server.scgi_server.QuixoteHandler):
- connection_limit = -1
- number_of_connection_handled = 0
-
- def handle_connection(self, conn):
- self.number_of_connection_handled = self.number_of_connection_handled + 1
- quixote.server.scgi_server.QuixoteHandler.handle_connection(self, conn)
- # input, output and conn are closed, long running jobs could be done
- # here.
- self.publisher.response.process_after_jobs()
- if self.number_of_connection_handled == self.connection_limit:
- raise SystemExit
-
-
-class SCGIServer(scgi_server.SCGIServer):
- def reap_children(self):
- while self.children:
- (pid, status) = os.waitpid(-1, os.WNOHANG)
- if pid <= 0:
- break
- try:
- os.close(self.children[pid])
- except KeyError:
- print >> sys.stderr, 'Closing connection to unknown pid:', pid
- continue
- del self.children[pid]
-
-def restart():
- '''Tell the server process to stop all worker process and start again'''
- ppid = os.getppid()
- os.kill(ppid, signal.SIGHUP)
-
-def run(create_publisher, host='localhost', port=3000, script_name=None,
- max_children = 5, spawn_cron = False, handler_connection_limit = -1):
-
- if spawn_cron:
- cron.spawn_cron(create_publisher)
-
- def create_handler(parent_fd):
- h = QommonHandler(parent_fd, create_publisher, script_name)
- h.connection_limit = handler_connection_limit
- return h
-
- s = SCGIServer(create_handler, host=host, port=port,
- max_children=max_children)
- s.serve()
-
-
-class QommonHTTPRequestHandler(simple_server.HTTPRequestHandler):
- def process(self, env, include_body=True):
- simple_server.HTTPRequestHandler.process(self, env, include_body=include_body)
- self.publisher.response.process_after_jobs()
-
-
-def http_run(create_publisher, spawn_cron = False,
- handler_connection_limit = -1, host = '', port = 80, https = False):
- """Runs a simple, single threaded, synchronous HTTP server that
- publishes a Quixote application.
- """
-
- if spawn_cron:
- cron.spawn_cron(create_publisher)
-
- if https:
- QommonHTTPRequestHandler.required_cgi_environment['HTTPS'] = 'on'
-
- httpd = simple_server.HTTPServer((host, port), QommonHTTPRequestHandler)
- def handle_error(request, client_address):
- simple_server.HTTPServer.handle_error(httpd, request, client_address)
- if sys.exc_info()[0] is SystemExit:
- raise sys.exc_info()[0]
- httpd.handle_error = handle_error
- QommonHTTPRequestHandler.publisher = create_publisher()
- try:
- httpd.serve_forever()
- finally:
- httpd.server_close()
-
-
diff --git a/wcs/settings.py b/wcs/settings.py
new file mode 100644
index 000000000..fc83e7160
--- /dev/null
+++ b/wcs/settings.py
@@ -0,0 +1,127 @@
+# Django settings for wcs project.
+
+import os
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+PROJECT_PATH = os.path.dirname(os.path.dirname(__file__))
+
+ADMINS = (
+ # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(PROJECT_PATH, 'wcs.sqlite3'),
+ }
+}
+
+# Hosts/domain names that are valid for this site; required if DEBUG is False
+# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = []
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# In a Windows environment this must be set to your system time zone.
+TIME_ZONE = 'Europe/Brussels'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = False
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale.
+USE_L10N = False
+
+# If you set this to False, Django will not use timezone-aware datetimes.
+USE_TZ = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/var/www/example.com/media/"
+MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://example.com/media/", "http://media.example.com/"
+MEDIA_URL = '/media/'
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/var/www/example.com/static/"
+STATIC_ROOT = os.path.join(PROJECT_PATH, 'static')
+
+# URL prefix for static files.
+# Example: "http://example.com/static/", "http://static.example.com/"
+STATIC_URL = '/static/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ os.path.join(PROJECT_PATH, 'wcs', 'static'),
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'k16cal%1fnochq4xbxqgdns-21lt9lxeof5*%j(0ief3=db32&'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'wcs.middleware.PublisherInitialisationMiddleware',
+ #'django.contrib.sessions.middleware.SessionMiddleware',
+ #'django.middleware.csrf.CsrfViewMiddleware',
+ #'django.contrib.auth.middleware.AuthenticationMiddleware',
+ #'django.contrib.messages.middleware.MessageMiddleware',
+ # Uncomment the next line for simple clickjacking protection:
+ # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'wcs.urls'
+
+# Python dotted path to the WSGI application used by Django's runserver.
+WSGI_APPLICATION = 'wcs.wsgi.application'
+
+TEMPLATE_DIRS = (
+ os.path.join(PROJECT_PATH, 'wcs', 'templates'),
+)
+
+INSTALLED_APPS = (
+ #'django.contrib.auth',
+ #'django.contrib.contenttypes',
+ #'django.contrib.sessions',
+ #'django.contrib.sites',
+ #'django.contrib.messages',
+ #'django.contrib.staticfiles',
+ #'django.contrib.admin',
+)
+
+WCS_LEGACY_CONFIG_FILE = None
+
+local_settings_file = os.environ.get('WCS_SETTINGS_FILE',
+ os.path.join(os.path.dirname(__file__), 'local_settings.py'))
+if os.path.exists(local_settings_file):
+ execfile(local_settings_file)
diff --git a/wcs/urls.py b/wcs/urls.py
new file mode 100644
index 000000000..eb85bafb0
--- /dev/null
+++ b/wcs/urls.py
@@ -0,0 +1,21 @@
+# w.c.s. - web application for online forms
+# Copyright (C) 2005-2013 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 .
+
+from django.conf.urls import patterns, url
+
+urlpatterns = patterns('',
+ url(r'', 'wcs.compat.quixote', name='quixote'),
+)
diff --git a/wcs/views.py b/wcs/views.py
new file mode 100644
index 000000000..8765d2024
--- /dev/null
+++ b/wcs/views.py
@@ -0,0 +1,16 @@
+# w.c.s. - web application for online forms
+# Copyright (C) 2005-2013 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 .
+
diff --git a/wcs/wsgi.py b/wcs/wsgi.py
new file mode 100644
index 000000000..3b1705df1
--- /dev/null
+++ b/wcs/wsgi.py
@@ -0,0 +1,8 @@
+"""
+WSGI config for wcs project.
+"""
+import os
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wcs.settings")
+
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()