Merge github.com:web2py/web2py
This commit is contained in:
@@ -121,7 +121,6 @@ commit:
|
||||
push:
|
||||
hg push
|
||||
git push
|
||||
git push mdipierro
|
||||
tag:
|
||||
git tag -l '$(S)'
|
||||
hg tag -l '$(S)'
|
||||
|
||||
@@ -1 +1 @@
|
||||
Version 2.4.1-alpha.2+timestamp.2012.12.28.01.21.20
|
||||
Version 2.4.1-alpha.2+timestamp.2013.01.09.16.56.34
|
||||
|
||||
@@ -163,6 +163,22 @@ class Servers:
|
||||
"tcp://127.0.0.1:9996")
|
||||
mongrel2_handler(app, conn, debug=False)
|
||||
|
||||
@staticmethod
|
||||
def motor(app, address, **options):
|
||||
#https://github.com/rpedroso/motor
|
||||
import motor
|
||||
app = motor.WSGIContainer(app)
|
||||
http_server = motor.HTTPServer(app)
|
||||
http_server.listen(address=address[0], port=address[1])
|
||||
#http_server.start(2)
|
||||
motor.IOLoop.instance().start()
|
||||
|
||||
@staticmethod
|
||||
def pulsar(app, address, **options):
|
||||
from pulsar.apps import wsgi
|
||||
sys.argv = ['anyserver.py']
|
||||
s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address)
|
||||
s.start()
|
||||
|
||||
def run(servername, ip, port, softcron=True, logging=False, profiler=None):
|
||||
if logging:
|
||||
|
||||
@@ -212,3 +212,26 @@ def toggle_breakpoint():
|
||||
except Exception, e:
|
||||
session.flash = str(e)
|
||||
return response.json({'ok': ok, 'lineno': lineno})
|
||||
|
||||
def list_breakpoints():
|
||||
"Return a list of linenumbers for current breakpoints"
|
||||
|
||||
breakpoints = []
|
||||
ok = None
|
||||
try:
|
||||
filename = os.path.join(request.env['applications_parent'],
|
||||
'applications', request.vars.filename)
|
||||
# normalize path name: replace slashes, references, etc...
|
||||
filename = os.path.normpath(os.path.normcase(filename))
|
||||
for bp in qdb_debugger.do_list_breakpoint():
|
||||
no, bp_filename, bp_lineno, temporary, enabled, hits, cond = bp
|
||||
# normalize path name: replace slashes, references, etc...
|
||||
bp_filename = os.path.normpath(os.path.normcase(bp_filename))
|
||||
if filename == bp_filename:
|
||||
breakpoints.append(bp_lineno)
|
||||
ok = True
|
||||
except Exception, e:
|
||||
session.flash = str(e)
|
||||
ok = False
|
||||
return response.json({'ok': ok, 'breakpoints': breakpoints})
|
||||
|
||||
|
||||
@@ -1493,7 +1493,9 @@ def errors():
|
||||
|
||||
else:
|
||||
for item in request.vars:
|
||||
if item[:7] == 'delete_':
|
||||
# delete_all} rows doesn't contain any ticket
|
||||
# Remove anything else as requested
|
||||
if item[:7] == 'delete_' and (not item == "delete_all}"):
|
||||
os.unlink(apath('%s/errors/%s' % (app, item[7:]), r=request))
|
||||
func = lambda p: os.stat(apath('%s/errors/%s' %
|
||||
(app, p), r=request)).st_mtime
|
||||
|
||||
@@ -148,8 +148,12 @@ function getSelectionRange() {
|
||||
return sel;
|
||||
}
|
||||
|
||||
function doToggleBreakpoint(filename, url) {
|
||||
var sel = getSelectionRange();
|
||||
function doToggleBreakpoint(filename, url, sel) {
|
||||
if (sel==null) {
|
||||
// use cursor position to determine the breakpoint line
|
||||
// (gutter already tell us the selected line)
|
||||
sel = getSelectionRange();
|
||||
}
|
||||
var dataForPost = prepareMultiPartPOST(new Array(
|
||||
prepareDataForSave('filename', filename),
|
||||
prepareDataForSave('sel_start', sel["start"]),
|
||||
@@ -199,6 +203,43 @@ function doToggleBreakpoint(filename, url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// on load, update all breakpoints markers:
|
||||
function doListBreakpoints(filename, url) {
|
||||
var dataForPost = prepareMultiPartPOST(new Array(
|
||||
prepareDataForSave('filename', filename)
|
||||
));
|
||||
jQuery.ajax({
|
||||
type: "POST",
|
||||
contentType: 'multipart/form-data;boundary="'+dataForPost[1]+'"',
|
||||
url: url,
|
||||
dataType: "json",
|
||||
data: dataForPost[0],
|
||||
timeout: 5000,
|
||||
beforeSend: function(xhr) {
|
||||
xhr.setRequestHeader('web2py-component-location',
|
||||
document.location);
|
||||
xhr.setRequestHeader('web2py-component-element',
|
||||
'doListBreakpoints');},
|
||||
success: function(json,text,xhr){
|
||||
try {
|
||||
if (json.error) {
|
||||
window.location.href=json.redirect;
|
||||
} else {
|
||||
if (window.mirror) {
|
||||
for (i in json.breakpoints) {
|
||||
lineno = json.breakpoints[i];
|
||||
// mark the breakpoint if ok=True
|
||||
editor.setMarker(lineno-1,
|
||||
"<span style='color: red'>●</span> %N%");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) { on_error(); }
|
||||
},
|
||||
error: function(json) { on_error(); }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function keepalive(url) {
|
||||
jQuery.ajax({
|
||||
|
||||
@@ -44,12 +44,25 @@ function web2py_event_handlers() {
|
||||
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
|
||||
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
|
||||
doc.on('click', "input[type='checkbox'].delete", function(){if(this.checked) if(!confirm(confirm_message)) this.checked=false;});
|
||||
|
||||
doc.ajaxSuccess(function(e, xhr) {
|
||||
var redirect=xhr.getResponseHeader('web2py-redirect-location');
|
||||
if (redirect != null) {
|
||||
var command=xhr.getResponseHeader('web2py-component-command');
|
||||
var flash=xhr.getResponseHeader('web2py-component-flash');
|
||||
if (redirect !== null) {
|
||||
window.location = redirect;
|
||||
};
|
||||
if(command !== null){
|
||||
eval(decodeURIComponent(command));
|
||||
}
|
||||
if(flash) {
|
||||
jQuery('.flash')
|
||||
.html(decodeURIComponent(flash))
|
||||
.append('<span id="closeflash">×</span>')
|
||||
.slideDown();
|
||||
}
|
||||
});
|
||||
|
||||
doc.ajaxError(function(e, xhr, settings, exception) {
|
||||
doc.off('click', '.flash')
|
||||
switch(xhr.status){
|
||||
@@ -98,8 +111,6 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
'complete':function(xhr,text){
|
||||
var html=xhr.responseText;
|
||||
var content=xhr.getResponseHeader('web2py-component-content');
|
||||
var command=xhr.getResponseHeader('web2py-component-command');
|
||||
var flash=xhr.getResponseHeader('web2py-component-flash');
|
||||
var t = jQuery('#'+target);
|
||||
if(content=='prepend') t.prepend(html);
|
||||
else if(content=='append') t.append(html);
|
||||
@@ -107,14 +118,6 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
web2py_trap_form(action,target);
|
||||
web2py_trap_link(target);
|
||||
web2py_ajax_init('#'+target);
|
||||
if(command)
|
||||
eval(decodeURIComponent(command));
|
||||
if(flash) {
|
||||
jQuery('.flash')
|
||||
.html(decodeURIComponent(flash))
|
||||
.append('<span id="closeflash">×</span>')
|
||||
.slideDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
<link rel="stylesheet" href="{{=cm}}/lib/util/dialog.css">
|
||||
<script src="{{=cm}}/emmet.min.js"></script>
|
||||
<script language="Javascript" type="text/javascript" src="{{=URL('static','js/ajax_editor.js')}}"></script>
|
||||
<script language="Javascript" type="text/javascript">
|
||||
jQuery(document).ready(function(){
|
||||
doListBreakpoints({{=XML("'%s','%s://%s%s'" % (filename,
|
||||
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||
URL(c='debug', f='list_breakpoints')))}});
|
||||
});
|
||||
</script>
|
||||
{{elif TEXT_EDITOR == 'ace':}}
|
||||
<script src="{{=URL(r=request,c='static',f='ace/src/ace.js')}}" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="{{=URL(r=request,c='static',f='ace/src/theme-%s.js' % TEXT_EDITOR_THEME)}}" type="text/javascript" charset="utf-8"></script>
|
||||
@@ -103,7 +110,7 @@ jQuery(document).ready(function(){
|
||||
{{if filetype=='python':}}
|
||||
{{=A(SPAN(T('toggle breakpoint')),
|
||||
_value="breakpoint", _name="breakpoint",
|
||||
_onclick="return doToggleBreakpoint('%s','%s://%s%s');" % (filename,
|
||||
_onclick="return doToggleBreakpoint('%s','%s://%s%s',null);" % (filename,
|
||||
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||
URL(c='debug', f='toggle_breakpoint')),
|
||||
_class="button special btn btn-inverse")}}
|
||||
@@ -202,7 +209,13 @@ jQuery(document).ready(function(){
|
||||
autofocus: true,
|
||||
onCursorActivity: function() {
|
||||
editor.setLineClass(hlLine, null, null);
|
||||
hlLine = editor.setLineClass(editor.getCursor().line, null, "activeline");}
|
||||
hlLine = editor.setLineClass(editor.getCursor().line, null, "activeline");},
|
||||
onGutterClick: function(cm, n) {
|
||||
sel = {start: n, end: n, data: ''};
|
||||
doToggleBreakpoint({{=XML("'%s','%s://%s%s',sel" % (filename,
|
||||
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||
URL(c='debug', f='toggle_breakpoint')))}});
|
||||
}
|
||||
};
|
||||
var editor = CodeMirror.fromTextArea(
|
||||
document.getElementById("body"),cm_opts);
|
||||
@@ -293,4 +306,4 @@ window.onload = function() {
|
||||
{{pass}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- end "edit" block -->
|
||||
<!-- end "edit" block -->
|
||||
|
||||
@@ -44,12 +44,25 @@ function web2py_event_handlers() {
|
||||
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
|
||||
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
|
||||
doc.on('click', "input[type='checkbox'].delete", function(){if(this.checked) if(!confirm(confirm_message)) this.checked=false;});
|
||||
|
||||
doc.ajaxSuccess(function(e, xhr) {
|
||||
var redirect=xhr.getResponseHeader('web2py-redirect-location');
|
||||
if (redirect != null) {
|
||||
var command=xhr.getResponseHeader('web2py-component-command');
|
||||
var flash=xhr.getResponseHeader('web2py-component-flash');
|
||||
if (redirect !== null) {
|
||||
window.location = redirect;
|
||||
};
|
||||
if(command !== null){
|
||||
eval(decodeURIComponent(command));
|
||||
}
|
||||
if(flash) {
|
||||
jQuery('.flash')
|
||||
.html(decodeURIComponent(flash))
|
||||
.append('<span id="closeflash">×</span>')
|
||||
.slideDown();
|
||||
}
|
||||
});
|
||||
|
||||
doc.ajaxError(function(e, xhr, settings, exception) {
|
||||
doc.off('click', '.flash')
|
||||
switch(xhr.status){
|
||||
@@ -98,8 +111,6 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
'complete':function(xhr,text){
|
||||
var html=xhr.responseText;
|
||||
var content=xhr.getResponseHeader('web2py-component-content');
|
||||
var command=xhr.getResponseHeader('web2py-component-command');
|
||||
var flash=xhr.getResponseHeader('web2py-component-flash');
|
||||
var t = jQuery('#'+target);
|
||||
if(content=='prepend') t.prepend(html);
|
||||
else if(content=='append') t.append(html);
|
||||
@@ -107,14 +118,6 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
web2py_trap_form(action,target);
|
||||
web2py_trap_link(target);
|
||||
web2py_ajax_init('#'+target);
|
||||
if(command)
|
||||
eval(decodeURIComponent(command));
|
||||
if(flash) {
|
||||
jQuery('.flash')
|
||||
.html(decodeURIComponent(flash))
|
||||
.append('<span id="closeflash">×</span>')
|
||||
.slideDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,12 +44,25 @@ function web2py_event_handlers() {
|
||||
doc.on('keyup', 'input.double, input.decimal', function(){this.value=this.value.reverse().replace(/[^0-9\-\.,]|[\-](?=.)|[\.,](?=[0-9]*[\.,])/g,'').reverse();});
|
||||
var confirm_message = (typeof w2p_ajax_confirm_message != 'undefined') ? w2p_ajax_confirm_message : "Are you sure you want to delete this object?";
|
||||
doc.on('click', "input[type='checkbox'].delete", function(){if(this.checked) if(!confirm(confirm_message)) this.checked=false;});
|
||||
|
||||
doc.ajaxSuccess(function(e, xhr) {
|
||||
var redirect=xhr.getResponseHeader('web2py-redirect-location');
|
||||
if (redirect != null) {
|
||||
var command=xhr.getResponseHeader('web2py-component-command');
|
||||
var flash=xhr.getResponseHeader('web2py-component-flash');
|
||||
if (redirect !== null) {
|
||||
window.location = redirect;
|
||||
};
|
||||
if(command !== null){
|
||||
eval(decodeURIComponent(command));
|
||||
}
|
||||
if(flash) {
|
||||
jQuery('.flash')
|
||||
.html(decodeURIComponent(flash))
|
||||
.append('<span id="closeflash">×</span>')
|
||||
.slideDown();
|
||||
}
|
||||
});
|
||||
|
||||
doc.ajaxError(function(e, xhr, settings, exception) {
|
||||
doc.off('click', '.flash')
|
||||
switch(xhr.status){
|
||||
@@ -98,8 +111,6 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
'complete':function(xhr,text){
|
||||
var html=xhr.responseText;
|
||||
var content=xhr.getResponseHeader('web2py-component-content');
|
||||
var command=xhr.getResponseHeader('web2py-component-command');
|
||||
var flash=xhr.getResponseHeader('web2py-component-flash');
|
||||
var t = jQuery('#'+target);
|
||||
if(content=='prepend') t.prepend(html);
|
||||
else if(content=='append') t.append(html);
|
||||
@@ -107,14 +118,6 @@ function web2py_ajax_page(method, action, data, target) {
|
||||
web2py_trap_form(action,target);
|
||||
web2py_trap_link(target);
|
||||
web2py_ajax_init('#'+target);
|
||||
if(command)
|
||||
eval(decodeURIComponent(command));
|
||||
if(flash) {
|
||||
jQuery('.flash')
|
||||
.html(decodeURIComponent(flash))
|
||||
.append('<span id="closeflash">×</span>')
|
||||
.slideDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{{
|
||||
if request.args(0)=='login':
|
||||
if not 'register' in auth.settings.actions_disabled:
|
||||
form.add_button(T('Register'),URL(args='register'),_class='btn')
|
||||
form.add_button(T('Register'),URL(args='register', vars={'_next': request.vars._next} if request.vars._next else None),_class='btn')
|
||||
pass
|
||||
if not 'request_reset_password' in auth.settings.actions_disabled:
|
||||
form.add_button(T('Lost Password'),URL(args='request_reset_password'),_class='btn')
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Web2Py framework modules
|
||||
========================
|
||||
"""
|
||||
|
||||
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
|
||||
__all__ = ['A', 'B', 'BEAUTIFY', 'BODY', 'BR', 'CAT', 'CENTER', 'CLEANUP', 'CODE', 'CRYPT', 'DAL', 'DIV', 'EM', 'EMBED', 'FIELDSET', 'FORM', 'Field', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEAD', 'HR', 'HTML', 'HTTP', 'I', 'IFRAME', 'IMG', 'INPUT', 'IS_ALPHANUMERIC', 'IS_DATE', 'IS_DATETIME', 'IS_DATETIME_IN_RANGE', 'IS_DATE_IN_RANGE', 'IS_DECIMAL_IN_RANGE', 'IS_EMAIL', 'IS_EMPTY_OR', 'IS_EQUAL_TO', 'IS_EXPR', 'IS_FLOAT_IN_RANGE', 'IS_IMAGE', 'IS_JSON', 'IS_INT_IN_RANGE', 'IS_IN_DB', 'IS_IN_SET', 'IS_IPV4', 'IS_LENGTH', 'IS_LIST_OF', 'IS_LOWER', 'IS_MATCH', 'IS_NOT_EMPTY', 'IS_NOT_IN_DB', 'IS_NULL_OR', 'IS_SLUG', 'IS_STRONG', 'IS_TIME', 'IS_UPLOAD_FILENAME', 'IS_UPPER', 'IS_URL', 'LABEL', 'LEGEND', 'LI', 'LINK', 'LOAD', 'MARKMIN', 'MENU', 'META', 'OBJECT', 'OL', 'ON', 'OPTGROUP', 'OPTION', 'P', 'PRE', 'SCRIPT', 'SELECT', 'SPAN', 'SQLFORM', 'SQLTABLE', 'STRONG', 'STYLE', 'TABLE', 'TAG', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', 'TITLE', 'TR', 'TT', 'UL', 'URL', 'XHTML', 'XML', 'redirect', 'current', 'embed64']
|
||||
|
||||
from globals import current
|
||||
from html import *
|
||||
|
||||
@@ -121,13 +121,13 @@ server for requests. It can be used for the optional"scope" parameters for Face
|
||||
"""
|
||||
# Create an OpenerDirector with support
|
||||
# for Basic HTTP Authentication...
|
||||
|
||||
auth_handler = urllib2.HTTPBasicAuthHandler()
|
||||
auth_handler.add_password(None,
|
||||
uri,
|
||||
self.client_id,
|
||||
self.client_secret)
|
||||
opener = urllib2.build_opener(auth_handler)
|
||||
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
password_mgr.add_password(realm=None,
|
||||
uri=uri,
|
||||
user=self.client_id,
|
||||
passwd=self.client_secret)
|
||||
handler = urllib2.HTTPBasicAuthHandler(password_mgr)
|
||||
opener = urllib2.build_opener(handler)
|
||||
return opener
|
||||
|
||||
def accessToken(self):
|
||||
|
||||
@@ -129,18 +129,21 @@ class OpenIDAuth(object):
|
||||
def _define_alt_login_table(self):
|
||||
"""
|
||||
Define the OpenID login table.
|
||||
Note: type is what I used for our project. We're going to support 'fackbook' and
|
||||
'plurk' alternate login methods. Otherwise it's always 'openid' and you
|
||||
Note: oidtype is what I used for our project.
|
||||
We're going to support 'fackbook' and
|
||||
'plurk' alternate login methods.
|
||||
Otherwise it's always 'openid' and you
|
||||
may not need it. This should be easy to changed.
|
||||
(Just remove the field of "type" and remove the
|
||||
"and db.alt_logins.type == type_" in _find_matched_openid function)
|
||||
"and db.alt_logins.oidtype == type_"
|
||||
in _find_matched_openid function)
|
||||
"""
|
||||
db = self.db
|
||||
table = db.define_table(
|
||||
self.table_alt_logins_name,
|
||||
Field('username', length=512, default=''),
|
||||
Field('type', length=128, default='openid', readable=False),
|
||||
Field('user', self.table_user, readable=False),
|
||||
Field('oidtype', length=128, default='openid', readable=False),
|
||||
Field('oiduser', self.table_user, readable=False),
|
||||
)
|
||||
table.username.requires = IS_NOT_IN_DB(db, table.username)
|
||||
self.table_alt_logins = table
|
||||
@@ -213,7 +216,7 @@ class OpenIDAuth(object):
|
||||
|
||||
# Get existed OpenID user
|
||||
user = db(
|
||||
self.table_user.id == alt_login.user).select().first()
|
||||
self.table_user.id == alt_login.oiduser).select().first()
|
||||
if user:
|
||||
if current.session.w2popenid:
|
||||
del(current.session.w2popenid)
|
||||
@@ -230,7 +233,7 @@ class OpenIDAuth(object):
|
||||
Get the matched OpenID for given
|
||||
"""
|
||||
query = (
|
||||
(db.alt_logins.username == oid) & (db.alt_logins.type == type_))
|
||||
(db.alt_logins.username == oid) & (db.alt_logins.oidtype == type_))
|
||||
alt_login = db(query).select().first() # Get the OpenID record
|
||||
return alt_login
|
||||
|
||||
@@ -239,7 +242,7 @@ class OpenIDAuth(object):
|
||||
Associate the user logged in with given OpenID
|
||||
"""
|
||||
# print "[DB] %s authenticated" % oid
|
||||
self.db.alt_logins.insert(username=oid, user=user.id)
|
||||
self.db.alt_logins.insert(username=oid, oiduser=user.id)
|
||||
|
||||
def _form_with_notification(self):
|
||||
"""
|
||||
@@ -400,7 +403,7 @@ width: 400px;
|
||||
if 'delete_openid' in request.vars:
|
||||
self.remove_openid(request.vars.delete_openid)
|
||||
|
||||
query = self.db.alt_logins.user == self.auth.user.id
|
||||
query = self.db.alt_logins.oiduser == self.auth.user.id
|
||||
alt_logins = self.db(query).select()
|
||||
l = []
|
||||
for alt_login in alt_logins:
|
||||
@@ -529,7 +532,7 @@ class Web2pyStore(OpenIDStore):
|
||||
self.database.define_table(self.table_oid_nonces_name,
|
||||
Field('server_url',
|
||||
'string', length=2047, required=True),
|
||||
Field('timestamp',
|
||||
Field('itimestamp',
|
||||
'integer', required=True),
|
||||
Field('salt', 'string',
|
||||
length=40, required=True)
|
||||
@@ -591,12 +594,12 @@ class Web2pyStore(OpenIDStore):
|
||||
db = self.database
|
||||
if abs(timestamp - time.time()) > nonce.SKEW:
|
||||
return False
|
||||
query = (db.oid_nonces.server_url == server_url) & (db.oid_nonces.timestamp == timestamp) & (db.oid_nonces.salt == salt)
|
||||
query = (db.oid_nonces.server_url == server_url) & (db.oid_nonces.itimestamp == timestamp) & (db.oid_nonces.salt == salt)
|
||||
if db(query).count() > 0:
|
||||
return False
|
||||
else:
|
||||
db.oid_nonces.insert(server_url=server_url,
|
||||
timestamp=timestamp,
|
||||
itimestamp=timestamp,
|
||||
salt=salt)
|
||||
return True
|
||||
|
||||
@@ -628,7 +631,7 @@ class Web2pyStore(OpenIDStore):
|
||||
"""
|
||||
|
||||
db = self.database
|
||||
query = (db.oid_nonces.timestamp < time.time() - nonce.SKEW)
|
||||
query = (db.oid_nonces.itimestamp < time.time() - nonce.SKEW)
|
||||
return db(query).delete()
|
||||
|
||||
def cleanupAssociations(self):
|
||||
|
||||
+499
-340
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -991,7 +991,7 @@ class DIV(XmlComponent):
|
||||
>>> a=FORM( INPUT(_type='text'), SELECT(range(1)), TEXTAREA() )
|
||||
>>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
|
||||
>>> a.xml()
|
||||
'<form action="" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>'
|
||||
'<form action="#" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>'
|
||||
|
||||
Elements that are matched can also be replaced or removed by specifying
|
||||
a "replace" argument (note, a list of the original matching elements
|
||||
@@ -1478,9 +1478,9 @@ class A(DIV):
|
||||
(self['component'], self['target'] or '', d)
|
||||
self['_href'] = self['_href'] or '#null'
|
||||
elif self['callback']:
|
||||
returnfalse = "var e = arguments[0] || window.event; e.cancelBubble=true; if (e.stopPropagation) e.stopPropagation();"
|
||||
returnfalse = "var e = arguments[0] || window.event; e.cancelBubble=true; if (e.stopPropagation) {e.stopPropagation(); e.stopImmediatePropagation(); e.preventDefault();}"
|
||||
if d:
|
||||
self['_onclick'] = "if(confirm(w2p_ajax_confirm_message||'Are you sure you want o delete this object?')){ajax('%s',[],'%s');%s};%s" % \
|
||||
self['_onclick'] = "if(confirm(w2p_ajax_confirm_message||'Are you sure you want to delete this object?')){ajax('%s',[],'%s');%s};%s" % \
|
||||
(self['callback'], self['target'] or '', d, returnfalse)
|
||||
else:
|
||||
self['_onclick'] = "ajax('%s',[],'%s');%sreturn false" % \
|
||||
@@ -1932,7 +1932,7 @@ class FORM(DIV):
|
||||
>>> from validators import IS_NOT_EMPTY
|
||||
>>> form=FORM(INPUT(_name=\"test\", requires=IS_NOT_EMPTY()))
|
||||
>>> form.xml()
|
||||
'<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>'
|
||||
'<form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"test\" type=\"text\" /></form>'
|
||||
|
||||
a FORM is container for INPUT, TEXTAREA, SELECT and other helpers
|
||||
|
||||
@@ -2033,7 +2033,7 @@ class FORM(DIV):
|
||||
|
||||
def _postprocessing(self):
|
||||
if not '_action' in self.attributes:
|
||||
self['_action'] = ''
|
||||
self['_action'] = '#'
|
||||
if not '_method' in self.attributes:
|
||||
self['_method'] = 'post'
|
||||
if not '_enctype' in self.attributes:
|
||||
@@ -2415,25 +2415,25 @@ def test():
|
||||
<table><tr><td>a</td><td>b</td><td>c</td></tr><tr><td>d</td><td>e</td><td>f</td></tr><tr><td>1</td><td>2</td><td>3</td></tr></table>
|
||||
>>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_EXPR('int(value)<10')))
|
||||
>>> print form.xml()
|
||||
<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
|
||||
<form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" /></form>
|
||||
>>> print form.accepts({'myvar':'34'}, formname=None)
|
||||
False
|
||||
>>> print form.xml()
|
||||
<form action="" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="34" /><div class="error_wrapper"><div class="error" id="myvar__error">invalid expression</div></div></form>
|
||||
<form action="#" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="34" /><div class="error_wrapper"><div class="error" id="myvar__error">invalid expression</div></div></form>
|
||||
>>> print form.accepts({'myvar':'4'}, formname=None, keepvalues=True)
|
||||
True
|
||||
>>> print form.xml()
|
||||
<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
|
||||
<form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><input name=\"myvar\" type=\"text\" value=\"4\" /></form>
|
||||
>>> form=FORM(SELECT('cat', 'dog', _name='myvar'))
|
||||
>>> print form.accepts({'myvar':'dog'}, formname=None, keepvalues=True)
|
||||
True
|
||||
>>> print form.xml()
|
||||
<form action=\"\" enctype=\"multipart/form-data\" method=\"post\"><select name=\"myvar\"><option value=\"cat\">cat</option><option selected=\"selected\" value=\"dog\">dog</option></select></form>
|
||||
<form action=\"#\" enctype=\"multipart/form-data\" method=\"post\"><select name=\"myvar\"><option value=\"cat\">cat</option><option selected=\"selected\" value=\"dog\">dog</option></select></form>
|
||||
>>> form=FORM(INPUT(_type='text', _name='myvar', requires=IS_MATCH('^\w+$', 'only alphanumeric!')))
|
||||
>>> print form.accepts({'myvar':'as df'}, formname=None)
|
||||
False
|
||||
>>> print form.xml()
|
||||
<form action="" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="as df" /><div class="error_wrapper"><div class="error" id="myvar__error">only alphanumeric!</div></div></form>
|
||||
<form action="#" enctype="multipart/form-data" method="post"><input class="invalidinput" name="myvar" type="text" value="as df" /><div class="error_wrapper"><div class="error" id="myvar__error">only alphanumeric!</div></div></form>
|
||||
>>> session={}
|
||||
>>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
|
||||
>>> if form.accepts({}, session,formname=None): print 'passed'
|
||||
|
||||
@@ -1686,6 +1686,9 @@ SQLITE = set((
|
||||
'WHERE',
|
||||
))
|
||||
|
||||
|
||||
MONGODB_NONRESERVED = set(('SAFE',))
|
||||
|
||||
# remove from here when you add a list.
|
||||
JDBCSQLITE = SQLITE
|
||||
DB2 = INFORMIX = INGRES = JDBCPOSTGRESQL = COMMON
|
||||
@@ -1708,6 +1711,7 @@ ADAPTERS = {
|
||||
'jdbc:sqlite': JDBCSQLITE,
|
||||
'jdbc:postgres': JDBCPOSTGRESQL,
|
||||
'common': COMMON,
|
||||
'mongodb_nonreserved': MONGODB_NONRESERVED
|
||||
}
|
||||
|
||||
ADAPTERS['all'] = reduce(lambda a, b: a.union(b), (
|
||||
|
||||
@@ -20,6 +20,10 @@ except ImportError:
|
||||
import contrib.simplejson as json_parser # fallback to pure-Python module
|
||||
|
||||
|
||||
def loads_json(o):
|
||||
# deserialize a json string
|
||||
return json_parser.loads(o)
|
||||
|
||||
def custom_json(o):
|
||||
if hasattr(o, 'custom_json') and callable(o.custom_json):
|
||||
return o.custom_json()
|
||||
|
||||
+64
-46
@@ -30,6 +30,7 @@ from utils import md5_hash
|
||||
from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \
|
||||
IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, IS_STRONG
|
||||
|
||||
import serializers
|
||||
import datetime
|
||||
import urllib
|
||||
import re
|
||||
@@ -39,6 +40,9 @@ import inspect
|
||||
import settings
|
||||
is_gae = settings.global_settings.web2py_runtime_gae
|
||||
|
||||
|
||||
|
||||
|
||||
table_field = re.compile('[\w_]+\.[\w_]+')
|
||||
widget_class = re.compile('^\w*')
|
||||
|
||||
@@ -164,11 +168,9 @@ class TimeWidget(StringWidget):
|
||||
class DateWidget(StringWidget):
|
||||
_class = 'date'
|
||||
|
||||
|
||||
class DatetimeWidget(StringWidget):
|
||||
_class = 'datetime'
|
||||
|
||||
|
||||
class TextWidget(FormWidget):
|
||||
_class = 'text'
|
||||
|
||||
@@ -184,6 +186,22 @@ class TextWidget(FormWidget):
|
||||
attr = cls._attributes(field, default, **attributes)
|
||||
return TEXTAREA(**attr)
|
||||
|
||||
class JSONWidget(FormWidget):
|
||||
_class = 'json'
|
||||
|
||||
@classmethod
|
||||
def widget(cls, field, value, **attributes):
|
||||
"""
|
||||
generates a TEXTAREA for JSON notation.
|
||||
|
||||
see also: :meth:`FormWidget.widget`
|
||||
"""
|
||||
if not isinstance(value, basestring):
|
||||
if value is not None:
|
||||
value = serializers.json(value)
|
||||
default = dict(value=value)
|
||||
attr = cls._attributes(field, default, **attributes)
|
||||
return TEXTAREA(**attr)
|
||||
|
||||
class BooleanWidget(FormWidget):
|
||||
_class = 'boolean'
|
||||
@@ -235,7 +253,6 @@ class OptionsWidget(FormWidget):
|
||||
raise SyntaxError(
|
||||
'widget cannot determine options of %s' % field)
|
||||
opts = [OPTION(v, _value=k) for (k, v) in options]
|
||||
|
||||
return SELECT(*opts, **attr)
|
||||
|
||||
|
||||
@@ -523,20 +540,25 @@ class UploadWidget(FormWidget):
|
||||
|
||||
requires = attr["requires"]
|
||||
if requires == [] or isinstance(requires, IS_EMPTY_OR):
|
||||
inp = DIV(inp, '[',
|
||||
A(current.T(
|
||||
UploadWidget.GENERIC_DESCRIPTION), _href=url),
|
||||
'|',
|
||||
INPUT(_type='checkbox',
|
||||
_name=field.name + cls.ID_DELETE_SUFFIX,
|
||||
_id=field.name + cls.ID_DELETE_SUFFIX),
|
||||
LABEL(current.T(cls.DELETE_FILE),
|
||||
_for=field.name + cls.ID_DELETE_SUFFIX),
|
||||
']', br, image)
|
||||
inp = DIV(inp,
|
||||
SPAN('[',
|
||||
A(current.T(
|
||||
UploadWidget.GENERIC_DESCRIPTION), _href=url),
|
||||
'|',
|
||||
INPUT(_type='checkbox',
|
||||
_name=field.name + cls.ID_DELETE_SUFFIX,
|
||||
_id=field.name + cls.ID_DELETE_SUFFIX),
|
||||
LABEL(current.T(cls.DELETE_FILE),
|
||||
_for=field.name + cls.ID_DELETE_SUFFIX,
|
||||
_style='display:inline'),
|
||||
']', _style='white-space:nowrap'),
|
||||
br, image)
|
||||
else:
|
||||
inp = DIV(inp, '[',
|
||||
A(cls.GENERIC_DESCRIPTION, _href=url),
|
||||
']', br, image)
|
||||
inp = DIV(inp,
|
||||
SPAN('[',
|
||||
A(cls.GENERIC_DESCRIPTION, _href=url),
|
||||
']', _style='white-space:nowrap'),
|
||||
br, image)
|
||||
return inp
|
||||
|
||||
@classmethod
|
||||
@@ -716,7 +738,7 @@ def formstyle_table2cols(form, fields):
|
||||
|
||||
def formstyle_divs(form, fields):
|
||||
''' divs only '''
|
||||
table = TAG['']()
|
||||
table = FIELDSET()
|
||||
for id, label, controls, help in fields:
|
||||
_help = DIV(help, _class='w2p_fc')
|
||||
_controls = DIV(controls, _class='w2p_fw')
|
||||
@@ -748,7 +770,7 @@ def formstyle_ul(form, fields):
|
||||
|
||||
def formstyle_bootstrap(form, fields):
|
||||
''' bootstrap format form layout '''
|
||||
form['_class'] = 'form-horizontal'
|
||||
form.add_class('form-horizontal')
|
||||
parent = FIELDSET()
|
||||
for id, label, controls, help in fields:
|
||||
# wrappers
|
||||
@@ -782,12 +804,12 @@ def formstyle_bootstrap(form, fields):
|
||||
|
||||
if _submit:
|
||||
# submit button has unwrapped label and controls, different class
|
||||
parent.append(DIV(label, controls, _class='form-actions'))
|
||||
parent.append(DIV(label, controls, _class='form-actions', _id=id))
|
||||
# unflag submit (possible side effect)
|
||||
_submit = False
|
||||
else:
|
||||
# unwrapped label
|
||||
parent.append(DIV(label, _controls, _class='control-group'))
|
||||
parent.append(DIV(label, _controls, _class='control-group', _id=id))
|
||||
return parent
|
||||
|
||||
|
||||
@@ -843,6 +865,7 @@ class SQLFORM(FORM):
|
||||
widgets = Storage(dict(
|
||||
string=StringWidget,
|
||||
text=TextWidget,
|
||||
json=JSONWidget,
|
||||
password=PasswordWidget,
|
||||
integer=IntegerWidget,
|
||||
double=DoubleWidget,
|
||||
@@ -1132,7 +1155,7 @@ class SQLFORM(FORM):
|
||||
# </block>
|
||||
|
||||
# when deletable, add delete? checkbox
|
||||
self.custom.deletable = ''
|
||||
self.custom.delete = self.custom.deletable = ''
|
||||
if record and deletable:
|
||||
#add secondary css class for cascade delete warning
|
||||
css = 'delete'
|
||||
@@ -1154,7 +1177,8 @@ class SQLFORM(FORM):
|
||||
_id=self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_LABEL_SUFFIX),
|
||||
widget,
|
||||
col3.get(self.FIELDKEY_DELETE_RECORD, '')))
|
||||
self.custom.deletable = widget
|
||||
self.custom.delete = self.custom.deletable = widget
|
||||
|
||||
|
||||
# when writable, add submit button
|
||||
self.custom.submit = ''
|
||||
@@ -1807,28 +1831,21 @@ class SQLFORM(FORM):
|
||||
buttonurl=url(args=[]), callback=None,
|
||||
delete=None, trap=True):
|
||||
if showbuttontext:
|
||||
if callback:
|
||||
return A(SPAN(_class=ui.get(buttonclass)),
|
||||
SPAN(T(buttontext), _title=buttontext,
|
||||
_class=ui.get('buttontext')),
|
||||
callback=callback, delete=delete,
|
||||
_class=trap_class(ui.get('button'), trap))
|
||||
else:
|
||||
return A(SPAN(_class=ui.get(buttonclass)),
|
||||
SPAN(T(buttontext), _title=buttontext,
|
||||
_class=ui.get('buttontext')),
|
||||
_href=buttonurl,
|
||||
_class=trap_class(ui.get('button'), trap))
|
||||
return A(SPAN(_class=ui.get(buttonclass)),
|
||||
SPAN(T(buttontext), _title=buttontext,
|
||||
_class=ui.get('buttontext')),
|
||||
_href=buttonurl,
|
||||
callback=callback,
|
||||
delete=delete,
|
||||
_class=trap_class(ui.get('button'), trap))
|
||||
else:
|
||||
if callback:
|
||||
return A(SPAN(_class=ui.get(buttonclass)),
|
||||
callback=callback, delete=delete,
|
||||
_title=buttontext,
|
||||
_class=trap_class(ui.get('buttontext'), trap))
|
||||
else:
|
||||
return A(SPAN(_class=ui.get(buttonclass)),
|
||||
_href=buttonurl, _title=buttontext,
|
||||
_class=trap_class(ui.get('buttontext'), trap))
|
||||
return A(SPAN(_class=ui.get(buttonclass)),
|
||||
_href=buttonurl,
|
||||
callback=callback,
|
||||
delete=delete,
|
||||
_title=buttontext,
|
||||
_class=trap_class(ui.get('buttontext'), trap))
|
||||
|
||||
dbset = db(query)
|
||||
tablenames = db._adapter.tables(dbset.query)
|
||||
if left is not None:
|
||||
@@ -1948,8 +1965,8 @@ class SQLFORM(FORM):
|
||||
table = db[request.args[-2]]
|
||||
if ondelete:
|
||||
ondelete(table, request.args[-1])
|
||||
ret = db(table[table._id.name] == request.args[-1]).delete()
|
||||
return ret
|
||||
db(table[table._id.name] == request.args[-1]).delete()
|
||||
redirect(referrer)
|
||||
|
||||
exportManager = dict(
|
||||
csv_with_hidden_cols=(ExporterCSV, 'CSV (hidden cols)'),
|
||||
@@ -2267,7 +2284,7 @@ class SQLFORM(FORM):
|
||||
elif not isinstance(value, DIV):
|
||||
value = field.formatter(value)
|
||||
trcols.append(TD(value))
|
||||
row_buttons = TD(_class='row_buttons')
|
||||
row_buttons = TD(_class='row_buttons',_nowrap=True)
|
||||
if links and links_in_grid:
|
||||
toadd = []
|
||||
for link in links:
|
||||
@@ -2293,6 +2310,7 @@ class SQLFORM(FORM):
|
||||
if deletable and (not callable(deletable) or deletable(row)):
|
||||
row_buttons.append(gridbutton(
|
||||
'buttondelete', 'Delete',
|
||||
url(args=['delete', tablename, id]),
|
||||
callback=url(args=['delete', tablename, id]),
|
||||
delete='tr'))
|
||||
if buttons_placement in ['right', 'both']:
|
||||
|
||||
@@ -31,6 +31,7 @@ ALLOWED_DATATYPES = [
|
||||
'datetime',
|
||||
'upload',
|
||||
'password',
|
||||
'json',
|
||||
]
|
||||
|
||||
|
||||
@@ -62,14 +63,14 @@ class TestFields(unittest.TestCase):
|
||||
|
||||
def testFieldTypes(self):
|
||||
|
||||
# Check that string, text, and password default length is 512
|
||||
# Check that string, and password default length is 512
|
||||
for typ in ['string', 'password']:
|
||||
self.assert_(Field('abc', typ).length == 512,
|
||||
"Default length for type '%s' is not 512 or 255" % typ)
|
||||
|
||||
# Check that upload default length is 512
|
||||
self.assert_(Field('abc', 'upload').length == 512,
|
||||
"Default length for type 'upload' is not 128")
|
||||
"Default length for type 'upload' is not 512")
|
||||
|
||||
# Check that Tables passed in the type creates a reference
|
||||
self.assert_(Field('abc', Table(None, 'temp')).type
|
||||
@@ -113,6 +114,10 @@ class TestFields(unittest.TestCase):
|
||||
self.assertEqual(db.t.insert(a=True), 1)
|
||||
self.assertEqual(db().select(db.t.a)[0].a, True)
|
||||
db.t.drop()
|
||||
db.define_table('t', Field('a', 'json', default={}))
|
||||
self.assertEqual(db.t.insert(a={}), 1)
|
||||
self.assertEqual(db().select(db.t.a)[0].a, {})
|
||||
db.t.drop()
|
||||
db.define_table('t', Field('a', 'date',
|
||||
default=datetime.date.today()))
|
||||
t0 = datetime.date.today()
|
||||
|
||||
@@ -74,7 +74,7 @@ class TestBareHelpers(unittest.TestCase):
|
||||
|
||||
def testFORM(self):
|
||||
self.assertEqual(FORM('<>', _a='1', _b='2').xml(),
|
||||
'<form a="1" action="" b="2" enctype="multipart/form-data" method="post"><></form>')
|
||||
'<form a="1" action="#" b="2" enctype="multipart/form-data" method="post"><></form>')
|
||||
|
||||
def testH1(self):
|
||||
self.assertEqual(H1('<>', _a='1', _b='2').xml(),
|
||||
|
||||
+35
-25
@@ -272,7 +272,7 @@ class Mail(object):
|
||||
cc=None,
|
||||
bcc=None,
|
||||
reply_to=None,
|
||||
sender='%(sender)s',
|
||||
sender=None,
|
||||
encoding='utf-8',
|
||||
raw=False,
|
||||
headers={}
|
||||
@@ -360,9 +360,11 @@ class Mail(object):
|
||||
text = encode_header(text)
|
||||
return text
|
||||
|
||||
sender = sender or self.settings.sender
|
||||
|
||||
if not isinstance(self.settings.server, str):
|
||||
raise Exception('Server address not specified')
|
||||
if not isinstance(self.settings.sender, str):
|
||||
if not isinstance(sender, str):
|
||||
raise Exception('Sender address not specified')
|
||||
|
||||
if not raw:
|
||||
@@ -458,11 +460,11 @@ class Mail(object):
|
||||
c.set_armor(1)
|
||||
c.signers_clear()
|
||||
# search for signing key for From:
|
||||
for sigkey in c.op_keylist_all(self.settings.sender, 1):
|
||||
for sigkey in c.op_keylist_all(sender, 1):
|
||||
if sigkey.can_sign:
|
||||
c.signers_add(sigkey)
|
||||
if not c.signers_enum(0):
|
||||
self.error = 'No key for signing [%s]' % self.settings.sender
|
||||
self.error = 'No key for signing [%s]' % sender
|
||||
return False
|
||||
c.set_passphrase_cb(lambda x, y, z: sign_passphrase)
|
||||
try:
|
||||
@@ -619,7 +621,6 @@ class Mail(object):
|
||||
# no cryptography process as usual
|
||||
payload = payload_in
|
||||
|
||||
sender = sender % dict(sender=self.settings.sender)
|
||||
payload['From'] = encoded_or_raw(sender.decode(encoding))
|
||||
origTo = to[:]
|
||||
if to:
|
||||
@@ -655,16 +656,16 @@ class Mail(object):
|
||||
attachments = attachments and [(a.my_filename, a.my_payload) for a in attachments if not raw]
|
||||
if attachments:
|
||||
result = mail.send_mail(
|
||||
sender=self.settings.sender, to=origTo,
|
||||
sender=sender, to=origTo,
|
||||
subject=subject, body=text, html=html,
|
||||
attachments=attachments, **xcc)
|
||||
elif html and (not raw):
|
||||
result = mail.send_mail(
|
||||
sender=self.settings.sender, to=origTo,
|
||||
sender=sender, to=origTo,
|
||||
subject=subject, body=text, html=html, **xcc)
|
||||
else:
|
||||
result = mail.send_mail(
|
||||
sender=self.settings.sender, to=origTo,
|
||||
sender=sender, to=origTo,
|
||||
subject=subject, body=text, **xcc)
|
||||
else:
|
||||
smtp_args = self.settings.server.split(':')
|
||||
@@ -679,7 +680,7 @@ class Mail(object):
|
||||
if self.settings.login:
|
||||
server.login(*self.settings.login.split(':', 1))
|
||||
result = server.sendmail(
|
||||
self.settings.sender, to, payload.as_string())
|
||||
sender, to, payload.as_string())
|
||||
server.quit()
|
||||
except Exception, e:
|
||||
logger.warn('Mail.send failure:%s' % e)
|
||||
@@ -881,6 +882,7 @@ class Auth(object):
|
||||
profile_fields=None,
|
||||
email_case_sensitive=True,
|
||||
username_case_sensitive=True,
|
||||
update_fields = ['email'],
|
||||
)
|
||||
# ## these are messages that can be customized
|
||||
default_messages = dict(
|
||||
@@ -2047,7 +2049,8 @@ class Auth(object):
|
||||
if not self in self.settings.login_methods:
|
||||
# do not store password in db
|
||||
form.vars[passfield] = None
|
||||
user = self.get_or_create_user(form.vars)
|
||||
user = self.get_or_create_user(
|
||||
form.vars, self.settings.update_fields)
|
||||
break
|
||||
if not user:
|
||||
# alternates have failed, maybe because service inaccessible
|
||||
@@ -2067,7 +2070,8 @@ class Auth(object):
|
||||
if not self in self.settings.login_methods:
|
||||
# do not store password in db
|
||||
form.vars[passfield] = None
|
||||
user = self.get_or_create_user(form.vars)
|
||||
user = self.get_or_create_user(
|
||||
form.vars, self.settings.update_fields)
|
||||
break
|
||||
if not user:
|
||||
self.log_event(self.messages.login_failed_log,
|
||||
@@ -2087,7 +2091,8 @@ class Auth(object):
|
||||
if cas_user:
|
||||
cas_user[passfield] = None
|
||||
user = self.get_or_create_user(
|
||||
table_user._filter_fields(cas_user))
|
||||
table_user._filter_fields(cas_user),
|
||||
self.settings.update_fields)
|
||||
elif hasattr(cas, 'login_form'):
|
||||
return cas.login_form()
|
||||
else:
|
||||
@@ -3309,14 +3314,14 @@ class Auth(object):
|
||||
restrict_search=False,
|
||||
resolve=True,
|
||||
extra=None,
|
||||
menugroups=None):
|
||||
menu_groups=None):
|
||||
if not hasattr(self, '_wiki'):
|
||||
self._wiki = Wiki(self, render=render,
|
||||
manage_permissions=manage_permissions,
|
||||
force_prefix=force_prefix,
|
||||
restrict_search=restrict_search,
|
||||
env=env, extra=extra or {},
|
||||
menugroups=menugroups)
|
||||
menu_groups=menu_groups)
|
||||
else:
|
||||
self._wiki.env.update(env or {})
|
||||
# if resolve is set to True, process request as wiki call
|
||||
@@ -4694,15 +4699,19 @@ class Expose(object):
|
||||
class Wiki(object):
|
||||
everybody = 'everybody'
|
||||
rows_page = 25
|
||||
|
||||
def markmin_render(self, page):
|
||||
html = MARKMIN(page.body, extra=self.extra,
|
||||
def markmin_base(self,body):
|
||||
return MARKMIN(body, extra=self.extra,
|
||||
url=True, environment=self.env,
|
||||
autolinks=lambda link: expand_one(link, {})).xml()
|
||||
html += DIV(_class='w2p_wiki_tags',
|
||||
*[A(t.strip(), _href=URL(args='_search', vars=dict(q=t)))
|
||||
for t in page.tags or [] if t.strip()]).xml()
|
||||
return html
|
||||
|
||||
def render_tags(self, tags):
|
||||
return DIV(
|
||||
_class='w2p_wiki_tags',
|
||||
*[A(t.strip(), _href=URL(args='_search', vars=dict(q=t)))
|
||||
for t in tags or [] if t.strip()])
|
||||
|
||||
def markmin_render(self, page):
|
||||
return self.markmin_base(page.body) + self.render_tags(page.tags).xml()
|
||||
|
||||
def html_render(self, page):
|
||||
html = page.body
|
||||
@@ -4712,6 +4721,7 @@ class Wiki(object):
|
||||
html = replace_autolinks(html, lambda link: expand_one(link, {}))
|
||||
# @{component:name} -> <script>embed component name</script>
|
||||
html = replace_components(html, self.env)
|
||||
html = html + self.render_tags(page.tags).xml()
|
||||
return html
|
||||
|
||||
@staticmethod
|
||||
@@ -4726,7 +4736,7 @@ class Wiki(object):
|
||||
|
||||
def __init__(self, auth, env=None, render='markmin',
|
||||
manage_permissions=False, force_prefix='',
|
||||
restrict_search=False, extra=None, menugroups=None):
|
||||
restrict_search=False, extra=None, menu_groups=None):
|
||||
self.env = env or {}
|
||||
self.env['component'] = Wiki.component
|
||||
if render == 'markmin':
|
||||
@@ -4735,7 +4745,7 @@ class Wiki(object):
|
||||
render = self.html_render
|
||||
self.render = render
|
||||
self.auth = auth
|
||||
self.menugroups = menugroups
|
||||
self.menu_groups = menu_groups
|
||||
if self.auth.user:
|
||||
self.force_prefix = force_prefix % self.auth.user
|
||||
else:
|
||||
@@ -4853,11 +4863,11 @@ class Wiki(object):
|
||||
return True
|
||||
|
||||
def can_see_menu(self):
|
||||
if self.menugroups is None:
|
||||
if self.menu_groups is None:
|
||||
return True
|
||||
if self.auth.user:
|
||||
groups = self.auth.user_groups.values()
|
||||
if any(t in self.menugroups for t in groups):
|
||||
if any(t in self.menu_groups for t in groups):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -21,6 +21,16 @@ import unicodedata
|
||||
from cStringIO import StringIO
|
||||
from utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE
|
||||
|
||||
JSONErrors = (NameError, TypeError, ValueError, AttributeError,
|
||||
KeyError)
|
||||
try:
|
||||
import json as simplejson
|
||||
except ImportError:
|
||||
from gluon.contrib import simplejson
|
||||
from gluon.contrib.simplejson.decoder import JSONDecodeError
|
||||
JSONErrors += (JSONDecodeError,)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'CLEANUP',
|
||||
'CRYPT',
|
||||
@@ -53,6 +63,7 @@ __all__ = [
|
||||
'IS_UPLOAD_FILENAME',
|
||||
'IS_UPPER',
|
||||
'IS_URL',
|
||||
'IS_JSON',
|
||||
]
|
||||
|
||||
try:
|
||||
@@ -300,6 +311,30 @@ class IS_LENGTH(Validator):
|
||||
return (value, translate(self.error_message)
|
||||
% dict(min=self.minsize, max=self.maxsize))
|
||||
|
||||
class IS_JSON(Validator):
|
||||
"""
|
||||
example::
|
||||
INPUT(_type='text', _name='name',
|
||||
requires=IS_JSON(error_message="This is not a valid json input")
|
||||
|
||||
>>> IS_JSON()('{"a": 100}')
|
||||
('{"a": 100}', None)
|
||||
|
||||
>>> IS_JSON()('spam1234')
|
||||
('spam1234', 'invalid json')
|
||||
"""
|
||||
|
||||
def __init__(self, error_message='invalid json'):
|
||||
self.error_message = error_message
|
||||
|
||||
def __call__(self, value):
|
||||
try:
|
||||
simplejson.loads(value)
|
||||
return (value, None)
|
||||
except JSONErrors:
|
||||
pass
|
||||
return (value, translate(self.error_message))
|
||||
|
||||
|
||||
class IS_IN_SET(Validator):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,464 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Description : Installation and basic configuration of web2py, uWSGI, Redmine,
|
||||
# Unicorn, Nginx and PostgreSQL.
|
||||
# Usage : Copy the script in /home/username and run it as root, you may
|
||||
# need to allow exectuion (chmod +x). Ex.:
|
||||
# sudo ./setup-ubuntu-12-04-redmine-unicorn-web2py-uwsgi-nginx.sh
|
||||
# File : setup-ubuntu-12-04-redmine-unicorn-web2py-uwsgi-nginx.sh
|
||||
# Author : Richard V?zina
|
||||
# Email : ml.richard.vezina@gmail.com
|
||||
# Copyright : Richard V?zina
|
||||
# Date : ven 28 d?c 2012 13:27:11 EST
|
||||
# Disclaimers : This script is provided "as is", without warranty of any kind.
|
||||
# Licence : CC BY-NC 2.5 CA
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
echo 'setup-ubuntu-12-04-redmine-unicorn-web2py-uwsgi-nginx.sh'
|
||||
echo 'Requires Ubuntu = 12.04 (May works with 12.10 not tested) and installs Redmine + Unicorn + Web2py + uWSGI + Nginx + PostgreSQL'
|
||||
# Check if user has root privileges
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "You must run the script as root or using sudo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# We concentrate here user prompts!!
|
||||
# Get Redmine Postgres Database Password
|
||||
echo -e "Redmine Postgres Database Password: \c "
|
||||
read REDMINEPASSWORD
|
||||
# Get Web2py Admin Password
|
||||
echo -e "Web2py Admin Password: \c "
|
||||
read PW
|
||||
|
||||
cd ~
|
||||
openssl genrsa 1024 > self_signed.key
|
||||
chmod 400 self_signed.key
|
||||
openssl req -new -x509 -nodes -sha1 -days 1780 -key self_signed.key > self_signed.cert
|
||||
openssl x509 -noout -fingerprint -text < self_signed.cert > self_signed.info
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
apt-get update
|
||||
apt-get -y upgrade
|
||||
apt-get autoremove
|
||||
apt-get autoclean
|
||||
apt-get -y install postgresql
|
||||
apt-get -y install nginx-full
|
||||
apt-get -y install build-essential python-dev libxml2-dev python-pip unzip
|
||||
apt-get -y install ruby1.9.3 # Ref.: http://askubuntu.com/questions/137485/rails-3-not-using-rvm
|
||||
apt-get -y install libpq-dev # Required for gem1.9.3 install pg Ref.: http://stackoverflow.com/questions/6040583/unable-to-install-pg-gem-on-ubuntu-cant-find-the-libpq-fe-h-header
|
||||
|
||||
gem1.9.3 install rails --no-rdoc --no-ri # For testing (faster) --no-rdoc --no-ri
|
||||
gem1.9.3 install unicorn --no-rdoc --no-ri # For testing (faster) --no-rdoc --no-ri
|
||||
gem1.9.3 install pg --no-rdoc --no-ri # For testing (faster) --no-rdoc --no-ri
|
||||
cd /opt
|
||||
wget http://rubyforge.org/frs/download.php/76627/redmine-2.2.0.tar.gz
|
||||
wget http://rubyforge.org/frs/download.php/76628/redmine-2.2.0.tar.gz.md5
|
||||
md5sum --check redmine-2.2.0.tar.gz.md5 > redmine_md5_checked_successfully
|
||||
if [ -f redmine_md5_checked_successfully ]
|
||||
then
|
||||
tar xvfz redmine-2.2.0.tar.gz
|
||||
rm redmine_md5_checked_successfully
|
||||
else
|
||||
echo "Redmine md5 check sum failed..."
|
||||
exit 1
|
||||
fi
|
||||
cd redmine-2.2.0
|
||||
bundle install --without development test rmagick sqlite mysql
|
||||
mkdir /var/www
|
||||
ln -s /opt/redmine-2.2.0/public /var/www/redmine
|
||||
chown -R www-data.www-data /var/www
|
||||
chown -R www-data.www-data /opt/redmine-2.2.0/public
|
||||
# To avoid prompt during execution of the script use psql instead of createuser
|
||||
#echo "Enter a postgres redmine user password twice:"
|
||||
#createuser -P -S -D -R -l -e redmine
|
||||
# createuser switch: -P --pwprompt -S --no-superuser -D --no-createdb -R --no-createrole -l --login -e --echo
|
||||
sudo -u postgres psql -c "CREATE ROLE redmine LOGIN; ALTER ROLE redmine WITH ENCRYPTED PASSWORD '$REDMINEPASSWORD';"
|
||||
# createdb wouldn't work without having root password
|
||||
#createdb -U postgres -w -E UTF8 -O redmine -e redmine
|
||||
# createdb switch: -U username --username=username -w --no-password -E Encoding -O owner --owner=owner -e --echo
|
||||
sudo -u postgres psql -c "CREATE DATABASE redmine WITH ENCODING='UTF8' OWNER=redmine;"
|
||||
cd /opt/redmine-2.2.0/config
|
||||
# Here we change related to an issue with new rails version as far as I understand
|
||||
# Ref1.: http://www.redmine.org/projects/redmine/wiki/HowTo_Install_Redmine_in_a_sub-URI # Preferred solution used
|
||||
# Ref2.: http://www.redmine.org/issues/12102 # JS and CSS was not working until I add this line 'RedmineApp::Application.routes.default_scope = { :path => "/redmine", :shallow_path => "/redmine" }' before 'RedmineApp::Application.initialize!'
|
||||
cp environment.rb environment.rb_original # Backup default environment.rb
|
||||
sed '/RedmineApp::Application.initialize!/c \RedmineApp::Application.routes.default_scope = { :path => "/redmine", :shallow_path => "/redmine" }\nRedmineApp::Application.initialize!\nRedmine::Utils::relative_url_root = "/redmine"' environment.rb_original > environment.rb
|
||||
# Now we configure Redmine database access
|
||||
#nano database.yml
|
||||
# paste :
|
||||
echo 'production:
|
||||
adapter: postgresql
|
||||
database: redmine
|
||||
host: localhost
|
||||
username: redmine
|
||||
password: "'$REDMINEPASSWORD'"
|
||||
encoding: utf8' > database.yml
|
||||
rake generate_secret_token
|
||||
RAILS_ENV=production rake db:migrate
|
||||
RAILS_ENV=production rake redmine:load_default_data
|
||||
mkdir /opt/redmine-2.2.0/tmp/pids
|
||||
#mkdir /opt/redmine-2.2.0/log # if not there
|
||||
cd /opt/redmine-2.2.0/config
|
||||
# Create Unicorn specific Redmine config in /opt/redmine-2.2.0/config/unicorn.rb
|
||||
echo '#unicorn.rb Starts here
|
||||
worker_processes 1
|
||||
working_directory "/opt/redmine-2.2.0" # needs to be the correct directory for redmine
|
||||
|
||||
# This loads the application in the master process before forking
|
||||
# worker processes
|
||||
# Read more about it here:
|
||||
# http://unicorn.bogomips.org/Unicorn/Configurator.html
|
||||
preload_app true
|
||||
timeout 45
|
||||
|
||||
# This is where we specify the socket.
|
||||
# We will point the upstream Nginx module to this socket later on
|
||||
listen "/tmp/unicorn_rails.socket", :backlog => 64 #directory structure needs to be created.
|
||||
pid "/opt/redmine-2.2.0/tmp/pids/unicorn_rails.pid" # make sure this points to a valid directory. Make sure it is named the same as the real process name in order to allow init.d script start-stop-daemon command to kill unicorn process properly
|
||||
|
||||
# Set the path of the log files inside the log folder of the testapp
|
||||
stderr_path "/opt/redmine-2.2.0/log/unicorn_rails.stderr.log"
|
||||
stdout_path "/opt/redmine-2.2.0/log/unicorn_rails.stdout.log"
|
||||
|
||||
before_fork do |server, worker|
|
||||
# This option works in together with preload_app true setting
|
||||
# What is does is prevent the master process from holding
|
||||
# the database connection
|
||||
defined?(ActiveRecord::Base) and
|
||||
ActiveRecord::Base.connection.disconnect!
|
||||
end
|
||||
|
||||
after_fork do |server, worker|
|
||||
# Here we are establishing the connection after forking worker
|
||||
# processes
|
||||
defined?(ActiveRecord::Base) and
|
||||
ActiveRecord::Base.establish_connection
|
||||
# change below if your redmine instance is running differently
|
||||
worker.user('\''www-data'\'', '\''www-data'\'') if Process.euid == 0
|
||||
end
|
||||
#unicorn.rb Ends here' > unicorn.rb
|
||||
chown www-data:www-data unicorn.rb
|
||||
chown -R www-data:www-data /opt/redmine-2.2.0/tmp
|
||||
mkdir /etc/unicorn
|
||||
# Set some config for Unicorn in /etc/unicorn/redmine
|
||||
echo 'RAILS_ROOT=/opt/redmine-2
|
||||
RAILS_ENV=production' > /etc/unicorn/redmine
|
||||
# Create a Unicorn Redmine start script in /etc/init.d/redmine
|
||||
echo '#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: redmine
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: redmine initscript
|
||||
# Description: This script startup unicorn server and redmine and should
|
||||
# be placed in /etc/init.d.
|
||||
### END INIT INFO
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Author: Richard V?zina <ml.richard.vezina@gmail.com>
|
||||
# Base on Ubuntu 12.04 : /etc/init.d/skeleton
|
||||
# ven 21 d?c 2012 11:08:31 EST
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
APP=/opt/redmine-2.2.0/
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="Unicorn and Redmine"
|
||||
NAME=unicorn_rails
|
||||
DAEMON=/usr/local/bin/$NAME
|
||||
DAEMON_ARGS=" -E production -c $APP/config/unicorn.rb -D"
|
||||
PIDFILE=/opt/redmine-2.2.0/tmp/pids/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/redmine
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don'\''t delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
#
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
#reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave '\''force-reload'\'' as an alias for '\''restart'\''.
|
||||
#
|
||||
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
#do_reload
|
||||
#log_end_msg $?
|
||||
#;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# '\''force-reload'\'' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:' > /etc/init.d/redmine
|
||||
chmod +x /etc/init.d/redmine
|
||||
# Backup default Nginx site and replace it
|
||||
cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default_original
|
||||
rm /etc/nginx/sites-available/default
|
||||
# Create configuration file /etc/nginx/sites-available/default
|
||||
echo 'upstream unicorn_server {
|
||||
# This is the socket we configured in unicorn.rb
|
||||
server unix:/tmp/unicorn_rails.socket
|
||||
fail_timeout=0;
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
#return 301 https://192.168.1.126$request_uri; # http://$hostname$request_uri; # idem #http://wiki.nginx.org/Pitfalls#Taxing_Rewrites
|
||||
charset utf-8;
|
||||
server_name localhost; # $hostname;
|
||||
root /var/www;
|
||||
access_log /var/log/nginx/yoursite.access.log;
|
||||
error_log /var/log/nginx/yoursite.error.log;
|
||||
#to enable correct use of response.static_version
|
||||
#location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
|
||||
# alias /home/www-data/web2py/applications/$1/static/$2;
|
||||
# expires max;
|
||||
#}
|
||||
location ~* /(\w+)/static/ {
|
||||
root /home/www-data/web2py/applications/;
|
||||
#remove next comment on production
|
||||
#expires max;
|
||||
}
|
||||
location ~^\/(?!redmine(.*)) {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
uwsgi_pass unix:///tmp/web2py.socket;
|
||||
include uwsgi_params;
|
||||
uwsgi_param UWSGI_SCHEME $scheme;
|
||||
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
}
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_redirect off;
|
||||
|
||||
if (!-f $request_filename) {
|
||||
proxy_pass http://unicorn_server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 443 default_server ssl;
|
||||
charset utf-8;
|
||||
server_name localhost; # $hostname;
|
||||
root /var/www;
|
||||
ssl_certificate /etc/nginx/ssl/self_signed.cert;
|
||||
ssl_certificate_key /etc/nginx/ssl/self_signed.key;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
|
||||
ssl_protocols SSLv3 TLSv1;
|
||||
keepalive_timeout 70;
|
||||
location ~^\/(?!redmine(.*)) {
|
||||
#uwsgi_pass 127.0.0.1:9001;
|
||||
uwsgi_pass unix:///tmp/web2py.socket;
|
||||
include uwsgi_params;
|
||||
uwsgi_param UWSGI_SCHEME $scheme;
|
||||
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||
}
|
||||
location / {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_redirect off;
|
||||
|
||||
if (!-f $request_filename) {
|
||||
proxy_pass http://unicorn_server;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}' >/etc/nginx/sites-available/default
|
||||
|
||||
#ln -s /etc/nginx/sites-available/web2py /etc/nginx/sites-enabled/web2py
|
||||
#rm /etc/nginx/sites-enabled/default
|
||||
|
||||
# We copy ssl files we previously created
|
||||
if [ -f /etc/nginx/ssl ]
|
||||
then
|
||||
cp ~/self_signed.* /etc/nginx/ssl/
|
||||
rm ~/self_signed.*
|
||||
else
|
||||
mkdir /etc/nginx/ssl
|
||||
cp ~/self_signed.* /etc/nginx/ssl/
|
||||
rm ~/self_signed.*
|
||||
fi
|
||||
|
||||
pip install --upgrade pip
|
||||
PIPPATH=`which pip`
|
||||
$PIPPATH install --upgrade uwsgi
|
||||
|
||||
# Prepare folders for uwsgi
|
||||
sudo mkdir /etc/uwsgi
|
||||
sudo mkdir /var/log/uwsgi
|
||||
|
||||
# Create configuration file /etc/uwsgi/web2py.xml
|
||||
echo '<uwsgi>
|
||||
<socket>/tmp/web2py.socket</socket>
|
||||
<pythonpath>/home/www-data/web2py/</pythonpath>
|
||||
<mount>/=wsgihandler:application</mount>
|
||||
<master/>
|
||||
<processes>4</processes>
|
||||
<harakiri>60</harakiri>
|
||||
<reload-mercy>8</reload-mercy>
|
||||
<cpu-affinity>1</cpu-affinity>
|
||||
<stats>/tmp/stats.socket</stats>
|
||||
<max-requests>2000</max-requests>
|
||||
<limit-as>512</limit-as>
|
||||
<reload-on-as>256</reload-on-as>
|
||||
<reload-on-rss>192</reload-on-rss>
|
||||
<uid>www-data</uid>
|
||||
<gid>www-data</gid>
|
||||
<cron>0 0 -1 -1 -1 python /home/www-data/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o</cron>
|
||||
<no-orphans/>
|
||||
</uwsgi>' > /etc/uwsgi/web2py.xml
|
||||
|
||||
#Create a configuration file for uwsgi in emperor-mode
|
||||
#for Upstart in /etc/init/uwsgi-emperor.conf
|
||||
echo '# Emperor uWSGI script
|
||||
|
||||
description "uWSGI Emperor"
|
||||
start on runlevel [2345]
|
||||
stop on runlevel [06]
|
||||
##
|
||||
#remove the comments in the next section to enable static file compression for the welcome app
|
||||
#in that case, turn on gzip_static on; on /etc/nginx/nginx.conf
|
||||
##
|
||||
#pre-start script
|
||||
# python /home/www-data/web2py/web2py.py -S welcome -R scripts/zip_static_files.py
|
||||
# chown -R www-data:www-data /home/www-data/web2py/*
|
||||
#end script
|
||||
respawn
|
||||
exec uwsgi --master --die-on-term --emperor /etc/uwsgi --logto /var/log/uwsgi/uwsgi.log
|
||||
' > /etc/init/uwsgi-emperor.conf
|
||||
# Install Web2py
|
||||
mkdir /home/www-data
|
||||
cd /home/www-data
|
||||
wget http://web2py.com/examples/static/web2py_src.zip
|
||||
unzip web2py_src.zip
|
||||
rm web2py_src.zip
|
||||
# Download latest version of sessions2trash.py
|
||||
wget http://web2py.googlecode.com/hg/scripts/sessions2trash.py -O /home/www-data/web2py/scripts/sessions2trash.py
|
||||
chown -R www-data:www-data web2py
|
||||
cd /home/www-data/web2py
|
||||
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
|
||||
/etc/init.d/redmine start
|
||||
start uwsgi-emperor
|
||||
/etc/init.d/nginx restart
|
||||
ufw allow 80 # Or check your firewall configuration
|
||||
Reference in New Issue
Block a user