Merge branch 'master' of github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2012-05-22 00:03:14 +02:00
10 changed files with 247 additions and 117 deletions
+1 -1
View File
@@ -1 +1 @@
Version 2.00.0 (2012-05-16 11:15:52) dev Version 2.00.0 (2012-05-21 16:49:59) dev
+116 -6
View File
@@ -283,6 +283,12 @@ if not 'google' in drivers:
except ImportError: except ImportError:
logger.debug('no MSSQL/DB2/Teradata driver') logger.debug('no MSSQL/DB2/Teradata driver')
try:
import Sybase
drivers.append('Sybase')
except ImportError:
logger.debug('no Sybase driver')
try: try:
import kinterbasdb import kinterbasdb
drivers.append('Interbase') drivers.append('Interbase')
@@ -1124,7 +1130,9 @@ class BaseAdapter(ConnectionPool):
elif not isinstance(expression.op, str): elif not isinstance(expression.op, str):
return expression.op() return expression.op()
else: else:
return '(%s)' % expression.op op = expression.op
if op.endswith(';'): op=op[:-1]
return '(%s)' % op
elif field_type: elif field_type:
return str(self.represent(expression,field_type)) return str(self.represent(expression,field_type))
elif isinstance(expression,(list,tuple)): elif isinstance(expression,(list,tuple)):
@@ -2747,6 +2755,101 @@ class MSSQL2Adapter(MSSQLAdapter):
def execute(self,a): def execute(self,a):
return self.log_execute(a.decode('utf8')) return self.log_execute(a.decode('utf8'))
class SybaseAdapter(MSSQLAdapter):
driver = globals().get('Sybase',None)
types = {
'boolean': 'BIT',
'string': 'CHAR VARYING(%(length)s)',
'text': 'TEXT',
'password': 'CHAR VARYING(%(length)s)',
'blob': 'IMAGE',
'upload': 'CHAR VARYING(%(length)s)',
'integer': 'INT',
'bigint': 'BIGINT',
'float': 'FLOAT',
'double': 'FLOAT',
'decimal': 'NUMERIC(%(precision)s,%(scale)s)',
'date': 'DATETIME',
'time': 'CHAR(8)',
'datetime': 'DATETIME',
'id': 'INT IDENTITY PRIMARY KEY',
'reference': 'INT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
'list:integer': 'TEXT',
'list:string': 'TEXT',
'list:reference': 'TEXT',
'geometry': 'geometry',
'geography': 'geography',
'big-id': 'BIGINT IDENTITY PRIMARY KEY',
'big-reference': 'BIGINT NULL, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
}
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
credential_decoder=lambda x:x, driver_args={},
adapter_args={}, fake_connect=False, srid=4326):
### Fix this for sybase
if not self.driver:
raise RuntimeError, "Unable to import driver"
self.db = db
self.dbengine = "sybase"
self.uri = uri
self.pool_size = pool_size
self.folder = folder
self.db_codec = db_codec
self.srid = srid
self.find_or_make_work_folder()
# ## read: http://bytes.com/groups/python/460325-cx_oracle-utf8
uri = uri.split('://')[1]
if '@' not in uri:
try:
m = re.compile('^(?P<dsn>.+)$').match(uri)
if not m:
raise SyntaxError, \
'Parsing uri string(%s) has no result' % self.uri
dsn = m.group('dsn')
if not dsn:
raise SyntaxError, 'DSN required'
except SyntaxError, e:
logger.error('NdGpatch error')
raise e
else:
m = re.compile('^(?P<user>[^:@]+)(\:(?P<password>[^@]*))?@(?P<host>[^\:/]+)(\:(?P<port>[0-9]+))?/(?P<db>[^\?]+)(\?(?P<urlargs>.*))?$').match(uri)
if not m:
raise SyntaxError, \
"Invalid URI string in DAL: %s" % uri
user = credential_decoder(m.group('user'))
if not user:
raise SyntaxError, 'User required'
password = credential_decoder(m.group('password'))
if not password:
password = ''
host = m.group('host')
if not host:
raise SyntaxError, 'Host name required'
db = m.group('db')
if not db:
raise SyntaxError, 'Database name required'
port = m.group('port') or '1433'
dsn = 'sybase:host=%s:%s;dbname=%s' % (host,port,db)
driver_args.update(dict(user = credential_decoder(user),
password = credential_decoder(password),
locale = charset))
def connect(dsn=dsn,driver_args=driver_args):
return self.driver.connect(dsn,**driver_args)
if not fake_connect:
self.pool_connection(connect)
self.after_connection()
def integrity_error_class(self):
return RuntimeError # FIX THIS
class FireBirdAdapter(BaseAdapter): class FireBirdAdapter(BaseAdapter):
@@ -5760,6 +5863,7 @@ ADAPTERS = {
'oracle': OracleAdapter, 'oracle': OracleAdapter,
'mssql': MSSQLAdapter, 'mssql': MSSQLAdapter,
'mssql2': MSSQL2Adapter, 'mssql2': MSSQL2Adapter,
'sybase': SybaseAdapter,
'db2': DB2Adapter, 'db2': DB2Adapter,
'teradata': TeradataAdapter, 'teradata': TeradataAdapter,
'informix': InformixAdapter, 'informix': InformixAdapter,
@@ -6062,7 +6166,8 @@ def smart_query(fields,text):
value = constants[item[1:]] value = constants[item[1:]]
else: else:
value = item value = item
if op == '=': op = 'like' if field.type in ('text','string'):
if op == '=': op = 'like'
if op == '=': new_query = field==value if op == '=': new_query = field==value
elif op == '<': new_query = field<value elif op == '<': new_query = field<value
elif op == '>': new_query = field>value elif op == '>': new_query = field>value
@@ -6512,7 +6617,8 @@ def index():
except ValueError: except ValueError:
return Row({'status':400,'pattern':pattern, return Row({'status':400,'pattern':pattern,
'error':'invalid path','response':None}) 'error':'invalid path','response':None})
return Row({'status':200,'response':response,'pattern':pattern}) return Row({'status':200,'response':response,
'pattern':pattern,'count':count})
return Row({'status':400,'error':'no matching pattern','response':None}) return Row({'status':400,'error':'no matching pattern','response':None})
@@ -7376,6 +7482,9 @@ class Expression(object):
def len(self): def len(self):
return Expression(self.db, self.db._adapter.AGGREGATE, self, 'LENGTH', 'integer') return Expression(self.db, self.db._adapter.AGGREGATE, self, 'LENGTH', 'integer')
def avg(self):
return Expression(self.db, self.db._adapter.AGGREGATE, self, 'AVG', self.type)
def lower(self): def lower(self):
return Expression(self.db, self.db._adapter.LOWER, self, None, self.type) return Expression(self.db, self.db._adapter.LOWER, self, None, self.type)
@@ -7874,8 +7983,6 @@ class Field(Expression):
return '<no table>.%s' % self.name return '<no table>.%s' % self.name
def raw(s): return Expression(None,s)
class Query(object): class Query(object):
""" """
@@ -7964,7 +8071,7 @@ class Set(object):
if isinstance(query,Table): if isinstance(query,Table):
query = query._id>0 query = query._id>0
elif isinstance(query,str): elif isinstance(query,str):
query = raw(query) query = Expression(self.db,query)
elif isinstance(query,Field): elif isinstance(query,Field):
query = query!=None query = query!=None
if self.query: if self.query:
@@ -8002,6 +8109,9 @@ class Set(object):
fields = adapter.expand_all(fields, adapter.tables(self.query)) fields = adapter.expand_all(fields, adapter.tables(self.query))
return adapter.select(self.query,fields,attributes) return adapter.select(self.query,fields,attributes)
def nested_select(self,*fields,**attributes):
return Expression(self.db,self._select(*fields,**attributes))
def delete(self): def delete(self):
tablename=self.db._adapter.get_table(self.query) tablename=self.db._adapter.get_table(self.query)
table = self.db[tablename] table = self.db[tablename]
+5 -4
View File
@@ -2150,10 +2150,11 @@ class MENU(DIV):
if not select: if not select:
select = SELECT(**self.attributes) select = SELECT(**self.attributes)
for item in data: for item in data:
if item[2]: if len(item) <= 4 or item[4] == True:
select.append(OPTION(CAT(prefix, item[0]), _value=item[2], _selected=item[1])) if item[2]:
if len(item)>3 and len(item[3]): select.append(OPTION(CAT(prefix, item[0]), _value=item[2], _selected=item[1]))
self.serialize_mobile(item[3], select, prefix = CAT(prefix, item[0], '/')) if len(item)>3 and len(item[3]):
self.serialize_mobile(item[3], select, prefix = CAT(prefix, item[0], '/'))
select['_onchange'] = 'window.location=this.value' select['_onchange'] = 'window.location=this.value'
return select return select
+7
View File
@@ -68,6 +68,13 @@ create_missing_folders()
# set up logging for subsequent imports # set up logging for subsequent imports
import logging import logging
import logging.config import logging.config
# This needed to prevent exception on Python 2.5:
# NameError: name 'gluon' is not defined
# See http://bugs.python.org/issue1436
import gluon.messageboxhandler
logging.gluon = gluon
logpath = abspath("logging.conf") logpath = abspath("logging.conf")
if os.path.exists(logpath): if os.path.exists(logpath):
logging.config.fileConfig(abspath("logging.conf")) logging.config.fileConfig(abspath("logging.conf"))
+4 -1
View File
@@ -1016,13 +1016,16 @@ class MapUrlIn(object):
self.request.args = self.args self.request.args = self.args
if self.language: if self.language:
self.request.uri_language = self.language self.request.uri_language = self.language
uri = '/%s/%s/%s' % (self.application, self.controller, self.function) uri = '/%s/%s' % (self.controller, self.function)
app = self.application
if self.map_hyphen: if self.map_hyphen:
uri = uri.replace('_', '-') uri = uri.replace('_', '-')
app = app.replace('_', '-')
if self.extension != 'html': if self.extension != 'html':
uri += '.' + self.extension uri += '.' + self.extension
if self.language: if self.language:
uri = '/%s%s' % (self.language, uri) uri = '/%s%s' % (self.language, uri)
uri = '/%s%s' % (app, uri)
uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or '' uri += self.args and urllib.quote('/' + '/'.join([str(x) for x in self.args])) or ''
uri += (self.query and ('?' + self.query) or '') uri += (self.query and ('?' + self.query) or '')
self.env['REQUEST_URI'] = uri self.env['REQUEST_URI'] = uri
+18 -18
View File
@@ -1323,7 +1323,7 @@ class SQLFORM(FORM):
def smartdictform(session,name,filename=None,query=None,**kwargs): def smartdictform(session,name,filename=None,query=None,**kwargs):
import os import os
if query: if query:
session[name] = db(query).select().first().as_dict() session[name] = query.db(query).select().first().as_dict()
elif os.path.exists(filename): elif os.path.exists(filename):
env = {'datetime':datetime} env = {'datetime':datetime}
session[name] = eval(open(filename).read(),{},env) session[name] = eval(open(filename).read(),{},env)
@@ -1331,7 +1331,7 @@ class SQLFORM(FORM):
if form.process().accepted: if form.process().accepted:
session[name].update(form.vars) session[name].update(form.vars)
if query: if query:
db(query).update(**form.vars) query.db(query).update(**form.vars)
else: else:
open(filename,'w').write(repr(session[name])) open(filename,'w').write(repr(session[name]))
return form return form
@@ -1546,27 +1546,27 @@ class SQLFORM(FORM):
buttonurl=url(args=[]),callback=None,delete=None,trap=True): buttonurl=url(args=[]),callback=None,delete=None,trap=True):
if showbuttontext: if showbuttontext:
if callback: if callback:
return A(SPAN(_class=ui.get(buttonclass,'')), return A(SPAN(_class=ui.get(buttonclass)),
SPAN(T(buttontext),_title=buttontext, SPAN(T(buttontext),_title=buttontext,
_class=ui.get('buttontext','')), _class=ui.get('buttontext')),
callback=callback,delete=delete, callback=callback,delete=delete,
_class=trap_class(ui.get('button',''),trap)) _class=trap_class(ui.get('button'),trap))
else: else:
return A(SPAN(_class=ui.get(buttonclass,'')), return A(SPAN(_class=ui.get(buttonclass)),
SPAN(T(buttontext),_title=buttontext, SPAN(T(buttontext),_title=buttontext,
_class=ui.get('buttontext','')), _class=ui.get('buttontext')),
_href=buttonurl, _href=buttonurl,
_class=trap_class(ui.get('button',''),trap)) _class=trap_class(ui.get('button'),trap))
else: else:
if callback: if callback:
return A(SPAN(_class=ui.get(buttonclass,'')), return A(SPAN(_class=ui.get(buttonclass)),
callback=callback,delete=delete, callback=callback,delete=delete,
_title=buttontext, _title=buttontext,
_class=trap_class(ui.get('buttontext',''),trap)) _class=trap_class(ui.get('buttontext'),trap))
else: else:
return A(SPAN(_class=ui.get(buttonclass,'')), return A(SPAN(_class=ui.get(buttonclass)),
_href=buttonurl,_title=buttontext, _href=buttonurl,_title=buttontext,
_class=trap_class(ui.get('buttontext',''),trap)) _class=trap_class(ui.get('buttontext'),trap))
dbset = db(query) dbset = db(query)
tablenames = db._adapter.tables(dbset.query) tablenames = db._adapter.tables(dbset.query)
if left!=None: tablenames+=db._adapter.tables(left) if left!=None: tablenames+=db._adapter.tables(left)
@@ -1772,9 +1772,9 @@ class SQLFORM(FORM):
else: else:
orderby = (order[:1]=='~' and ~sort_field) or sort_field orderby = (order[:1]=='~' and ~sort_field) or sort_field
head = TR(_class=ui.get('header','')) head = TR(_class=ui.get('header'))
if selectable: if selectable:
head.append(TH(_class=ui.get('default',''))) head.append(TH(_class=ui.get('default')))
for field in fields: for field in fields:
if columns and not str(field) in columns: continue if columns and not str(field) in columns: continue
if not field.readable: continue if not field.readable: continue
@@ -1791,19 +1791,19 @@ class SQLFORM(FORM):
header = A(header,marker,_href=url(vars=dict( header = A(header,marker,_href=url(vars=dict(
keywords=request.vars.keywords or '', keywords=request.vars.keywords or '',
order=key)),_class=trap_class()) order=key)),_class=trap_class())
head.append(TH(header, _class=ui.get('default',''))) head.append(TH(header, _class=ui.get('default')))
if links and links_in_grid: if links and links_in_grid:
for link in links: for link in links:
if isinstance(link,dict): if isinstance(link,dict):
head.append(TH(link['header'], _class=ui.get('default',''))) head.append(TH(link['header'], _class=ui.get('default')))
# Include extra column for buttons if needed. # Include extra column for buttons if needed.
include_buttons_column = (details or editable or deletable or include_buttons_column = (details or editable or deletable or
(links and links_in_grid and (links and links_in_grid and
not all([isinstance(link, dict) for link in links]))) not all([isinstance(link, dict) for link in links])))
if include_buttons_column: if include_buttons_column:
head.append(TH(_class=ui.get('default',''))) head.append(TH(_class=ui.get('default')))
paginator = UL() paginator = UL()
if paginate and paginate<nrows: if paginate and paginate<nrows:
@@ -1938,7 +1938,7 @@ class SQLFORM(FORM):
DIV(htmltable,_class="web2py_table"), DIV(htmltable,_class="web2py_table"),
DIV(paginator,_class=\ DIV(paginator,_class=\
"web2py_paginator %(header)s %(cornerbottom)s" % ui), "web2py_paginator %(header)s %(cornerbottom)s" % ui),
_class='%s %s' % (_class, ui.get('widget',''))) _class='%s %s' % (_class, ui.get('widget')))
res.create_form = create_form res.create_form = create_form
res.update_form = update_form res.update_form = update_form
res.view_form = view_form res.view_form = view_form
+2 -2
View File
@@ -47,8 +47,8 @@ class SuperNode(Node):
if self.value: if self.value:
return str(self.value) return str(self.value)
else: else:
raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + \ # raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
"You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." ) return ''
def __repr__(self): def __repr__(self):
return "%s->%s" % (self.name, self.value) return "%s->%s" % (self.name, self.value)
+3 -3
View File
@@ -646,7 +646,7 @@ class Mail(object):
server.ehlo() server.ehlo()
server.starttls() server.starttls()
server.ehlo() server.ehlo()
if not self.settings.login is None: if not self.settings.login:
server.login(*self.settings.login.split(':',1)) server.login(*self.settings.login.split(':',1))
result = server.sendmail(self.settings.sender, to, payload.as_string()) result = server.sendmail(self.settings.sender, to, payload.as_string())
server.quit() server.quit()
@@ -861,14 +861,14 @@ class Auth(object):
""" """
@staticmethod @staticmethod
def get_or_create_key(filename=None): def get_or_create_key(filename=None, alg='sha512'):
request = current.request request = current.request
if not filename: if not filename:
filename = os.path.join(request.folder,'private','auth.key') filename = os.path.join(request.folder,'private','auth.key')
if os.path.exists(filename): if os.path.exists(filename):
key = open(filename,'r').read().strip() key = open(filename,'r').read().strip()
else: else:
key = web2py_uuid() key = alg+':'+web2py_uuid()
open(filename,'w').write(key) open(filename,'w').write(key)
return key return key
+12 -1
View File
@@ -896,7 +896,7 @@ def start(cron=True):
if hasattr(options,key): if hasattr(options,key):
setattr(options,key,getattr(options2,key)) setattr(options,key,getattr(options2,key))
if not os.path.exists('logging.conf') and \ if False and not os.path.exists('logging.conf') and \
os.path.exists('logging.example.conf'): os.path.exists('logging.example.conf'):
import shutil import shutil
sys.stdout.write("Copying logging.conf.example to logging.conf ... ") sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
@@ -983,6 +983,17 @@ def start(cron=True):
if root: if root:
root.focus_force() root.focus_force()
# Mac OS X - make the GUI window rise to the top
if os.path.exists("/usr/bin/osascript"):
applescript = """
tell application "System Events"
set proc to first process whose unix id is %d
set frontmost of proc to true
end tell
""" % (os.getpid())
os.system("/usr/bin/osascript -e '%s'" % applescript)
if not options.quiet: if not options.quiet:
presentation(root) presentation(root)
master = web2pyDialog(root, options) master = web2pyDialog(root, options)
+79 -81
View File
@@ -1,81 +1,79 @@
#!/bin/bash #!/bin/bash
echo 'setup-web2py-nginx-uwsgi-ubuntu.sh' echo 'setup-web2py-nginx-uwsgi-ubuntu-precise.sh'
echo 'Requires Ubuntu 10.04 (LTS) and installs Nginx + uWSGI + Web2py' echo 'Requires Ubuntu 12.04 and installs Nginx + uWSGI + Web2py'
# Get Web2py Admin Password # Get Web2py Admin Password
echo -e "Web2py Admin Password: \c " echo -e "Web2py Admin Password: \c "
read PW read PW
# Upgrade and install needed software # Upgrade and install needed software
apt-get update apt-get update
apt-get -y upgrade apt-get -y upgrade
apt-get install python-software-properties apt-get -y dist-upgrade
add-apt-repository ppa:nginx/stable apt-get autoremove
add-apt-repository ppa:uwsgi/release apt-get autoclean
apt-get update apt-get -y install nginx-full
apt-get -y install nginx-full apt-get -y install uwsgi uwsgi-plugin-python
apt-get -y install uwsgi-python
# Create configuration file /etc/nginx/sites-available/web2py
# Create configuration file /etc/nginx/sites-available/web2py echo 'server {
echo 'server { listen 80;
listen 80; server_name $hostname;
server_name $hostname; location ~* /(\w+)/static/ {
location ~* /(\w+)/static/ { root /home/www-data/web2py/applications/;
root /home/www-data/web2py/applications/; }
} location / {
location / { uwsgi_pass 127.0.0.1:9001;
uwsgi_pass 127.0.0.1:9001; include uwsgi_params;
include uwsgi_params; uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param UWSGI_SCHEME $scheme; uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version; }
} }
}
server {
server { listen 443;
listen 443; server_name $hostname;
server_name $hostname; ssl on;
ssl on; ssl_certificate /etc/nginx/ssl/web2py.crt;
ssl_certificate /etc/nginx/ssl/web2py.crt; ssl_certificate_key /etc/nginx/ssl/web2py.key;
ssl_certificate_key /etc/nginx/ssl/web2py.key; location / {
location / { uwsgi_pass 127.0.0.1:9001;
uwsgi_pass 127.0.0.1:9001; include uwsgi_params;
include uwsgi_params; uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param UWSGI_SCHEME $scheme; uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version; }
}
}' >/etc/nginx/sites-available/web2py
}' >/etc/nginx/sites-available/web2py
ln -s /etc/nginx/sites-available/web2py /etc/nginx/sites-enabled/web2py
ln -s /etc/nginx/sites-available/web2py /etc/nginx/sites-enabled/web2py rm /etc/nginx/sites-enabled/default
rm /etc/nginx/sites-enabled/default mkdir /etc/nginx/ssl
rm /etc/nginx/sites-available/default cd /etc/nginx/ssl
mkdir /etc/nginx/ssl openssl genrsa -out web2py.key 1024
cd /etc/nginx/ssl openssl req -batch -new -key web2py.key -out web2py.csr
openssl genrsa -out web2py.key 1024 openssl x509 -req -days 1780 -in web2py.csr -signkey web2py.key -out web2py.crt
openssl req -batch -new -key web2py.key -out web2py.csr
openssl x509 -req -days 1780 -in web2py.csr -signkey web2py.key -out web2py.crt # Create configuration file /etc/uwsgi/apps-available/web2py.xml
echo '<uwsgi>
# Create configuration file /etc/uwsgi-python/apps-available/web2py.xml <plugin>python</plugin>
echo '<uwsgi> <socket>127.0.0.1:9001</socket>
<socket>127.0.0.1:9001</socket> <pythonpath>/home/www-data/web2py/</pythonpath>
<pythonpath>/home/www-data/web2py/</pythonpath> <app mountpoint="/">
<app mountpoint="/"> <script>wsgihandler</script>
<script>wsgihandler</script> </app>
</app> </uwsgi>' >/etc/uwsgi/apps-available/web2py.xml
</uwsgi>' >/etc/uwsgi-python/apps-available/web2py.xml ln -s /etc/uwsgi/apps-available/web2py.xml /etc/uwsgi/apps-enabled/web2py.xml
ln -s /etc/uwsgi-python/apps-available/web2py.xml /etc/uwsgi-python/apps-enabled/web2py.xml
# Install Web2py
# Install Web2py apt-get -y install unzip
apt-get -y install unzip mkdir /home/www-data
cd /home cd /home/www-data
mkdir www-data wget http://web2py.com/examples/static/web2py_src.zip
cd www-data unzip web2py_src.zip
wget http://web2py.com/examples/static/web2py_src.zip rm web2py_src.zip
unzip web2py_src.zip chown -R www-data:www-data web2py
rm web2py_src.zip cd /home/www-data/web2py
chown -R www-data:www-data web2py sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
cd /home/www-data/web2py /etc/init.d/uwsgi restart
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)" /etc/init.d/nginx restart
/etc/init.d/uwsgi-python restart
/etc/init.d/nginx restart