Merge ssh://github.com/web2py/web2py

This commit is contained in:
Michele Comitini
2013-09-05 11:17:43 +02:00
41 changed files with 548 additions and 302 deletions
+6 -6
View File
@@ -6,17 +6,17 @@ python:
- '2.7'
- 'pypy'
install:
- pip install -e . --use-mirrors
- pip install -e .
env:
- DB=sqlite:memory
- DB=mysql://root:@localhost/test_w2p
- DB=postgres://postgres:@localhost/test_w2p
before_script:
- if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install --use-mirrors unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors coverage; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors python-coveralls; fi
- if [[ $DB == postgres* ]]; then pip install --use-mirrors psycopg2; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install --use-mirrors pysqlite; fi
- if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install coverage; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install python-coveralls; fi
- if [[ $DB == postgres* ]]; then pip install psycopg2; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi
- if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi
- if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi
#Temporal solution to travis issue #155
+1 -1
View File
@@ -54,7 +54,7 @@ src:
### build web2py_src.zip
echo '' > NEWINSTALL
mv web2py_src.zip web2py_src_old.zip | echo 'no old'
cd ..; zip -r web2py/web2py_src.zip web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/contrib/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py
mdp:
make src
+1 -1
View File
@@ -1 +1 @@
Version 2.6.0-development+timestamp.2013.08.29.21.31.47
Version 2.6.0-development+timestamp.2013.09.04.16.32.36
+4 -4
View File
@@ -189,7 +189,7 @@ def run(servername, ip, port, softcron=True, logging=False, profiler=None):
if logging:
application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase,
logfilename='httpserver.log',
profilerfilename=profiler)
profiler_dir=profiler)
else:
application = gluon.main.wsgibase
if softcron:
@@ -319,8 +319,8 @@ def main():
parser.add_option('-P',
'--profiler',
default=False,
dest='profiler',
help='profiler filename')
dest='profiler_dir',
help='profiler dir')
servers = ', '.join(x for x in dir(Servers) if not x[0] == '_')
parser.add_option('-s',
'--server',
@@ -346,7 +346,7 @@ def main():
print 'starting %s on %s:%s...' % (
options.server, options.ip, options.port)
run(options.server, options.ip, options.port,
logging=options.logging, profiler=options.profiler)
logging=options.logging, profiler=options.profiler_dir)
if __name__ == '__main__':
main()
+2 -1
View File
@@ -1016,8 +1016,9 @@ def design():
privates.sort()
# Get all static files
MAXNFILES = 1000
statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*')
statics = [x.replace('\\', '/') for x in statics]
statics = [x.replace('\\', '/') for x in statics[:MAXNFILES]]
statics.sort()
# Get all languages
+1
View File
@@ -248,6 +248,7 @@
'Next Edit Point': 'nächster Bearbeitungsschritt',
'NO': 'NEIN',
'No databases in this application': 'Keine Datenbank in dieser Anwendung',
'no package selected': 'no package selected',
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
'online designer': 'online designer',
'or alternatively': 'or alternatively',
+43 -29
View File
@@ -74,28 +74,6 @@
* Ideally all events should be bound to the document, so we can avoid calling
* this over and over... all will be bound to the document
*/
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
$("input.date", target).each(function () {
$(this).attr('autocomplete', 'off');
Calendar.setup({
inputField: this,
ifFormat: date_format,
showsTime: false
});
});
$("input.datetime", target).each(function () {
$(this).attr('autocomplete', 'off');
Calendar.setup({
inputField: this,
ifFormat: datetime_format,
showsTime: true,
timeFormat: "24"
});
});
$("input.time", target).each(function () {
$(this).timeEntry().attr('autocomplete', 'off');
});
/*adds btn class to buttons*/
$('button', target).addClass('btn');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
@@ -181,18 +159,13 @@
/*
* This is called once for page
* Ideally it should bound all the things that are needed
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.flash', function (e) {
var t = $(this);
if(t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
/* if I want to display a clickable something
* inside flash, I should not be prevented to follow it
*
* e.preventDefault();
*/
});
doc.on('keyup', 'input.integer', function () {
this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse();
@@ -205,7 +178,48 @@
if(this.checked)
if(!web2py.confirm(confirm_message)) this.checked = false;
});
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
doc.on('click', "input.datetime", function () {
var tformat = $(this).data('w2p_datetime_format')
var active = $(this).data('w2p_datetime');
var format = (typeof tformat != 'undefined') ? tformat : datetime_format;
if(active === undefined) {
Calendar.setup({
inputField: this,
ifFormat: format,
showsTime: true,
timeFormat: "24"
});
$(this).attr('autocomplete', 'off');
$(this).data('w2p_datetime', 1);
$(this).trigger('click');
}
});
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
doc.on('click', "input.date", function () {
var tformat = $(this).data('w2p_date_format')
var active = $(this).data('w2p_date');
var format = (typeof tformat != 'undefined') ? tformat : date_format;
if(active === undefined) {
Calendar.setup({
inputField: this,
ifFormat: format,
showsTime: false
});
$(this).data('w2p_date', 1);
$(this).attr('autocomplete', 'off');
$(this).trigger('click');
}
});
doc.on('focus', "input.time", function () {
var active = $(this).data('w2p_time');
if(active === undefined) {
$(this).timeEntry({
spinnerImage: ''
}).attr('autocomplete', 'off');
$(this).data('w2p_time', 1);
}
});
doc.ajaxSuccess(function (e, xhr) {
var redirect = xhr.getResponseHeader('web2py-redirect-location');
if(redirect !== null) {
@@ -1,5 +1,6 @@
import time
response.view = 'cache_examples/generic.html'
def cache_in_ram():
"""cache the output of the lambda function in ram"""
+43 -29
View File
@@ -74,28 +74,6 @@
* Ideally all events should be bound to the document, so we can avoid calling
* this over and over... all will be bound to the document
*/
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
$("input.date", target).each(function () {
$(this).attr('autocomplete', 'off');
Calendar.setup({
inputField: this,
ifFormat: date_format,
showsTime: false
});
});
$("input.datetime", target).each(function () {
$(this).attr('autocomplete', 'off');
Calendar.setup({
inputField: this,
ifFormat: datetime_format,
showsTime: true,
timeFormat: "24"
});
});
$("input.time", target).each(function () {
$(this).timeEntry().attr('autocomplete', 'off');
});
/*adds btn class to buttons*/
$('button', target).addClass('btn');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
@@ -181,18 +159,13 @@
/*
* This is called once for page
* Ideally it should bound all the things that are needed
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.flash', function (e) {
var t = $(this);
if(t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
/* if I want to display a clickable something
* inside flash, I should not be prevented to follow it
*
* e.preventDefault();
*/
});
doc.on('keyup', 'input.integer', function () {
this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse();
@@ -205,7 +178,48 @@
if(this.checked)
if(!web2py.confirm(confirm_message)) this.checked = false;
});
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
doc.on('click', "input.datetime", function () {
var tformat = $(this).data('w2p_datetime_format')
var active = $(this).data('w2p_datetime');
var format = (typeof tformat != 'undefined') ? tformat : datetime_format;
if(active === undefined) {
Calendar.setup({
inputField: this,
ifFormat: format,
showsTime: true,
timeFormat: "24"
});
$(this).attr('autocomplete', 'off');
$(this).data('w2p_datetime', 1);
$(this).trigger('click');
}
});
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
doc.on('click', "input.date", function () {
var tformat = $(this).data('w2p_date_format')
var active = $(this).data('w2p_date');
var format = (typeof tformat != 'undefined') ? tformat : date_format;
if(active === undefined) {
Calendar.setup({
inputField: this,
ifFormat: format,
showsTime: false
});
$(this).data('w2p_date', 1);
$(this).attr('autocomplete', 'off');
$(this).trigger('click');
}
});
doc.on('focus', "input.time", function () {
var active = $(this).data('w2p_time');
if(active === undefined) {
$(this).timeEntry({
spinnerImage: ''
}).attr('autocomplete', 'off');
$(this).data('w2p_time', 1);
}
});
doc.ajaxSuccess(function (e, xhr) {
var redirect = xhr.getResponseHeader('web2py-redirect-location');
if(redirect !== null) {
@@ -0,0 +1,3 @@
{{extend 'layout.html'}}
<h1>Cache Examples</h1>
{{=BEAUTIFY(response._vars)}}
@@ -0,0 +1,4 @@
{{extend 'layout.html'}}
<h1>Ajax Wiki</h1>
{{=form}}
{{=html}}
+43 -29
View File
@@ -74,28 +74,6 @@
* Ideally all events should be bound to the document, so we can avoid calling
* this over and over... all will be bound to the document
*/
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
$("input.date", target).each(function () {
$(this).attr('autocomplete', 'off');
Calendar.setup({
inputField: this,
ifFormat: date_format,
showsTime: false
});
});
$("input.datetime", target).each(function () {
$(this).attr('autocomplete', 'off');
Calendar.setup({
inputField: this,
ifFormat: datetime_format,
showsTime: true,
timeFormat: "24"
});
});
$("input.time", target).each(function () {
$(this).timeEntry().attr('autocomplete', 'off');
});
/*adds btn class to buttons*/
$('button', target).addClass('btn');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn');
@@ -181,18 +159,13 @@
/*
* This is called once for page
* Ideally it should bound all the things that are needed
* and require no dom manipulations
*/
var doc = $(document);
doc.on('click', '.flash', function (e) {
var t = $(this);
if(t.css('top') == '0px') t.slideUp('slow');
else t.fadeOut();
/* if I want to display a clickable something
* inside flash, I should not be prevented to follow it
*
* e.preventDefault();
*/
});
doc.on('keyup', 'input.integer', function () {
this.value = this.value.reverse().replace(/[^0-9\-]|\-(?=.)/g, '').reverse();
@@ -205,7 +178,48 @@
if(this.checked)
if(!web2py.confirm(confirm_message)) this.checked = false;
});
var datetime_format = (typeof w2p_ajax_datetime_format != 'undefined') ? w2p_ajax_datetime_format : "%Y-%m-%d %H:%M:%S";
doc.on('click', "input.datetime", function () {
var tformat = $(this).data('w2p_datetime_format')
var active = $(this).data('w2p_datetime');
var format = (typeof tformat != 'undefined') ? tformat : datetime_format;
if(active === undefined) {
Calendar.setup({
inputField: this,
ifFormat: format,
showsTime: true,
timeFormat: "24"
});
$(this).attr('autocomplete', 'off');
$(this).data('w2p_datetime', 1);
$(this).trigger('click');
}
});
var date_format = (typeof w2p_ajax_date_format != 'undefined') ? w2p_ajax_date_format : "%Y-%m-%d";
doc.on('click', "input.date", function () {
var tformat = $(this).data('w2p_date_format')
var active = $(this).data('w2p_date');
var format = (typeof tformat != 'undefined') ? tformat : date_format;
if(active === undefined) {
Calendar.setup({
inputField: this,
ifFormat: format,
showsTime: false
});
$(this).data('w2p_date', 1);
$(this).attr('autocomplete', 'off');
$(this).trigger('click');
}
});
doc.on('focus', "input.time", function () {
var active = $(this).data('w2p_time');
if(active === undefined) {
$(this).timeEntry({
spinnerImage: ''
}).attr('autocomplete', 'off');
$(this).data('w2p_time', 1);
}
});
doc.ajaxSuccess(function (e, xhr) {
var redirect = xhr.getResponseHeader('web2py-redirect-location');
if(redirect !== null) {
+1 -1
View File
@@ -18,7 +18,7 @@ from fileutils import up, fix_newlines, abspath, recursive_unlink
from fileutils import read_file, write_file, parse_version
from restricted import RestrictedError
from settings import global_settings
from http import HTTP
if not global_settings.web2py_runtime_gae:
import site
+1 -1
View File
@@ -68,7 +68,7 @@ class CacheAbstract(object):
def __init__(self, request=None):
"""
Paremeters
Parameters
----------
request:
the global request object
-1
View File
@@ -13,7 +13,6 @@ FOR INTERNAL USE ONLY
"""
import re
import sys
import fnmatch
import os
import copy
+66 -3
View File
@@ -6,6 +6,12 @@
import re
from cgi import escape
from string import maketrans
try:
from ast import parse as ast_parse
import ast
except ImportError: # python 2.5
from compiler import parse
import compiler.ast as ast
"""
TODO: next version should use MathJax
@@ -532,7 +538,7 @@ LINK = '\x07'
DISABLED_META = '\x08'
LATEX = '<img src="http://chart.apis.google.com/chart?cht=tx&chl=%s" />'
regex_URL=re.compile(r'@/(?P<a>\w*)/(?P<c>\w*)/(?P<f>\w*(\.\w+)?)(/(?P<args>[\w\.\-/]+))?')
regex_env=re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_env2=re.compile(r'@\{(?P<a>[\w\-\.]+?)(\:(?P<b>.*?))?\}')
regex_expand_meta = re.compile('('+META+'|'+DISABLED_META+'|````)')
regex_dd=re.compile(r'\$\$(?P<latex>.*?)\$\$')
regex_code = re.compile('('+META+'|'+DISABLED_META+r'|````)|(``(?P<t>.+?)``(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[^\]]*)\])?)?)',re.S)
@@ -553,6 +559,53 @@ regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])")
regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])")
ttab_in = maketrans("'`:*~\\[]{}@$+-.#\n", '\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05')
ttab_out = maketrans('\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x05',"'`:*~\\[]{}@$+-.#\n")
regex_quote = re.compile('(?P<name>\w+?)\s*\=\s*')
def make_dict(b):
return '{%s}' % regex_quote.sub("'\g<name>':",b)
def safe_eval(node_or_string, env):
"""
Safely evaluate an expression node or a string containing a Python
expression. The string or node provided may only consist of the following
Python literal structures: strings, numbers, tuples, lists, dicts, booleans,
and None.
"""
_safe_names = {'None': None, 'True': True, 'False': False}
_safe_names.update(env)
if isinstance(node_or_string, basestring):
node_or_string = ast_parse(node_or_string, mode='eval')
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
def _convert(node):
if isinstance(node, ast.Str):
return node.s
elif isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.Tuple):
return tuple(map(_convert, node.elts))
elif isinstance(node, ast.List):
return list(map(_convert, node.elts))
elif isinstance(node, ast.Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
elif isinstance(node, ast.Name):
if node.id in _safe_names:
return _safe_names[node.id]
elif isinstance(node, ast.BinOp) and \
isinstance(node.op, (Add, Sub)) and \
isinstance(node.right, Num) and \
isinstance(node.right.n, complex) and \
isinstance(node.left, Num) and \
isinstance(node.left.n, (int, long, float)):
left = node.left.n
right = node.right.n
if isinstance(node.op, Add):
return left + right
else:
return left - right
raise ValueError('malformed string')
return _convert(node_or_string)
def markmin_escape(text):
""" insert \\ before markmin control characters: '`:*~[]{}@$ """
@@ -571,15 +624,22 @@ def replace_at_urls(text,url):
return regex_URL.sub(u1,text)
def replace_components(text,env):
# not perfect but acceptable
def u2(match, env=env):
f = env.get(match.group('a'), match.group(0))
if callable(f):
b = match.group('b')
try:
f = f(match.group('b'))
b = safe_eval(make_dict(b),env)
except:
pass
try:
f = f(**b) if isinstance(b,dict) else f(b)
except Exception, e:
f = 'ERROR: %s' % e
return str(f)
return regex_env.sub(u2, text)
text = regex_env2.sub(u2, text)
return text
def autolinks_simple(url):
"""
@@ -844,6 +904,9 @@ def render(text,
>>> render("**@{probe:1}**", environment=dict(probe=lambda t:"test %s" % t))
'<p><strong>test 1</strong></p>'
>>> render("**@{probe:t=a}**", environment=dict(probe=lambda t:"test %s" % t, a=1))
'<p><strong>test 1</strong></p>'
>>> render('[[id1 [span **messag** in ''markmin''] ]] ... [[**link** to id [link\\\'s title] #mark1]]')
'<p><span class="anchor" id="markmin_id1">span <strong>messag</strong> in markmin</span> ... <a href="#markmin_mark1" title="link\\\'s title"><strong>link</strong> to id</a></p>'
+9 -5
View File
@@ -48,12 +48,12 @@ _DEBUG = True
_HISTORY_KIND = '_Shell_History'
# Types that can't be pickled.
UNPICKLABLE_TYPES = (
UNPICKLABLE_TYPES = [
types.ModuleType,
types.TypeType,
types.ClassType,
types.FunctionType,
)
]
# Unpicklable statements to seed new historys with.
INITIAL_UNPICKLABLES = [
@@ -244,8 +244,8 @@ def run(history, statement, env={}):
for name, val in statement_module.__dict__.items():
if name not in old_globals or represent(val) != old_globals[name]:
new_globals[name] = val
if True in [isinstance(val, UNPICKLABLE_TYPES)
if True in [isinstance(val, tuple(UNPICKLABLE_TYPES))
for val in new_globals.values()]:
# this statement added an unpicklable global. store the statement and
# the names of all of the globals it added in the unpicklables.
@@ -256,7 +256,11 @@ def run(history, statement, env={}):
# new globals back into the datastore.
for name, val in new_globals.items():
if not name.startswith('__'):
history.set_global(name, val)
try:
history.set_global(name, val)
except TypeError, ex:
UNPICKLABLE_TYPES.append(type(val))
history.add_unpicklable(statement, new_globals.keys())
finally:
sys.modules['__main__'] = old_main
-1
View File
@@ -3,7 +3,6 @@
import __builtin__
import os
import re
import sys
import threading
import traceback
+38 -21
View File
@@ -690,7 +690,11 @@ class BaseAdapter(ConnectionPool):
return isinstance(exception, self.driver.ProgrammingError)
def id_query(self, table):
return table._id != None
pkeys = getattr(table,'_primarykey',None)
if pkeys:
return table[pkeys[0]] != None
else:
return table._id != None
def adapt(self, obj):
return "'%s'" % obj.replace("'", "''")
@@ -6939,9 +6943,11 @@ def sqlhtml_validators(field):
referenced._format,multiple=True)
else:
requires = validators.IS_IN_DB(db,referenced._id,
multiple=True)
multiple=True)
if field.unique:
requires._and = validators.IS_NOT_IN_DB(db,field)
if not field.notnull:
requires = validators.IS_EMPTY_OR(requires)
return requires
elif field_type.startswith('list:'):
def repr_list(values,row=None): return', '.join(str(v) for v in (values or []))
@@ -8206,8 +8212,12 @@ class Reference(long):
def __getattr__(self, key):
if key == 'id':
return long(self)
self.__allocate()
return self._record.get(key, None)
if key in self._table:
self.__allocate()
if self._record:
return self._record.get(key,None) # to deal with case self.update_record()
else:
return None
def get(self, key, default=None):
return self.__getattr__(key, default)
@@ -8416,8 +8426,9 @@ class Table(object):
def _enable_record_versioning(self,
archive_db=None,
archive_name = '%(tablename)s_archive',
is_active = 'is_active',
current_record = 'current_record',
is_active = 'is_active'):
current_record_label = None):
db = self._db
archive_db = archive_db or db
archive_name = archive_name % dict(tablename=self._tablename)
@@ -8432,7 +8443,8 @@ class Table(object):
clones.append(field.clone(
unique=False, type=field.type if nfk else 'bigint'))
archive_db.define_table(
archive_name, Field(current_record,field_type), *clones)
archive_name, Field(current_record,field_type,
label=current_record_label), *clones)
self._before_update.append(
lambda qset,fs,db=archive_db,an=archive_name,cn=current_record:
archive_record(qset,fs,db[an],cn))
@@ -8875,24 +8887,31 @@ class Table(object):
first = True
unique_idx = None
for line in reader:
for lineno, line in enumerate(reader):
if not line:
break
if not colnames:
# assume this is the first line of the input, contains colnames
colnames = [x.split('.',1)[-1] for x in line][:len(line)]
cols, cid = [], None
for i,colname in enumerate(colnames):
if is_id(colname):
cid = i
else:
cols.append(i)
elif colname in self.fields:
cols.append((i,self[colname]))
if colname == unique:
unique_idx = i
else:
items = [fix(self[colnames[i]], line[i], id_map, id_offset) \
for i in cols if colnames[i] in self.fields]
if not id_map and cid is not None and id_offset is not None and not unique_idx:
# every other line contains instead data
items = []
for i, field in cols:
try:
items.append(fix(field, line[i], id_map, id_offset))
except ValueError:
raise RuntimeError("Unable to parse line:%s field:%s value:'%s'"
% (lineno+1,field,line[i]))
if not (id_map or cid is None or id_offset is None or unique_idx):
csv_id = long(line[cid])
curr_id = self.insert(**dict(items))
if first:
@@ -8900,10 +8919,8 @@ class Table(object):
# First curr_id is bigger than csv_id,
# then we are not restoring but
# extending db table with csv db table
if curr_id>csv_id:
id_offset[self._tablename] = curr_id-csv_id
else:
id_offset[self._tablename] = 0
id_offset[self._tablename] = (curr_id-csv_id) \
if curr_id>csv_id else 0
# create new id until we get the same as old_id+offset
while curr_id<csv_id+id_offset[self._tablename]:
self._db(self._db[self][colnames[cid]] == curr_id).delete()
@@ -10690,7 +10707,7 @@ class Rows(object):
def test_all():
"""
>>> if len(sys.argv)<2: db = DAL(\"sqlite://test.db\")
>>> if len(sys.argv)<2: db = DAL("sqlite://test.db")
>>> if len(sys.argv)>1: db = DAL(sys.argv[1])
>>> tmp = db.define_table('users',\
Field('stringf', 'string', length=32, required=True),\
@@ -10726,8 +10743,8 @@ def test_all():
Field('name'),\
Field('birth','date'),\
migrate='test_person.table')
>>> person_id = db.person.insert(name=\"Marco\",birth='2005-06-22')
>>> person_id = db.person.insert(name=\"Massimo\",birth='1971-12-21')
>>> person_id = db.person.insert(name='Marco',birth='2005-06-22')
>>> person_id = db.person.insert(name='Massimo',birth='1971-12-21')
commented len(db().select(db.person.ALL))
commented 2
@@ -10753,7 +10770,7 @@ def test_all():
Update a single record
>>> me.update_record(name=\"Max\")
>>> me.update_record(name="Max")
<Row {'name': 'Max', 'birth': datetime.date(1971, 12, 21), 'id': 2}>
>>> me.name
'Max'
-1
View File
@@ -10,7 +10,6 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""
import logging
import os
import pdb
import Queue
import sys
+1 -1
View File
@@ -1,5 +1,5 @@
import codecs
import encodings
"""Caller will hand this library a buffer and ask it to either convert
it or auto-detect the type.
+18 -5
View File
@@ -7,7 +7,6 @@ Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""
import sys
import storage
import os
import re
@@ -360,12 +359,21 @@ def get_session(request, other_application='admin'):
raise KeyError
try:
session_id = request.cookies['session_id_' + other_application].value
osession = storage.load_storage(os.path.join(
up(request.folder), other_application, 'sessions', session_id))
session_filename = os.path.join(
up(request.folder), other_application, 'sessions', session_id)
osession = storage.load_storage(session_filename)
except Exception, e:
osession = storage.Storage()
return osession
def set_session(request, session, other_application='admin'):
""" checks that user is authorized to access other_application"""
if request.application == other_application:
raise KeyError
session_id = request.cookies['session_id_' + other_application].value
session_filename = os.path.join(
up(request.folder), other_application, 'sessions', session_id)
storage.save_storage(session,session_filename)
def check_credentials(request, other_application='admin',
expiration=60 * 60, gae_login=True):
@@ -381,9 +389,14 @@ def check_credentials(request, other_application='admin',
else:
return False
else:
dt = time.time() - expiration
t0 = time.time()
dt = t0 - expiration
s = get_session(request, other_application)
return (s.authorized and s.last_time and s.last_time > dt)
r = (s.authorized and s.last_time and s.last_time > dt)
if r:
s.last_time = t0
set_session(request,s,other_application)
return r
def fix_newlines(path):
+17 -9
View File
@@ -841,8 +841,8 @@ class DIV(XmlComponent):
c.latest = self.latest
c.session = self.session
c.formname = self.formname
c['hideerror'] = hideerror or \
self.attributes.get('hideerror', False)
if not c.attributes.get('hideerror'):
c['hideerror'] = hideerror or self.attributes.get('hideerror')
newstatus = c._traverse(status, hideerror) and newstatus
# for input, textarea, select, option
@@ -1961,7 +1961,7 @@ class FORM(DIV):
example::
>>> from validators import IS_NOT_EMPTY
>>> form=FORM(INPUT(_name=\"test\", requires=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>'
@@ -2457,15 +2457,23 @@ class MENU(DIV):
def serialize_mobile(self, data, select=None, prefix=''):
if not select:
select = SELECT(**self.attributes)
custom_items = []
for item in data:
if len(item) <= 4 or item[4] == True:
# Custom item aren't serialized as mobile
if len(item) >= 3 and (not item[0]) or (isinstance(item[0], DIV) and not (item[2])):
# ex: ('', False,A('title',_href=URL(...),_title="title"))
# ex: (A('title',_href=URL(...),_title="title"), False, None)
custom_items.append(item)
elif len(item) <= 4 or item[4] == True:
select.append(OPTION(CAT(prefix, item[0]),
_value=item[2], _selected=item[1]))
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'
return select
# avoid to wrap the select if no custom items are present
html = DIV(select, self.serialize(custom_items)) if len( custom_items) else select
return html
def xml(self):
if self['mobile']:
@@ -2501,11 +2509,11 @@ def test():
Example:
>>> from validators import *
>>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN(\"World\"), _class='unknown')).xml()
>>> print DIV(A('click me', _href=URL(a='a', c='b', f='c')), BR(), HR(), DIV(SPAN("World"), _class='unknown')).xml()
<div><a data-w2p_disable_with="default" href="/a/b/c">click me</a><br /><hr /><div class=\"unknown\"><span>World</span></div></div>
>>> print DIV(UL(\"doc\",\"cat\",\"mouse\")).xml()
>>> print DIV(UL("doc","cat","mouse")).xml()
<div><ul><li>doc</li><li>cat</li><li>mouse</li></ul></div>
>>> print DIV(UL(\"doc\", LI(\"cat\", _class='feline'), 18)).xml()
>>> print DIV(UL("doc", LI("cat", _class='feline'), 18)).xml()
<div><ul><li>doc</li><li class=\"feline\">cat</li><li>18</li></ul></div>
>>> print TABLE(['a', 'b', 'c'], TR('d', 'e', 'f'), TR(TD(1), TD(2), TD(3))).xml()
<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>
@@ -2531,7 +2539,7 @@ def test():
>>> 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>
>>> session={}
>>> form=FORM(INPUT(value=\"Hello World\", _name=\"var\", requires=IS_MATCH('^\w+$')))
>>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
>>> isinstance(form.as_dict(), dict)
True
>>> form.as_dict(flat=True).has_key("vars")
-1
View File
@@ -15,7 +15,6 @@ import re
import sys
import pkgutil
import logging
import marshal
from cgi import escape
from threading import RLock
-1
View File
@@ -14,7 +14,6 @@ Contains:
if False: import import_all # DO NOT REMOVE PART OF FREEZE PROCESS
import gc
import cStringIO
import Cookie
import os
import re
+12 -2
View File
@@ -117,6 +117,10 @@ class Token(object):
if a cron job started before 60 seconds and did not stop,
a warning is issue "Stale cron.master detected"
"""
if sys.platform == 'win32':
locktime = 59.5
else:
locktime = 59.99
if portalocker.LOCK_EX is None:
logger.warning('WEB2PY CRON: Disabled because no file locking')
return None
@@ -128,7 +132,7 @@ class Token(object):
(start, stop) = cPickle.load(self.master)
except:
(start, stop) = (0, 1)
if startup or self.now - start > 59.99:
if startup or self.now - start > locktime:
ret = self.now
if not stop:
# this happens if previous cron job longer than 1 minute
@@ -136,6 +140,7 @@ class Token(object):
logger.debug('WEB2PY CRON: Acquiring lock')
self.master.seek(0)
cPickle.dump((self.now, 0), self.master)
self.master.flush()
finally:
portalocker.unlock(self.master)
if not ret:
@@ -246,6 +251,7 @@ class cronlauncher(threading.Thread):
shell=self.shell)
_cron_subprocs.append(proc)
(stdoutdata, stderrdata) = proc.communicate()
_cron_subprocs.remove(proc)
if proc.returncode != 0:
logger.warning(
'WEB2PY CRON Call returned code %s:\n%s' %
@@ -303,7 +309,11 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
for task in tasks:
if _cron_stopping:
break
commands = [sys.executable]
if sys.executable.lower().endswith('pythonservice.exe'):
_python_exe = os.path.join(sys.exec_prefix, 'python.exe')
else:
_python_exe = sys.executable
commands = [_python_exe]
w2p_path = fileutils.abspath('web2py.py', gluon=True)
if os.path.exists(w2p_path):
commands.append(w2p_path)
-1
View File
@@ -6,7 +6,6 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
import os
import sys
import socket
import platform
from storage import Storage
+8 -8
View File
@@ -2073,14 +2073,13 @@ class SQLFORM(FORM):
order = request.vars.order or ''
if sortable:
if order and not order == 'None':
if order[:1] == '~':
sign, rorder = '~', order[1:]
otablename, ofieldname = order.split('~')[-1].split('.', 1)
sort_field = db[otablename][ofieldname]
exception = sort_field.type in ('date', 'datetime', 'time')
if exception:
orderby = (order[:1] == '~' and sort_field) or ~sort_field
else:
sign, rorder = '', order
tablename, fieldname = rorder.split('.', 1)
orderby = db[tablename][fieldname]
if sign == '~':
orderby = ~orderby
orderby = (order[:1] == '~' and ~sort_field) or sort_field
expcolumns = [str(f) for f in columns]
if export_type.endswith('with_hidden_cols'):
@@ -2095,7 +2094,8 @@ class SQLFORM(FORM):
try:
dbset = dbset(SQLFORM.build_query(
fields, request.vars.get('keywords', '')))
rows = dbset.select(cacheable=True, *expcolumns)
rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *expcolumns)
except Exception, e:
response.flash = T('Internal Error')
rows = []
+2 -2
View File
@@ -845,9 +845,9 @@ def render(content="hello world",
'hello world'
>>> render(content='abc')
'abc'
>>> render(content='abc\\'')
>>> render(content='abc\'')
"abc'"
>>> render(content='a"\\'bc')
>>> render(content='a"\'bc')
'a"\\'bc'
>>> render(content='a\\nbc')
'a\\nbc'
+109 -61
View File
@@ -1304,8 +1304,9 @@ class Auth(object):
else:
raise HTTP(404)
def navbar(self, mode='Default', action=None, prefix='Welcome',
referrer_actions=DEFAULT, user_identifier=DEFAULT):
def navbar(self, prefix='Welcome', action=None,
separators=(' [ ', ' | ', ' ] '), user_identifier=DEFAULT,
referrer_actions=DEFAULT, mode='default'):
""" Navbar with support for more templates
This uses some code from the old navbar.
@@ -1490,21 +1491,21 @@ class Auth(object):
'bare': bare
} # Define custom modes.
try:
if mode in options and callable(options[mode]):
options[mode]()
except KeyError: # KeyError if mode is not in options (do Default)
else:
s1, s2, s3 = separators
if self.user_id:
self.bar = SPAN(prefix, user_identifier, '[',
self.bar = SPAN(prefix, user_identifier, s1,
Anr(items[0]['name'],
_href=items[0]['href']), ']',
_href=items[0]['href']), s3,
_class='auth_navbar')
else:
self.bar = SPAN('[', Anr(items[0]['name'],
_href=items[0]['href']), ']',
self.bar = SPAN(s1, Anr(items[0]['name'],
_href=items[0]['href']), s3,
_class='auth_navbar')
del items[0]
for item in items:
self.bar.insert(-1, ']')
self.bar.insert(-1, s2)
self.bar.insert(-1, Anr(item['name'], _href=item['href']))
return self.bar
@@ -1522,7 +1523,8 @@ class Auth(object):
tables,
archive_db=None,
archive_names='%(tablename)s_archive',
current_record='current_record'):
current_record='current_record',
current_record_label=None):
"""
to enable full record versioning (including auth tables):
@@ -1549,6 +1551,8 @@ class Auth(object):
does automatically.
"""
current_record_label = current_record_label or current.T(
current_record.replace('_',' ').title())
for table in tables:
fieldnames = table.fields()
if ('id' in fieldnames and
@@ -1557,7 +1561,8 @@ class Auth(object):
table._enable_record_versioning(
archive_db=archive_db,
archive_name=archive_names,
current_record=current_record)
current_record=current_record,
current_record_label=current_record_label)
def define_signature(self):
db = self.db
@@ -2157,12 +2162,17 @@ class Auth(object):
elif 'username' in table_user.fields:
username = 'username'
else:
username = 'email'
username = 'email'
settings = self.settings
if 'username' in table_user.fields or \
not self.settings.login_email_validate:
not settings.login_email_validate:
tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
if not settings.username_case_sensitive:
tmpvalidator = [IS_LOWER(), tmpvalidator]
else:
tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email)
if not settings.email_case_sensitive:
tmpvalidator = [IS_LOWER(), tmpvalidator]
old_requires = table_user[username].requires
table_user[username].requires = tmpvalidator
@@ -2170,7 +2180,7 @@ class Auth(object):
response = current.response
session = current.session
passfield = self.settings.password_field
passfield = settings.password_field
try:
table_user[passfield].requires[-1].min_length = 0
except:
@@ -2186,43 +2196,43 @@ class Auth(object):
if next is DEFAULT:
# important for security
next = self.settings.login_next
next = settings.login_next
user_next = snext
if user_next:
external = user_next.split('://')
if external[0].lower() in ['http', 'https', 'ftp']:
host_next = user_next.split('//', 1)[-1].split('/')[0]
if host_next in self.settings.cas_domains:
if host_next in settings.cas_domains:
next = user_next
else:
next = user_next
if onvalidation is DEFAULT:
onvalidation = self.settings.login_onvalidation
onvalidation = settings.login_onvalidation
if onaccept is DEFAULT:
onaccept = self.settings.login_onaccept
onaccept = settings.login_onaccept
if log is DEFAULT:
log = self.messages['login_log']
onfail = self.settings.login_onfail
onfail = settings.login_onfail
user = None # default
# do we use our own login form, or from a central source?
if self.settings.login_form == self:
if settings.login_form == self:
form = SQLFORM(
table_user,
fields=[username, passfield],
hidden=dict(_next=next),
showid=self.settings.showid,
showid=settings.showid,
submit_button=self.messages.login_button,
delete_label=self.messages.delete_label,
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
formstyle=settings.formstyle,
separator=settings.label_separator
)
if self.settings.remember_me_form:
if settings.remember_me_form:
## adds a new input checkbox "remember me for longer"
if self.settings.formstyle != 'bootstrap':
if settings.formstyle != 'bootstrap':
addrow(form, XML("&nbsp;"),
DIV(XML("&nbsp;"),
INPUT(_type='checkbox',
@@ -2235,9 +2245,9 @@ class Auth(object):
self.messages.label_remember_me,
_for="auth_user_remember",
)), "",
self.settings.formstyle,
settings.formstyle,
'auth_user_remember__row')
elif self.settings.formstyle == 'bootstrap':
elif settings.formstyle == 'bootstrap':
addrow(form,
"",
LABEL(
@@ -2247,20 +2257,20 @@ class Auth(object):
self.messages.label_remember_me,
_class="checkbox"),
"",
self.settings.formstyle,
settings.formstyle,
'auth_user_remember__row')
captcha = self.settings.login_captcha or \
(self.settings.login_captcha != False and self.settings.captcha)
captcha = settings.login_captcha or \
(settings.login_captcha != False and settings.captcha)
if captcha:
addrow(form, captcha.label, captcha, captcha.comment,
self.settings.formstyle, 'captcha__row')
settings.formstyle, 'captcha__row')
accepted_form = False
if form.accepts(request, session,
formname='login', dbio=False,
onvalidation=onvalidation,
hideerror=self.settings.hideerror):
hideerror=settings.hideerror):
accepted_form = True
# check for username in db
@@ -2282,36 +2292,36 @@ class Auth(object):
# try alternate logins 1st as these have the
# current version of the password
user = None
for login_method in self.settings.login_methods:
for login_method in settings.login_methods:
if login_method != self and \
login_method(request.vars[username],
request.vars[passfield]):
if not self in self.settings.login_methods:
if not self in settings.login_methods:
# do not store password in db
form.vars[passfield] = None
user = self.get_or_create_user(
form.vars, self.settings.update_fields)
form.vars, settings.update_fields)
break
if not user:
# alternates have failed, maybe because service inaccessible
if self.settings.login_methods[0] == self:
if settings.login_methods[0] == self:
# try logging in locally using cached credentials
if form.vars.get(passfield, '') == temp_user[passfield]:
# success
user = temp_user
else:
# user not in db
if not self.settings.alternate_requires_registration:
if not settings.alternate_requires_registration:
# we're allowed to auto-register users from external systems
for login_method in self.settings.login_methods:
for login_method in settings.login_methods:
if login_method != self and \
login_method(request.vars[username],
request.vars[passfield]):
if not self in self.settings.login_methods:
if not self in settings.login_methods:
# do not store password in db
form.vars[passfield] = None
user = self.get_or_create_user(
form.vars, self.settings.update_fields)
form.vars, settings.update_fields)
break
if not user:
self.log_event(self.messages['login_failed_log'],
@@ -2321,25 +2331,25 @@ class Auth(object):
callback(onfail, None)
redirect(
self.url(args=request.args, vars=request.get_vars),
client_side=self.settings.client_side)
client_side=settings.client_side)
else:
# use a central authentication server
cas = self.settings.login_form
cas = settings.login_form
cas_user = cas.get_user()
if cas_user:
cas_user[passfield] = None
user = self.get_or_create_user(
table_user._filter_fields(cas_user),
self.settings.update_fields)
settings.update_fields)
elif hasattr(cas, 'login_form'):
return cas.login_form()
else:
# we need to pass through login again before going on
next = self.url(self.settings.function, args='login')
next = self.url(settings.function, args='login')
redirect(cas.login_url(next),
client_side=self.settings.client_side)
client_side=settings.client_side)
# process authenticated users
if user:
@@ -2349,20 +2359,20 @@ class Auth(object):
self.login_user(user)
session.auth.expiration = \
request.vars.get('remember', False) and \
self.settings.long_expiration or \
self.settings.expiration
settings.long_expiration or \
settings.expiration
session.auth.remember = 'remember' in request.vars
self.log_event(log, user)
session.flash = self.messages.logged_in
# how to continue
if self.settings.login_form == self:
if settings.login_form == self:
if accepted_form:
callback(onaccept, form)
if next == session._auth_next:
session._auth_next = None
next = replace_id(next, form)
redirect(next, client_side=self.settings.client_side)
redirect(next, client_side=settings.client_side)
table_user[username].requires = old_requires
return form
@@ -2371,7 +2381,7 @@ class Auth(object):
if next == session._auth_next:
del session._auth_next
redirect(next, client_side=self.settings.client_side)
redirect(next, client_side=settings.client_side)
def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT):
"""
@@ -3584,7 +3594,8 @@ class Auth(object):
templates=None,
migrate=True,
controller=None,
function=None):
function=None,
force_render=False):
if controller and function: resolve = False
@@ -3608,7 +3619,7 @@ class Auth(object):
if resolve:
action = str(current.request.args(0)).startswith("_")
if slug and not action:
wiki = self._wiki.read(slug)
wiki = self._wiki.read(slug,force_render)
if isinstance(wiki, dict) and wiki.has_key('content'):
# We don't want to return a dict object, just the wiki
wiki = wiki['content']
@@ -5189,13 +5200,18 @@ class Wiki(object):
controller, function, args = items[0], items[1], items[2:]
return LOAD(controller, function, args=args, ajax=True).xml()
def get_render(self):
def get_renderer(self):
if isinstance(self.settings.render, basestring):
r = getattr(self, "%s_render" % self.settings.render)
elif callable(self.settings.render):
r = self.settings.render
elif isinstance(self.settings.render, dict):
return lambda page: self.settings.render.get(page.render,
getattr(self,
"%s_render" % (page.render or 'markmin')))(page)
else:
raise ValueError("Invalid render type %s" % type(render))
raise ValueError(
"Invalid render type %s" % type(self.settings.render))
return r
def __init__(self, auth, env=None, render='markmin',
@@ -5206,7 +5222,24 @@ class Wiki(object):
settings = self.settings = auth.settings.wiki
# render: "markmin", "html", ..., <function>
"""render argument options:
- "markmin"
- "html"
- <function>
Sets a custom render function
- dict(html=<function>, markmin=...):
dict(...) allows multiple custom render functions
- "multiple"
Is the same as {}. It enables per-record formats
using builtins
"""
engines = set(['markmin', 'html'])
show_engine = False
if render == "multiple":
render = {}
if isinstance(render, dict):
[engines.add(key) for key in render]
show_engine = True
settings.render = render
perms = settings.manage_permissions = manage_permissions
@@ -5250,8 +5283,13 @@ class Wiki(object):
default=[Wiki.everybody]),
Field('changelog'),
Field('html', 'text',
compute=self.get_render(),
compute=self.get_renderer(),
readable=False, writable=False),
Field('render', default="markmin",
readable=show_engine,
writable=show_engine,
requires=IS_EMPTY_OR(
IS_IN_SET(engines))),
auth.signature],
'vars':{'format':'%(title)s', 'migrate':migrate}}),
('wiki_tag', {
@@ -5403,7 +5441,7 @@ class Wiki(object):
elif zero == '_cloud':
return self.cloud()
elif zero == '_preview':
return self.preview(self.get_render())
return self.preview(self.get_renderer())
def first_paragraph(self, page):
if not self.can_read(page):
@@ -5417,7 +5455,7 @@ class Wiki(object):
def fix_hostname(self, body):
return (body or '').replace('://HOSTNAME', '://%s' % self.host)
def read(self, slug):
def read(self, slug, force_render=False):
if slug in '_cloud':
return self.cloud()
elif slug in '_search':
@@ -5432,10 +5470,12 @@ class Wiki(object):
url = URL(args=('_edit', slug))
return dict(content=A('Create page "%s"' % slug, _href=url, _class="btn"))
else:
html = page.html if not force_render else self.get_renderer(page)
content = XML(self.fix_hostname(html))
return dict(title=page.title,
slug=page.slug,
page=page,
content=XML(self.fix_hostname(page.html)),
content=content,
tags=page.tags,
created_on=page.created_on,
modified_on=page.modified_on)
@@ -5524,7 +5564,11 @@ class Wiki(object):
if (prevbutton.hasClass('nopreview')) {
prevbutton.addClass('preview').removeClass(
'nopreview').html('Edit Source');
web2py_ajax_page('post', '%(url)s', {body : jQuery('#wiki_page_body').val()}, 'preview');
try{var wiki_render = jQuery('#wiki_page_render').val()}
catch(e){var wiki_render = null;}
web2py_ajax_page('post', \
'%(url)s', {body: jQuery('#wiki_page_body').val(), \
render: wiki_render}, 'preview');
form.fadeOut('fast', function() {preview.fadeIn()});
} else {
prevbutton.addClass(
@@ -5798,6 +5842,10 @@ class Wiki(object):
def preview(self, render):
request = current.request
# FIXME: This is an ugly hack to ensure a default render
# engine if not specified (with multiple render engines)
if not "render" in request.post_vars:
request.post_vars.render = None
return render(request.post_vars)
+1 -3
View File
@@ -11,8 +11,6 @@ This file specifically includes utilities for security.
import threading
import struct
#import hashlib
#import hmac
import uuid
import random
import time
@@ -23,7 +21,7 @@ import logging
import socket
import base64
import zlib
from types import ModuleType
_struct_2_long_long = struct.Struct('=QQ')
+53 -15
View File
@@ -36,6 +36,7 @@ class Service(win32serviceutil.ServiceFramework):
_svc_name_ = '_unNamed'
_svc_display_name_ = '_Service Template'
_svc_description_ = '_Service Template description'
def __init__(self, *args):
win32serviceutil.ServiceFramework.__init__(self, *args)
@@ -44,6 +45,12 @@ class Service(win32serviceutil.ServiceFramework):
def log(self, msg):
servicemanager.LogInfoMsg(str(msg))
def log_error(self, msg):
"""
Log an error message to windows application event log.
"""
servicemanager.LogErrorMsg(str(msg))
def SvcDoRun(self):
self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
try:
@@ -80,6 +87,7 @@ class Web2pyService(Service):
_svc_name_ = 'web2py'
_svc_display_name_ = 'web2py Service'
_svc_description_ = 'Web2py application framework service'
_exe_args_ = 'options'
server = None
@@ -132,17 +140,16 @@ class Web2pyService(Service):
server_name=options.server_name,
request_queue_size=options.request_queue_size,
timeout=options.timeout,
socket_timeout=options.socket_timeout,
shutdown_timeout=options.shutdown_timeout,
path=options.folder
path=options.folder,
interfaces=options.interfaces
)
try:
from rewrite import load
load()
self.server.start()
except:
# self.server.stop()
self.server = None
raise
@@ -159,12 +166,18 @@ class Web2pyCronService(Web2pyService):
_svc_name_ = 'web2py_cron'
_svc_display_name_ = 'web2py Cron Service'
_svc_description_ = 'web2py Windows cron service for scheduled tasks'
_exe_args_ = 'options'
def start(self):
import newcron
import global_settings
self.log('web2py server starting')
import logging
import logging.config
from settings import global_settings
from fileutils import abspath
from os.path import exists, join
self.log('web2py Cron service starting')
if not self.chdir():
return
if len(sys.argv) == 2:
@@ -172,25 +185,50 @@ class Web2pyCronService(Web2pyService):
else:
opt_mod = self._exe_args_
options = __import__(opt_mod, [], [], '')
global_settings.global_settings.web2py_crontype = 'external'
logpath = abspath(join(options.folder, "logging.conf"))
if exists(logpath):
logging.config.fileConfig(logpath)
else:
logging.basicConfig()
logger = logging.getLogger("web2py.cron")
global_settings.web2py_crontype = 'external'
if options.scheduler: # -K
apps = [app.strip() for app in options.scheduler.split(
',') if check_existent_app(options, app.strip())]
else:
apps = None
self.extcron = newcron.extcron(options.folder, apps=apps)
try:
self.extcron.start()
except:
# self.server.stop()
self.extcron = None
raise
misfire_gracetime = float(options.misfire_gracetime) if 'misfire_gracetime' in dir(options) else 0.0
logger.info('Starting Window cron service with %0.2f secs gracetime.' % misfire_gracetime)
self._started = True
wait_full_min = lambda: 60 - time.time() % 60
while True:
try:
if wait_full_min() >= misfire_gracetime: # an offset of max. 5 secs before full minute (e.g. time.sleep(60) == 58.99 secs)
self.extcron = newcron.extcron(options.folder, apps=apps)
self.extcron.start()
time.sleep(wait_full_min())
else:
logger.debug('time.sleep() offset detected: %0.3f s' % wait_full_min())
while wait_full_min() <= misfire_gracetime:
pass
if apps != None:
break
if not self._started:
break
except Exception, ex:
self.extcron = None
self.log_error('%s, restarting service.' % ex)
logger.exception('Exception! Restarting Windows cron service.' % ex)
self.start()
def stop(self):
self.log('web2py cron stopping')
self.log('web2py Cron service stopping')
if not self.chdir():
return
if self.extcron:
self._started = False
self.extcron.join()
def register_service_handler(argv=None, opt_file='options', cls=Web2pyService):
@@ -453,10 +453,9 @@ 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
mv handlers/wsgihandler.py wsgihandler.py
sudo -u www-data python -c "from gluon.main import save_password; save_password('$PW',443)"
/etc/init.d/redmine start
start uwsgi-emperor
+1
View File
@@ -159,6 +159,7 @@ fi
wget http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
chown -R apache:apache web2py
###
+1
View File
@@ -159,6 +159,7 @@ fi
wget http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
chown -R apache:apache web2py
###
+43 -43
View File
@@ -3,53 +3,53 @@
echo 'setup-web2py-nginx-uwsgi-centos64.sh'
echo 'Support CentOS 6.4'
echo 'Installs Nginx 1.4.1 + uWSGI + Web2py'
# Get Web2py Admin Password
echo -e "Web2py Admin Password: \c "
read PW
echo -e "Set Server Name Ex: web2py.domain.com : \c "
read SERVER_FQDN
echo -e "Set Server IP: \c "
read SERVER_IP
echo "" >>/etc/hosts
echo "$SERVER_IP $SERVER_FQDN" >>/etc/hosts
yum update -y
yum install -y http://mirror-fpt-telecom.fpt.net/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
yum clean all
yum install -y gcc libxml2-devel python-devel python-pip PyXML unzip make sudo
## 64Bits System
## yum install -y http://nginx.org/packages/rhel/6/x86_64/RPMS/nginx-1.4.1-1.el6.ngx.x86_64.rpm
## yum install -y http://nginx.org/packages/rhel/6/x86_64/RPMS/nginx-1.4.1-1.el6.ngx.x86_64.rpm
yum install -y http://nginx.org/packages/rhel/6/i386/RPMS/nginx-1.4.1-1.el6.ngx.i386.rpm
pip-python install --upgrade pip
PIPPATH=`which pip`
$PIPPATH install --upgrade uwsgi
# Prepare folders for uwsgi
mkdir /etc/uwsgi
mkdir /var/log/uwsgi
mkdir -p /var/www/
#usermod -a -G apache nginx
mkdir -p /etc/nginx/ssl/
cd /etc/nginx/ssl
openssl genrsa 1024 > web2py.key && chmod 400 web2py.key
openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
echo 'server {
listen YOUR_SERVER_IP:80;
server_name YOUR_SERVER_FQDN;
@@ -97,13 +97,13 @@ server {
#client_max_body_size 10m;
###
}
}' >/etc/nginx/conf.d/web2py.conf
sed -i "s/YOUR_SERVER_IP/$SERVER_IP/" /etc/nginx/conf.d/web2py.conf
sed -i "s/YOUR_SERVER_FQDN/$SERVER_FQDN/" /etc/nginx/conf.d/web2py.conf
# Create configuration file /etc/uwsgi/web2py.ini
echo '[uwsgi]
@@ -126,21 +126,21 @@ cron = 0 0 -1 -1 -1 python /var/www/web2py/web2py.py -Q -S welcome -M -R scripts
no-orphans = true
chmod-socket = 666
' >/etc/uwsgi/web2py.ini
cd /var/www/
curl --progress -O http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip && rm -rf web2py_src.zip
# Download latest version of sessions2trash.py
curl --output /var/www/web2py/scripts/sessions2trash.py http://web2py.googlecode.com/hg/scripts/sessions2trash.py
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
chown -R nginx:nginx web2py
cd /var/www/web2py
sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)"
## Daemons /start/stop
echo '#!/bin/sh
# Autor: Nilton OS -- www.linuxpro.com.br
#
@@ -154,27 +154,27 @@ echo '#!/bin/sh
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
### END INIT INFO
# Source function library.
. /etc/rc.d/init.d/functions
# Check for missing binaries (stale symlinks should not happen)
UWSGI_BIN=`which uwsgi`
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
if [ "$1" = "stop" ]; then exit 0;
else exit 5; fi; }
UWSGI_EMPEROR_MODE=true
UWSGI_VASSALS="/etc/uwsgi/"
UWSGI_OPTIONS="--enable-threads --logto /var/log/uwsgi/uwsgi.log"
lockfile=/var/lock/subsys/uwsgi
UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"
if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
fi
case "$1" in
start)
echo -n "Starting uWSGI "
@@ -198,19 +198,19 @@ case "$1" in
;;
esac
exit 0 '> /etc/init.d/uwsgi
chmod +x /etc/init.d/uwsgi
/etc/init.d/uwsgi start
/etc/init.d/nginx start
/etc/init.d/iptables stop
chkconfig --del iptables
chkconfig --levels 235 uwsgi on
chkconfig --levels 235 nginx on
## you can reload uwsgi with
#/etc/init.d/uwsgi restart
## to reload web2py only (without restarting uwsgi)
# touch /etc/uwsgi/web2py.ini
# touch /etc/uwsgi/web2py.ini
@@ -122,6 +122,7 @@ mkdir ./web-apps
cd ./web-apps
curl -O http://www.web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
echo "Set the ownership for web2py application to uwsgi"
chown -R uwsgi /opt/web-apps/web2py
+11 -12
View File
@@ -71,7 +71,7 @@ server {
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
}
}' >/etc/nginx/vhosts.d/web2py.conf
@@ -102,8 +102,7 @@ cd /srv/www/
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 /srv/www/web2py/scripts/sessions2trash.py
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
chown -R nginx:www web2py
cd /srv/www/web2py
sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)"
@@ -137,7 +136,7 @@ echo '#!/bin/sh
# Check for missing binaries (stale symlinks should not happen)
UWSGI_BIN=/usr/bin/uwsgi
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
if [ "$1" = "stop" ]; then exit 0;
else exit 5; fi; }
@@ -150,13 +149,13 @@ UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"
if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
fi
. /etc/rc.status
rc_reset
case "$1" in
fi
. /etc/rc.status
rc_reset
case "$1" in
start)
echo -n "Starting uWSGI "
/sbin/startproc $UWSGI_BIN $UWSGI_OPTIONS
@@ -219,7 +218,7 @@ chmod +x /etc/init.d/uwsgi
chkconfig --add uwsgi
chkconfig --add nginx
## you can reload uwsgi with
#/etc/init.d/uwsgi restart
## to reload web2py only (without restarting uwsgi)
+1 -2
View File
@@ -169,9 +169,8 @@ mkdir /home/www-data
cd /home/www-data
wget http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
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)"
+1
View File
@@ -59,6 +59,7 @@ cd www-data
rm web2py_src.zip*
wget http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
mv web2py/handlers/wsgihandler.py web2py/wsgihandler.py
chown -R www-data:www-data web2py
echo "setting up apache modules"