Merge github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2013-01-10 00:25:31 +01:00
24 changed files with 1292 additions and 487 deletions
+1 -1
View File
@@ -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):
+16 -13
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -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'
+4
View File
@@ -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), (
+4
View File
@@ -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
View File
@@ -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']:
+7 -2
View File
@@ -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()
+1 -1
View File
@@ -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">&lt;&gt;</form>')
'<form a="1" action="#" b="2" enctype="multipart/form-data" method="post">&lt;&gt;</form>')
def testH1(self):
self.assertEqual(H1('<>', _a='1', _b='2').xml(),
+35 -25
View File
@@ -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
+35
View File
@@ -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):
"""