switch to django request/response handling (#6735)
This commit is contained in:
parent
fa5d08658b
commit
0482cdcd7d
|
@ -1 +1,2 @@
|
|||
*.pyc
|
||||
local_settings.py
|
||||
|
|
55
INSTALL
55
INSTALL
|
@ -1,57 +1,4 @@
|
|||
w.c.s. - Installation Instructions
|
||||
==================================
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
- Python 2.5 <http://www.python.org/>
|
||||
- Quixote 2.5 <http://www.mems-exchange.org/software/quixote/>
|
||||
- Lasso 2.2 <http://lasso.entrouvert.org/> 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.
|
||||
|
||||
<VirtualHost *>
|
||||
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/
|
||||
<LocationMatch "^/static">
|
||||
SCGIHandler off
|
||||
</LocationMatch>
|
||||
|
||||
# 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
|
||||
|
||||
</VirtualHost>
|
||||
We recommend using .deb packages from https://deb.entrouvert.org/
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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 '<p>The requested link' in response.body
|
||||
assert '<p>The requested link' in str(response)
|
||||
|
||||
def test_get_tenants():
|
||||
pub = create_temporary_pub()
|
||||
|
|
|
@ -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/')
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
152
wcs/ctl/start.py
152
wcs/ctl/start.py
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
||||
|
|
@ -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)
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'', 'wcs.compat.quixote', name='quixote'),
|
||||
)
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
|
@ -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()
|
Loading…
Reference in New Issue