Initial commit
This commit is contained in:
commit
7004713412
19 changed files with 772 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*~
|
||||||
|
.*.swp
|
||||||
|
/*.egg-info
|
||||||
|
/cache/*
|
||||||
|
/data/*
|
10
Makefile
Normal file
10
Makefile
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.PHONY: clean flake8
|
||||||
|
|
||||||
|
all: clean flake8
|
||||||
|
|
||||||
|
clean:
|
||||||
|
find -name "*.pyc" | xargs rm -f
|
||||||
|
rm -rf cache/*
|
||||||
|
|
||||||
|
flake8:
|
||||||
|
flake8 --ignore=E123,E128 --max-line-length=120 mycoserver
|
24
README
Normal file
24
README
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Install
|
||||||
|
=======
|
||||||
|
|
||||||
|
Debian dependencies:
|
||||||
|
|
||||||
|
$ aptitude install python python-mako python-markupsafe python-paste python-pastedeploy python-pastescript \
|
||||||
|
python-weberror python-webhelpers python-webob
|
||||||
|
|
||||||
|
Non-Debian dependencies:
|
||||||
|
|
||||||
|
$ git clone git://gitorious.org/biryani/biryani.git biryani1
|
||||||
|
$ cd biryani1
|
||||||
|
$ git checkout biryani1
|
||||||
|
$ python setup.py develop --no-deps --user
|
||||||
|
|
||||||
|
Install mycoserver Python egg (from mycoserver root directory):
|
||||||
|
|
||||||
|
$ python setup.py develop --no-deps --user
|
||||||
|
|
||||||
|
|
||||||
|
Start server
|
||||||
|
============
|
||||||
|
|
||||||
|
$ paster serve --reload development.ini
|
53
development.ini
Normal file
53
development.ini
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# EesyVPN Web - Development environment configuration
|
||||||
|
#
|
||||||
|
# The %(here)s variable will be replaced with the parent directory of this file.
|
||||||
|
|
||||||
|
[DEFAULT]
|
||||||
|
debug = true
|
||||||
|
# Uncomment and replace with the address which should receive any error reports
|
||||||
|
#email_to = you@yourdomain.com
|
||||||
|
smtp_server = localhost
|
||||||
|
from_address = myco-server@localhost
|
||||||
|
dbpass = myP@ssw0rd
|
||||||
|
|
||||||
|
[server:main]
|
||||||
|
use = egg:Paste#http
|
||||||
|
host = 0.0.0.0
|
||||||
|
port = 8765
|
||||||
|
|
||||||
|
[app:main]
|
||||||
|
use = egg:MyCoServer
|
||||||
|
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root, mycoserver, mycoserver_router
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = DEBUG
|
||||||
|
handlers = console
|
||||||
|
|
||||||
|
[logger_mycoserver]
|
||||||
|
level = DEBUG
|
||||||
|
handlers =
|
||||||
|
qualname = mycoserver
|
||||||
|
|
||||||
|
[logger_mycoserver_router]
|
||||||
|
level = DEBUG
|
||||||
|
handlers =
|
||||||
|
qualname = mycoserver.router
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s:%(funcName)s line %(lineno)d] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
0
mycoserver/__init__.py
Normal file
0
mycoserver/__init__.py
Normal file
57
mycoserver/application.py
Normal file
57
mycoserver/application.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
"""Middleware initialization"""
|
||||||
|
|
||||||
|
|
||||||
|
import logging.config
|
||||||
|
import os
|
||||||
|
|
||||||
|
from paste.cascade import Cascade
|
||||||
|
from paste.urlparser import StaticURLParser
|
||||||
|
from weberror.errormiddleware import ErrorMiddleware
|
||||||
|
|
||||||
|
from . import configuration, context, controllers, templates
|
||||||
|
|
||||||
|
import db
|
||||||
|
|
||||||
|
|
||||||
|
def make_app(global_conf, **app_conf):
|
||||||
|
"""Create a WSGI application and return it
|
||||||
|
|
||||||
|
``global_conf``
|
||||||
|
The inherited configuration for this application. Normally from
|
||||||
|
the [DEFAULT] section of the Paste ini file.
|
||||||
|
|
||||||
|
``app_conf``
|
||||||
|
The application's local configuration. Normally specified in
|
||||||
|
the [app:<name>] section of the Paste ini file (where <name>
|
||||||
|
defaults to main).
|
||||||
|
"""
|
||||||
|
logging.config.fileConfig(global_conf['__file__'])
|
||||||
|
app_ctx = context.Context()
|
||||||
|
app_ctx.conf = configuration.load_configuration(global_conf, app_conf)
|
||||||
|
app_ctx.templates = templates.load_templates(app_ctx)
|
||||||
|
app_ctx.db = db.DB(
|
||||||
|
app_ctx.conf.get('dbhost','localhost'),
|
||||||
|
app_ctx.conf.get('dbuser','myco'),
|
||||||
|
app_ctx.conf.get('dbpass','password'),
|
||||||
|
app_ctx.conf.get('dbname','myco'),
|
||||||
|
)
|
||||||
|
if not app_ctx.db.connect():
|
||||||
|
logging.error('Failed to connect DB')
|
||||||
|
app = controllers.make_router()
|
||||||
|
app = context.make_add_context_to_request(app, app_ctx)
|
||||||
|
if not app_ctx.conf['debug']:
|
||||||
|
app = ErrorMiddleware(
|
||||||
|
app,
|
||||||
|
error_email=app_ctx.conf['email_to'],
|
||||||
|
error_log=app_ctx.conf.get('error_log', None),
|
||||||
|
error_message=app_ctx.conf.get('error_message', 'An internal server error occurred'),
|
||||||
|
error_subject_prefix=app_ctx.conf.get('error_subject_prefix', 'Web application error: '),
|
||||||
|
from_address=app_ctx.conf['from_address'],
|
||||||
|
smtp_server=app_ctx.conf.get('smtp_server', 'localhost'),
|
||||||
|
)
|
||||||
|
app = Cascade([StaticURLParser(os.path.join(app_ctx.conf['app_dir'], 'static')), app])
|
||||||
|
app.ctx = app_ctx
|
||||||
|
return app
|
30
mycoserver/configuration.py
Normal file
30
mycoserver/configuration.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
"""Paste INI configuration"""
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from biryani1 import strings
|
||||||
|
from biryani1.baseconv import (check, default, guess_bool, pipe, struct)
|
||||||
|
|
||||||
|
|
||||||
|
def load_configuration(global_conf, app_conf):
|
||||||
|
"""Build the application configuration dict."""
|
||||||
|
app_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
conf = {}
|
||||||
|
conf.update(strings.deep_decode(global_conf))
|
||||||
|
conf.update(strings.deep_decode(app_conf))
|
||||||
|
conf.update(check(struct(
|
||||||
|
{
|
||||||
|
'app_conf': default(app_conf),
|
||||||
|
'app_dir': default(app_dir),
|
||||||
|
'cache_dir': default(os.path.join(os.path.dirname(app_dir), 'cache')),
|
||||||
|
'debug': pipe(guess_bool, default(False)),
|
||||||
|
'global_conf': default(global_conf),
|
||||||
|
},
|
||||||
|
default='drop',
|
||||||
|
drop_none_values=False,
|
||||||
|
))(conf))
|
||||||
|
return conf
|
24
mycoserver/context.py
Normal file
24
mycoserver/context.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
"""Context loaded and saved in WSGI requests"""
|
||||||
|
|
||||||
|
|
||||||
|
from webob.dec import wsgify
|
||||||
|
|
||||||
|
|
||||||
|
def make_add_context_to_request(app, app_ctx):
|
||||||
|
"""Return a WSGI middleware that adds context to requests."""
|
||||||
|
@wsgify
|
||||||
|
def add_context_to_request(req):
|
||||||
|
req.ctx = app_ctx
|
||||||
|
req.ctx.req = req
|
||||||
|
return req.get_response(app)
|
||||||
|
return add_context_to_request
|
||||||
|
|
||||||
|
|
||||||
|
class Context(object):
|
||||||
|
_ = lambda self, message: message
|
||||||
|
conf = None
|
||||||
|
templates = None
|
||||||
|
db = None
|
70
mycoserver/controllers.py
Normal file
70
mycoserver/controllers.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from webob.dec import wsgify
|
||||||
|
|
||||||
|
from . import conv, router, templates, wsgi_helpers
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@wsgify
|
||||||
|
def home(req):
|
||||||
|
return templates.render(req.ctx, '/home.mako', data={})
|
||||||
|
|
||||||
|
@wsgify
|
||||||
|
def login(req):
|
||||||
|
params = req.params
|
||||||
|
log.debug(u'params = {}'.format(params))
|
||||||
|
inputs = {
|
||||||
|
'email': params.get('email'),
|
||||||
|
'password': params.get('password'),
|
||||||
|
}
|
||||||
|
log.debug(u'inputs = {}'.format(inputs))
|
||||||
|
data, errors = conv.inputs_to_login_data(inputs)
|
||||||
|
if errors is not None:
|
||||||
|
return wsgi_helpers.bad_request(req.ctx, comment=errors)
|
||||||
|
|
||||||
|
log.debug(u'data = {}'.format(data))
|
||||||
|
|
||||||
|
login_data=req.ctx.db.login(data['email'],data['password'])
|
||||||
|
return wsgi_helpers.respond_json(req.ctx,login_data,headers=[('Access-Control-Allow-Origin','*')])
|
||||||
|
|
||||||
|
@wsgify
|
||||||
|
def sync(req):
|
||||||
|
params = req.params
|
||||||
|
log.debug(u'params = {}'.format(params))
|
||||||
|
inputs = {
|
||||||
|
'email': params.get('email'),
|
||||||
|
'password': params.get('password'),
|
||||||
|
'groups': params.get('groups')
|
||||||
|
}
|
||||||
|
log.debug(u'inputs = {}'.format(inputs))
|
||||||
|
data, errors = conv.inputs_to_sync_data(inputs)
|
||||||
|
if errors is not None or data['groups'] is None:
|
||||||
|
return wsgi_helpers.bad_request(req.ctx, comment=errors)
|
||||||
|
|
||||||
|
data['groups']=json.loads(data['groups'])
|
||||||
|
|
||||||
|
log.debug(u'data = {}'.format(data))
|
||||||
|
|
||||||
|
login_data=req.ctx.db.login(data['email'],data['password'])
|
||||||
|
if 'email' in login_data:
|
||||||
|
ret=req.ctx.db.sync_group(data['email'],data['groups'])
|
||||||
|
return wsgi_helpers.respond_json(req.ctx,ret,headers=[('Access-Control-Allow-Origin','*')])
|
||||||
|
else:
|
||||||
|
return wsgi_helpers.respond_json(
|
||||||
|
req.ctx,
|
||||||
|
login_data,
|
||||||
|
headers=[('Access-Control-Allow-Origin','*')]
|
||||||
|
)
|
||||||
|
|
||||||
|
def make_router():
|
||||||
|
return router.make_router(
|
||||||
|
('GET', '^/$', home),
|
||||||
|
('GET', '^/login$', login),
|
||||||
|
('GET', '^/sync$', sync),
|
||||||
|
)
|
23
mycoserver/conv.py
Normal file
23
mycoserver/conv.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
from biryani1.baseconv import cleanup_line, empty_to_none, not_none, pipe, struct
|
||||||
|
|
||||||
|
inputs_to_login_data = struct(
|
||||||
|
{
|
||||||
|
'email': pipe(cleanup_line, empty_to_none),
|
||||||
|
'password': pipe(cleanup_line, empty_to_none),
|
||||||
|
},
|
||||||
|
default='drop',
|
||||||
|
drop_none_values=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs_to_sync_data = struct(
|
||||||
|
{
|
||||||
|
'email': pipe(cleanup_line, empty_to_none),
|
||||||
|
'password': pipe(cleanup_line, empty_to_none),
|
||||||
|
'groups': pipe(empty_to_none),
|
||||||
|
},
|
||||||
|
default='drop',
|
||||||
|
drop_none_values=False,
|
||||||
|
)
|
84
mycoserver/db/__init__.py
Normal file
84
mycoserver/db/__init__.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
import MySQLdb
|
||||||
|
|
||||||
|
class DB(object):
|
||||||
|
|
||||||
|
def __init__(self,host,user,pwd,db):
|
||||||
|
self.host = host
|
||||||
|
self.user = user
|
||||||
|
self.pwd = pwd
|
||||||
|
self.db = db
|
||||||
|
self.con = 0
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
if self.con == 0:
|
||||||
|
try:
|
||||||
|
con = MySQLdb.connect(self.host,self.user,self.pwd,self.db)
|
||||||
|
self.con = con
|
||||||
|
return True
|
||||||
|
except Exception, e:
|
||||||
|
log.fatal('Error connecting to database : %s' % e)
|
||||||
|
return
|
||||||
|
|
||||||
|
def do_sql(self,sql):
|
||||||
|
try:
|
||||||
|
c=self.con.cursor()
|
||||||
|
c.execute(sql)
|
||||||
|
self.con.commit()
|
||||||
|
return c
|
||||||
|
except Exception,e:
|
||||||
|
log.error('Error executing request %s : %s' % (sql,e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def select(self,sql):
|
||||||
|
ret=self.do_sql(sql)
|
||||||
|
if ret!=False:
|
||||||
|
return ret.fetchall()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def login(self,email,password):
|
||||||
|
ret=self.select("SELECT email,name,password FROM users WHERE email='%s' AND password='%s'" % (email,password))
|
||||||
|
log.debug(ret)
|
||||||
|
if ret:
|
||||||
|
if len(ret)==1:
|
||||||
|
return {
|
||||||
|
'email': ret[0][0],
|
||||||
|
'name': ret[0][1]
|
||||||
|
}
|
||||||
|
elif len(ret)>=1:
|
||||||
|
log.warning('Duplicate user %s in database' % email)
|
||||||
|
elif ret==():
|
||||||
|
return { 'loginerror': 'Utilisateur inconnu' }
|
||||||
|
return { 'loginerror': 'Erreur inconnu' }
|
||||||
|
|
||||||
|
def sync_group(self,email,groups):
|
||||||
|
db_groups=self.get_group(email)
|
||||||
|
json_group=json.dumps(groups)
|
||||||
|
if db_groups!=False:
|
||||||
|
if db_groups=={}:
|
||||||
|
if groups=={}:
|
||||||
|
return {'groups': {}}
|
||||||
|
else:
|
||||||
|
if self.do_sql("INSERT INTO groups (email,groups) VALUES ('%s','%s')" % (email,json_group)):
|
||||||
|
return {'groups': groups}
|
||||||
|
elif groups=={}:
|
||||||
|
return {'groups': db_groups}
|
||||||
|
else:
|
||||||
|
if self.do_sql("UPDATE groups SET groups='%s' WHERE email='%s'" % (json_group,email)):
|
||||||
|
return {'groups': groups}
|
||||||
|
return {'syncerror': 'Erreur inconnu'}
|
||||||
|
|
||||||
|
def get_group(self,email):
|
||||||
|
ret=self.select("SELECT groups FROM groups WHERE email='%s'" % email)
|
||||||
|
if ret!=False:
|
||||||
|
if len(ret)==1:
|
||||||
|
return json.loads(ret[0][0])
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
return False
|
50
mycoserver/router.py
Normal file
50
mycoserver/router.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
"""Helpers for URLs"""
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from webob.dec import wsgify
|
||||||
|
|
||||||
|
from . import wsgi_helpers
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def make_router(*routings):
|
||||||
|
"""Return a WSGI application that dispatches requests to controllers."""
|
||||||
|
routes = []
|
||||||
|
for routing in routings:
|
||||||
|
methods, regex, app = routing[:3]
|
||||||
|
if isinstance(methods, basestring):
|
||||||
|
methods = (methods,)
|
||||||
|
vars = routing[3] if len(routing) >= 4 else {}
|
||||||
|
routes.append((methods, re.compile(regex), app, vars))
|
||||||
|
|
||||||
|
@wsgify
|
||||||
|
def router(req):
|
||||||
|
"""Dispatch request to controllers."""
|
||||||
|
split_path_info = req.path_info.split('/')
|
||||||
|
assert not split_path_info[0], split_path_info
|
||||||
|
for methods, regex, app, vars in routes:
|
||||||
|
if methods is None or req.method in methods:
|
||||||
|
match = regex.match(req.path_info)
|
||||||
|
if match is not None:
|
||||||
|
log.debug(u'URL path = {path} matched controller {controller}'.format(
|
||||||
|
controller=app, path=req.path_info))
|
||||||
|
if getattr(req, 'urlvars', None) is None:
|
||||||
|
req.urlvars = {}
|
||||||
|
req.urlvars.update(dict(
|
||||||
|
(name, value.decode('utf-8') if value is not None else None)
|
||||||
|
for name, value in match.groupdict().iteritems()
|
||||||
|
))
|
||||||
|
req.urlvars.update(vars)
|
||||||
|
req.script_name += req.path_info[:match.end()]
|
||||||
|
req.path_info = req.path_info[match.end():]
|
||||||
|
return req.get_response(app)
|
||||||
|
return wsgi_helpers.not_found(req.ctx)
|
||||||
|
return router
|
44
mycoserver/templates/__init__.py
Normal file
44
mycoserver/templates/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
"""Mako templates rendering"""
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
import mako.lookup
|
||||||
|
import os
|
||||||
|
|
||||||
|
from . import helpers
|
||||||
|
|
||||||
|
|
||||||
|
js = lambda x: json.dumps(x, encoding='utf-8', ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def load_templates(ctx):
|
||||||
|
# Create the Mako TemplateLookup, with the default auto-escaping.
|
||||||
|
return mako.lookup.TemplateLookup(
|
||||||
|
default_filters=['h'],
|
||||||
|
directories=[os.path.join(ctx.conf['app_dir'], 'templates')],
|
||||||
|
input_encoding='utf-8',
|
||||||
|
module_directory=os.path.join(ctx.conf['cache_dir'], 'templates'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render(ctx, template_path, **kw):
|
||||||
|
return ctx.templates.get_template(template_path).render_unicode(
|
||||||
|
ctx=ctx,
|
||||||
|
helpers=helpers,
|
||||||
|
js=js,
|
||||||
|
N_=lambda message: message,
|
||||||
|
req=ctx.req,
|
||||||
|
**kw).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def render_def(ctx, template_path, def_name, **kw):
|
||||||
|
return ctx.templates.get_template(template_path).get_def(def_name).render_unicode(
|
||||||
|
_=ctx.translator.ugettext,
|
||||||
|
ctx=ctx,
|
||||||
|
js=js,
|
||||||
|
N_=lambda message: message,
|
||||||
|
req=ctx.req,
|
||||||
|
**kw).strip()
|
14
mycoserver/templates/helpers.py
Normal file
14
mycoserver/templates/helpers.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def random_sequence(length):
|
||||||
|
return [random.random() for idx in xrange(0, length)]
|
||||||
|
|
||||||
|
|
||||||
|
def relative_path(ctx, abs_path):
|
||||||
|
return os.path.relpath(abs_path, ctx.req.path)
|
13
mycoserver/templates/home.mako
Normal file
13
mycoserver/templates/home.mako
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
<%inherit file="/site.mako"/>
|
||||||
|
|
||||||
|
<%block name="title_content">MyCo</%block>
|
||||||
|
|
||||||
|
<%block name="body_content">
|
||||||
|
<div class="hero-unit">
|
||||||
|
<h1>MyCo <small>Gérer vos déponses communes</small></h1>
|
||||||
|
<p class="muted">Application mobile de gestion de vos dépenses communes.</p>
|
||||||
|
</div>
|
||||||
|
</%block>
|
23
mycoserver/templates/http-error.mako
Normal file
23
mycoserver/templates/http-error.mako
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
<%inherit file="/site.mako"/>
|
||||||
|
|
||||||
|
|
||||||
|
<%block name="body_content">
|
||||||
|
<div class="alert alert-block alert-error">
|
||||||
|
<h4 class="alert-heading">Error « ${title} »</h4>
|
||||||
|
<p>${explanation}</p>
|
||||||
|
% if comment:
|
||||||
|
<p>${comment}</p>
|
||||||
|
% endif
|
||||||
|
% if message:
|
||||||
|
<p>${message}</p>
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
|
</%block>
|
||||||
|
|
||||||
|
|
||||||
|
<%block name="title_content">
|
||||||
|
${title} - ${parent.title_content()}
|
||||||
|
</%block>
|
36
mycoserver/templates/site.mako
Normal file
36
mycoserver/templates/site.mako
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><%block name="title_content">MyCo</%block></title>
|
||||||
|
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css">
|
||||||
|
<link rel="stylesheet" href="${helpers.relative_path(ctx, '/css/style.css')}">
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||||
|
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand" href="index.html">MyCo</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<%block name="body_content"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery.js"></script>
|
||||||
|
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
170
mycoserver/wsgi_helpers.py
Normal file
170
mycoserver/wsgi_helpers.py
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import json
|
||||||
|
|
||||||
|
from markupsafe import Markup
|
||||||
|
from webhelpers.html import tags
|
||||||
|
import webob.dec
|
||||||
|
import webob.exc
|
||||||
|
|
||||||
|
from . import templates
|
||||||
|
|
||||||
|
|
||||||
|
N_ = lambda message: message
|
||||||
|
|
||||||
|
|
||||||
|
errors_explanation = {
|
||||||
|
400: N_("Request is faulty"),
|
||||||
|
401: N_("Access is restricted to authorized persons."),
|
||||||
|
403: N_("Access is forbidden."),
|
||||||
|
404: N_("The requested page was not found."),
|
||||||
|
}
|
||||||
|
errors_message = {
|
||||||
|
401: N_("You must login to access this page."),
|
||||||
|
}
|
||||||
|
errors_title = {
|
||||||
|
400: N_("Unable to Access"),
|
||||||
|
401: N_("Access Denied"),
|
||||||
|
403: N_("Access Denied"),
|
||||||
|
404: N_("Unable to Access"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def bad_request(ctx, **kw):
|
||||||
|
return error(ctx, 400, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def discard_empty_items(data):
|
||||||
|
if isinstance(data, collections.Mapping):
|
||||||
|
# Use type(data) to keep OrderedDicts.
|
||||||
|
data = type(data)(
|
||||||
|
(name, discard_empty_items(value))
|
||||||
|
for name, value in data.iteritems()
|
||||||
|
if value is not None
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def error(ctx, code, **kw):
|
||||||
|
response = webob.exc.status_map[code](headers=kw.pop('headers', None))
|
||||||
|
if code != 204: # No content
|
||||||
|
body = kw.pop('body', None)
|
||||||
|
if body is None:
|
||||||
|
template_path = kw.pop('template_path', '/http-error.mako')
|
||||||
|
explanation = kw.pop('explanation', None)
|
||||||
|
if explanation is None:
|
||||||
|
explanation = errors_explanation.get(code)
|
||||||
|
explanation = ctx._(explanation) if explanation is not None else response.explanation
|
||||||
|
message = kw.pop('message', None)
|
||||||
|
if message is None:
|
||||||
|
message = errors_message.get(code)
|
||||||
|
if message is not None:
|
||||||
|
message = ctx._(message)
|
||||||
|
comment = kw.pop('comment', None)
|
||||||
|
if isinstance(comment, dict):
|
||||||
|
comment = tags.ul(u'{0} : {1}'.format(key, value) for key, value in comment.iteritems())
|
||||||
|
elif isinstance(comment, list):
|
||||||
|
comment = tags.ul(comment)
|
||||||
|
title = kw.pop('title', None)
|
||||||
|
if title is None:
|
||||||
|
title = errors_title.get(code)
|
||||||
|
title = ctx._(title) if title is not None else response.status
|
||||||
|
body = templates.render(ctx, template_path,
|
||||||
|
comment=comment,
|
||||||
|
explanation=explanation,
|
||||||
|
message=message,
|
||||||
|
response=response,
|
||||||
|
title=title,
|
||||||
|
**kw)
|
||||||
|
response.body = body.encode('utf-8') if isinstance(body, unicode) else body
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def forbidden(ctx, **kw):
|
||||||
|
return error(ctx, 403, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def method_not_allowed(ctx, **kw):
|
||||||
|
return error(ctx, 405, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def no_content(ctx, headers=None):
|
||||||
|
return error(ctx, 204, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
|
def not_found(ctx, **kw):
|
||||||
|
return error(ctx, 404, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def redirect(ctx, code=302, location=None, **kw):
|
||||||
|
assert location is not None
|
||||||
|
location_str = location.encode('utf-8') if isinstance(location, unicode) else location
|
||||||
|
response = webob.exc.status_map[code](headers=kw.pop('headers', None), location=location_str)
|
||||||
|
body = kw.pop('body', None)
|
||||||
|
if body is None:
|
||||||
|
template_path = kw.pop('template_path', '/http-error.mako')
|
||||||
|
explanation = kw.pop('explanation', None)
|
||||||
|
if explanation is None:
|
||||||
|
explanation = Markup(u'{0} <a href="{1}">{1}</a>.').format(ctx._(u"You'll be redirected to page"), location)
|
||||||
|
message = kw.pop('message', None)
|
||||||
|
if message is None:
|
||||||
|
message = errors_message.get(code)
|
||||||
|
if message is not None:
|
||||||
|
message = ctx._(message)
|
||||||
|
title = kw.pop('title', None)
|
||||||
|
if title is None:
|
||||||
|
title = ctx._("Redirection in progress...")
|
||||||
|
body = templates.render(ctx, template_path,
|
||||||
|
comment=kw.pop('comment', None),
|
||||||
|
explanation=explanation,
|
||||||
|
message=message,
|
||||||
|
response=response,
|
||||||
|
title=title,
|
||||||
|
**kw)
|
||||||
|
response.body = body.encode('utf-8') if isinstance(body, unicode) else body
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def respond_json(ctx, data, code=None, headers=None, jsonp=None):
|
||||||
|
"""Return a JSON response.
|
||||||
|
|
||||||
|
This function is optimized for JSON following
|
||||||
|
`Google JSON Style Guide <http://google-styleguide.googlecode.com/svn/trunk/jsoncstyleguide.xml>`_, but will handle
|
||||||
|
any JSON except for HTTP errors.
|
||||||
|
"""
|
||||||
|
if isinstance(data, collections.Mapping):
|
||||||
|
# Remove null properties as recommended by Google JSON Style Guide.
|
||||||
|
data = discard_empty_items(data)
|
||||||
|
error = data.get('error')
|
||||||
|
else:
|
||||||
|
error = None
|
||||||
|
if headers is None:
|
||||||
|
headers = []
|
||||||
|
if jsonp:
|
||||||
|
headers.append(('Content-Type', 'application/javascript; charset=utf-8'))
|
||||||
|
else:
|
||||||
|
headers.append(('Content-Type', 'application/json; charset=utf-8'))
|
||||||
|
if error:
|
||||||
|
code = code or error['code']
|
||||||
|
assert isinstance(code, int)
|
||||||
|
response = webob.exc.status_map[code](headers=headers)
|
||||||
|
if error.get('code') is None:
|
||||||
|
error['code'] = code
|
||||||
|
if error.get('message') is None:
|
||||||
|
error['message'] = response.title
|
||||||
|
else:
|
||||||
|
response = ctx.req.response
|
||||||
|
if code is not None:
|
||||||
|
response.status = code
|
||||||
|
response.headers.update(headers)
|
||||||
|
text = unicode(json.dumps(data, encoding='utf-8', ensure_ascii=False, indent=2, sort_keys=True))
|
||||||
|
if jsonp:
|
||||||
|
text = u'{0}({1})'.format(jsonp, text)
|
||||||
|
response.text = text
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def unauthorized(ctx, **kw):
|
||||||
|
return error(ctx, 401, **kw)
|
40
setup.py
Executable file
40
setup.py
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
"""MyCO Server web application."""
|
||||||
|
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
doc_lines = __doc__.split('\n')
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
author=u'Benjamin Renard',
|
||||||
|
author_email=u'brenard@zionetrix.net',
|
||||||
|
description=doc_lines[0],
|
||||||
|
entry_points="""
|
||||||
|
[paste.app_factory]
|
||||||
|
main = mycoserver.application:make_app
|
||||||
|
""",
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=[
|
||||||
|
'Biryani1 >= 0.9dev',
|
||||||
|
'MarkupSafe >= 0.15',
|
||||||
|
'WebError >= 0.10',
|
||||||
|
'WebHelpers >= 1.3',
|
||||||
|
'WebOb >= 1.1',
|
||||||
|
],
|
||||||
|
# keywords='',
|
||||||
|
# license=u'http://www.fsf.org/licensing/licenses/agpl-3.0.html',
|
||||||
|
long_description='\n'.join(doc_lines[2:]),
|
||||||
|
name=u'MyCoServer',
|
||||||
|
packages=find_packages(),
|
||||||
|
paster_plugins=['PasteScript'],
|
||||||
|
setup_requires=['PasteScript >= 1.6.3'],
|
||||||
|
# url=u'',
|
||||||
|
version='0.1',
|
||||||
|
zip_safe=False,
|
||||||
|
)
|
Loading…
Reference in a new issue