Merge github.com:web2py/web2py
This commit is contained in:
+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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user