Merge pull request #512 from niphlod/issue/1980

fixes issue 1980
This commit is contained in:
mdipierro
2014-10-16 15:48:55 -05:00
+99 -67
View File
@@ -14,13 +14,18 @@ Holds:
""" """
import datetime
import urllib
import re
import cStringIO
import os import os
from gluon.http import HTTP from gluon.http import HTTP, redirect
from gluon.html import XmlComponent from gluon.html import XmlComponent, truncate_string
from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT, P from gluon.html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG
from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, DEFAULT_PASSWORD_DISPLAY from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
from gluon.html import URL, truncate_string, FIELDSET from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
from gluon.dal import DAL, Field from gluon.dal import DAL, Field
from gluon.dal.base import DEFAULT from gluon.dal.base import DEFAULT
from gluon.dal.objects import Table, Row, Expression from gluon.dal.objects import Table, Row, Expression
@@ -29,16 +34,12 @@ from gluon.dal.helpers.methods import smart_query, bar_encode, sqlhtml_validator
from gluon.dal.helpers.classes import Reference, SQLCustomType from gluon.dal.helpers.classes import Reference, SQLCustomType
from gluon.storage import Storage from gluon.storage import Storage
from gluon.utils import md5_hash from gluon.utils import md5_hash
from gluon.validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \ from gluon.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 from gluon.validators import IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE
from gluon.validators import IS_STRONG
import gluon.serializers as serializers import gluon.serializers as serializers
import datetime
import urllib
import re
import cStringIO
from gluon.globals import current from gluon.globals import current
from gluon.http import redirect
try: try:
import gluon.settings as settings import gluon.settings as settings
@@ -49,7 +50,7 @@ widget_class = re.compile('^\w*')
def add_class(a, b): def add_class(a, b):
return a+' '+b if a else b return a + ' ' + b if a else b
def represent(field, value, record): def represent(field, value, record):
@@ -85,11 +86,11 @@ def show_if(cond):
if not cond: if not cond:
return None return None
base = "%s_%s" % (cond.first.tablename, cond.first.name) base = "%s_%s" % (cond.first.tablename, cond.first.name)
if ((cond.op.__name__ == 'EQ' and cond.second == True) or if ((cond.op.__name__ == 'EQ' and cond.second is True) or
(cond.op.__name__ == 'NE' and cond.second == False)): (cond.op.__name__ == 'NE' and cond.second is False)):
return base, ":checked" return base, ":checked"
if ((cond.op.__name__ == 'EQ' and cond.second == False) or if ((cond.op.__name__ == 'EQ' and cond.second is False) or
(cond.op.__name__ == 'NE' and cond.second == True)): (cond.op.__name__ == 'NE' and cond.second is True)):
return base, ":not(:checked)" return base, ":not(:checked)"
if cond.op.__name__ == 'EQ': if cond.op.__name__ == 'EQ':
return base, "[value='%s']" % cond.second return base, "[value='%s']" % cond.second
@@ -305,7 +306,8 @@ class ListWidget(StringWidget):
_class = 'string' _class = 'string'
requires = field.requires if isinstance( requires = field.requires if isinstance(
field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None
if isinstance(value, str): value = [value] if isinstance(value, str):
value = [value]
nvalue = value or [''] nvalue = value or ['']
items = [LI(INPUT(_id=_id, _class=_class, _name=_name, items = [LI(INPUT(_id=_id, _class=_class, _name=_name,
value=v, hideerror=k < len(nvalue) - 1, value=v, hideerror=k < len(nvalue) - 1,
@@ -351,7 +353,6 @@ class RadioWidget(OptionsWidget):
else: else:
value = str(value) value = str(value)
attr = cls._attributes(field, {}, **attributes) attr = cls._attributes(field, {}, **attributes)
attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget') attr['_class'] = add_class(attr.get('_class'), 'web2py_radiowidget')
@@ -626,7 +627,7 @@ class AutocompleteWidget(object):
self.help_fields = help_fields or [] self.help_fields = help_fields or []
self.help_string = help_string self.help_string = help_string
if self.help_fields and not self.help_string: if self.help_fields and not self.help_string:
self.help_string = ' '.join('%%(%s)s'%f.name self.help_string = ' '.join('%%(%s)s' % f.name
for f in self.help_fields) for f in self.help_fields)
self.request = request self.request = request
@@ -900,7 +901,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
label['_for'] = None label['_for'] = None
label.insert(0, controls) label.insert(0, controls)
_controls = DIV(DIV(label, _help, _class="checkbox"), _controls = DIV(DIV(label, _help, _class="checkbox"),
_class="%s %s" % (offset_class, col_class)) _class="%s %s" % (offset_class, col_class))
label = '' label = ''
elif isinstance(controls, SELECT): elif isinstance(controls, SELECT):
controls.add_class('form-control') controls.add_class('form-control')
@@ -1132,7 +1133,7 @@ class SQLFORM(FORM):
if fieldname.find('.') >= 0: if fieldname.find('.') >= 0:
continue continue
field = (self.table[fieldname] if fieldname in self.table.fields field = (self.table[fieldname] if fieldname in self.table.fields
else self.extra_fields[fieldname]) else self.extra_fields[fieldname])
comment = None comment = None
@@ -1184,7 +1185,7 @@ class SQLFORM(FORM):
cond = readonly or \ cond = readonly or \
(not ignore_rw and not field.writable and field.readable) (not ignore_rw and not field.writable and field.readable)
if default is not None and not cond: if default is not None and not cond:
default = field.formatter(default) default = field.formatter(default)
dspval = default dspval = default
@@ -1192,10 +1193,6 @@ class SQLFORM(FORM):
if cond: if cond:
# ## if field.re field.requires = sqlhtml_validators(field) field.requires = sqlhtml_validators(field)present is available else
# ## ignore blob and preview uploaded images
# ## format everything else
if field.represent: if field.represent:
inp = represent(field, default, record) inp = represent(field, default, record)
elif field.type in ['blob']: elif field.type in ['blob']:
@@ -1272,7 +1269,7 @@ class SQLFORM(FORM):
(olname.replace('.', '__') + SQLFORM.ID_ROW_SUFFIX, (olname.replace('.', '__') + SQLFORM.ID_ROW_SUFFIX,
'', widget, col3.get(olname, ''))) '', widget, col3.get(olname, '')))
self.custom.linkto[olname.replace('.', '__')] = widget self.custom.linkto[olname.replace('.', '__')] = widget
# </block> # </block>
# when deletable, add delete? checkbox # when deletable, add delete? checkbox
self.custom.delete = self.custom.deletable = '' self.custom.delete = self.custom.deletable = ''
@@ -1291,16 +1288,15 @@ class SQLFORM(FORM):
) )
xfields.append( xfields.append(
(self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX, (self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX,
LABEL( LABEL(
T(delete_label), sep, T(delete_label), sep,
_for=self.FIELDKEY_DELETE_RECORD, _for=self.FIELDKEY_DELETE_RECORD,
_id=self.FIELDKEY_DELETE_RECORD + \ _id=self.FIELDKEY_DELETE_RECORD +
SQLFORM.ID_LABEL_SUFFIX), SQLFORM.ID_LABEL_SUFFIX),
widget, widget,
col3.get(self.FIELDKEY_DELETE_RECORD, ''))) col3.get(self.FIELDKEY_DELETE_RECORD, '')))
self.custom.delete = self.custom.deletable = widget self.custom.delete = self.custom.deletable = widget
# when writable, add submit button # when writable, add submit button
self.custom.submit = '' self.custom.submit = ''
if not readonly: if not readonly:
@@ -1480,7 +1476,7 @@ class SQLFORM(FORM):
# that does not pass validation, yet it should be deleted # that does not pass validation, yet it should be deleted
for fieldname in self.fields: for fieldname in self.fields:
field = (self.table[fieldname] field = (self.table[fieldname]
if fieldname in self.table.fields if fieldname in self.table.fields
else self.extra_fields[fieldname]) else self.extra_fields[fieldname])
### this is a workaround! widgets should always have default not None! ### this is a workaround! widgets should always have default not None!
@@ -1995,6 +1991,18 @@ class SQLFORM(FORM):
details = details and not groupby details = details and not groupby
rows = None rows = None
# see issue 1980. Basically we can have keywords in get_vars
# (i.e. when the search term is propagated through page=2&keywords=abc)
# but if there is keywords in post_vars (i.e. POSTing a search request)
# the one in get_vars should be replaced by the new one
keywords = ''
if 'keywords' in request.post_vars:
keywords = request.post_vars.keywords
elif 'keywords' in request.get_vars:
keywords = request.get_vars.keywords
print 'keyworkds are', keywords
def fetch_count(dbset): def fetch_count(dbset):
##FIXME for google:datastore cache_count is ignored ##FIXME for google:datastore cache_count is ignored
## if it's not an integer ## if it's not an integer
@@ -2051,7 +2059,7 @@ class SQLFORM(FORM):
'/'.join(str(a) for a in args) == '/'.join(request.args) or '/'.join(str(a) for a in args) == '/'.join(request.args) or
URL.verify(request, user_signature=user_signature, URL.verify(request, user_signature=user_signature,
hash_vars=False) or hash_vars=False) or
(request.args(len(args))=='view' and not logged)): (request.args(len(args))=='view' and not logged)):
session.flash = T('not authorized') session.flash = T('not authorized')
redirect(referrer) redirect(referrer)
@@ -2096,8 +2104,8 @@ class SQLFORM(FORM):
else: else:
fields = [] fields = []
columns = [] columns = []
filter1 = lambda f:isinstance(f, Field) filter1 = lambda f: isinstance(f, Field)
filter2 = lambda f:isinstance(f, Field) and f.readable filter2 = lambda f: isinstance(f, Field) and f.readable
for table in tables: for table in tables:
fields += filter(filter1, table) fields += filter(filter1, table)
columns += filter(filter2, table) columns += filter(filter2, table)
@@ -2111,15 +2119,18 @@ class SQLFORM(FORM):
if groupby is None: if groupby is None:
field_id = tables[0]._id field_id = tables[0]._id
elif groupby and isinstance(groupby, Field): elif groupby and isinstance(groupby, Field):
field_id = groupby #take the field passed as groupby #take the field passed as groupby
field_id = groupby
elif groupby and isinstance(groupby, Expression): elif groupby and isinstance(groupby, Expression):
field_id = groupby.first #take the first groupby field #take the first groupby field
while not(isinstance(field_id, Field)): # Navigate to the first Field of the expression field_id = groupby.first
while not(isinstance(field_id, Field)):
# Navigate to the first Field of the expression
field_id = field_id.first field_id = field_id.first
table = field_id.table table = field_id.table
tablename = table._tablename tablename = table._tablename
if not any(str(f) == str(field_id) for f in fields): if not any(str(f) == str(field_id) for f in fields):
fields = [f for f in fields]+[field_id] fields = [f for f in fields] + [field_id]
if upload == '<default>': if upload == '<default>':
upload = lambda filename: url(args=['download', filename]) upload = lambda filename: url(args=['download', filename])
if request.args(-2) == 'download': if request.args(-2) == 'download':
@@ -2283,8 +2294,10 @@ class SQLFORM(FORM):
expcolumns = [str(f) for f in columns] expcolumns = [str(f) for f in columns]
selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)] selectable_columns = [str(f) for f in columns if not isinstance(f, Field.Virtual)]
if export_type.endswith('with_hidden_cols'): if export_type.endswith('with_hidden_cols'):
#expcolumns = [] start with the visible columns, which includes visible virtual fields # expcolumns = [] start with the visible columns, which
selectable_columns = [] #like expcolumns but excluding virtual # includes visible virtual fields
selectable_columns = []
#like expcolumns but excluding virtual
for table in tables: for table in tables:
for field in table: for field in table:
if field.readable and field.tablename in tablenames: if field.readable and field.tablename in tablenames:
@@ -2292,19 +2305,21 @@ class SQLFORM(FORM):
expcolumns.append(str(field)) expcolumns.append(str(field))
if not(isinstance(field, Field.Virtual)): if not(isinstance(field, Field.Virtual)):
selectable_columns.append(str(field)) selectable_columns.append(str(field))
#look for virtual fields not displayed (and virtual method fields to be added here?) #look for virtual fields not displayed (and virtual method
#fields to be added here?)
for (field_name, field) in table.iteritems(): for (field_name, field) in table.iteritems():
if isinstance(field, Field.Virtual) and not str(field) in expcolumns: if isinstance(field, Field.Virtual) and not str(field) in expcolumns:
expcolumns.append(str(field)) expcolumns.append(str(field))
if export_type in exportManager and exportManager[export_type]: if export_type in exportManager and exportManager[export_type]:
if request.vars.keywords: if keywords:
try: try:
#the query should be constructed using searchable fields but not virtual fields #the query should be constructed using searchable
#fields but not virtual fields
sfields = reduce(lambda a, b: a + b, sfields = reduce(lambda a, b: a + b,
[[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables]) [[f for f in t if f.readable and not isinstance(f, Field.Virtual)] for t in tables])
dbset = dbset(SQLFORM.build_query( dbset = dbset(SQLFORM.build_query(
sfields, request.vars.get('keywords', ''))) sfields, keywords))
rows = dbset.select(left=left, orderby=orderby, rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *selectable_columns) cacheable=True, *selectable_columns)
except Exception, e: except Exception, e:
@@ -2316,7 +2331,8 @@ class SQLFORM(FORM):
value = exportManager[export_type] value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value clazz = value[0] if hasattr(value, '__getitem__') else value
rows.colnames = expcolumns # expcolumns is all cols to be exported including virtual fields # expcolumns is all cols to be exported including virtual fields
rows.colnames = expcolumns
oExp = clazz(rows) oExp = clazz(rows)
filename = '.'.join(('rows', oExp.file_ext)) filename = '.'.join(('rows', oExp.file_ext))
response.headers['Content-Type'] = oExp.content_type response.headers['Content-Type'] = oExp.content_type
@@ -2330,8 +2346,7 @@ class SQLFORM(FORM):
elif not request.vars.records: elif not request.vars.records:
request.vars.records = [] request.vars.records = []
session['_web2py_grid_referrer_' + formname] = \ session['_web2py_grid_referrer_' + formname] = url2(vars=request.get_vars)
url2(vars=request.get_vars)
console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui) console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui)
error = None error = None
if create: if create:
@@ -2357,7 +2372,7 @@ class SQLFORM(FORM):
sfields_id = '%s_query_panel' % prefix sfields_id = '%s_query_panel' % prefix
skeywords_id = '%s_keywords' % prefix skeywords_id = '%s_keywords' % prefix
search_widget = lambda sfield, url: CAT(FORM( search_widget = lambda sfield, url: CAT(FORM(
INPUT(_name='keywords', _value=request.vars.keywords, INPUT(_name='keywords', _value=keywords,
_id=skeywords_id,_class='form-control', _id=skeywords_id,_class='form-control',
_onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id) if advanced_search else '' _onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id) if advanced_search else ''
), ),
@@ -2368,7 +2383,6 @@ class SQLFORM(FORM):
form = search_widget and search_widget(sfields, url()) or '' form = search_widget and search_widget(sfields, url()) or ''
console.append(add) console.append(add)
console.append(form) console.append(form)
keywords = request.vars.get('keywords', '')
try: try:
if callable(searchable): if callable(searchable):
subquery = searchable(sfields, keywords) subquery = searchable(sfields, keywords)
@@ -2428,7 +2442,7 @@ class SQLFORM(FORM):
elif key == ordermatch[1:]: elif key == ordermatch[1:]:
marker = sorter_icons[1] marker = sorter_icons[1]
header = A(header, marker, _href=url(vars=dict( header = A(header, marker, _href=url(vars=dict(
keywords=request.vars.keywords or '', keywords=keywords,
order=key)), cid=request.cid) order=key)), cid=request.cid)
headcols.append(TH(header, _class=ui.get('default'))) headcols.append(TH(header, _class=ui.get('default')))
@@ -2466,17 +2480,22 @@ class SQLFORM(FORM):
if paginate and dbset._db._adapter.dbengine == 'google:datastore': if paginate and dbset._db._adapter.dbengine == 'google:datastore':
cursor = request.vars.cursor or True cursor = request.vars.cursor or True
limitby = (0, paginate) limitby = (0, paginate)
try: page = int(request.vars.page or 1)-1 try:
except ValueError: page = 0 page = int(request.vars.page or 1) - 1
elif paginate and paginate<nrows: except ValueError:
try: page = int(request.vars.page or 1)-1 page = 0
except ValueError: page = 0 elif paginate and paginate < nrows:
limitby = (paginate*page, paginate*(page+1)) try:
page = int(request.vars.page or 1) - 1
except ValueError:
page = 0
limitby = (paginate * page, paginate * (page + 1))
else: else:
limitby = None limitby = None
try: try:
table_fields = [field for field in fields table_fields = [field for field in fields
if (field.tablename in tablenames and not(isinstance(field, Field.Virtual)))] if (field.tablename in tablenames and
not(isinstance(field, Field.Virtual)))]
if dbset._db._adapter.dbengine == 'google:datastore': if dbset._db._adapter.dbengine == 'google:datastore':
rows = dbset.select(left=left, orderby=orderby, rows = dbset.select(left=left, orderby=orderby,
groupby=groupby, limitby=limitby, groupby=groupby, limitby=limitby,
@@ -2514,8 +2533,15 @@ class SQLFORM(FORM):
paginator.append(LI('page %s' % (page+1))) paginator.append(LI('page %s' % (page+1)))
if next_cursor: if next_cursor:
d = dict(page=page+2, cursor=next_cursor) d = dict(page=page+2, cursor=next_cursor)
if order: d['order'] = order if order:
if request.vars.keywords: d['keywords'] = request.vars.keywords d['order'] = order
# see issue 1980, also at the top of the definition
# if keyworkds is in request.vars, we don't need to
# copy over the keywords parameter in the links for pagination
if 'keywords' in request.vars and not keywords:
d['keywords'] = ''
elif keywords:
d['keywords'] = keywords
paginator.append(LI( paginator.append(LI(
A('next', _href=url(vars=d), cid=request.cid))) A('next', _href=url(vars=d), cid=request.cid)))
elif paginate and paginate < nrows: elif paginate and paginate < nrows:
@@ -2531,8 +2557,13 @@ class SQLFORM(FORM):
d = dict(page=p + 1) d = dict(page=p + 1)
if order: if order:
d['order'] = order d['order'] = order
if request.vars.keywords: # see issue 1980, also at the top of the definition
d['keywords'] = request.vars.keywords # if keyworkds is in request.vars, we don't need to
# copy over the keywords parameter in the links for pagination
if 'keywords' in request.vars and not keywords:
d['keywords'] = ''
elif keywords:
d['keywords'] = keywords
return A(name, _href=url(vars=d), cid=request.cid) return A(name, _href=url(vars=d), cid=request.cid)
NPAGES = 5 # window is 2*NPAGES NPAGES = 5 # window is 2*NPAGES
if page > NPAGES + 1: if page > NPAGES + 1:
@@ -2660,7 +2691,8 @@ class SQLFORM(FORM):
_style='width:100%;overflow-x:auto;-ms-overflow-x:scroll') _style='width:100%;overflow-x:auto;-ms-overflow-x:scroll')
if selectable: if selectable:
if not callable(selectable): if not callable(selectable):
#now expect that selectable and related parameters are iterator (list, tuple, etc) #now expect that selectable and related parameters are
#iterator (list, tuple, etc)
inputs = [] inputs = []
for i, submit_info in enumerate(selectable): for i, submit_info in enumerate(selectable):
submit_text = submit_info[0] submit_text = submit_info[0]
@@ -2708,7 +2740,7 @@ class SQLFORM(FORM):
link = url2(vars=dict( link = url2(vars=dict(
order=request.vars.order or '', order=request.vars.order or '',
_export_type=k, _export_type=k,
keywords=request.vars.keywords or '')) keywords=keywords or ''))
export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default')) export_links.append(A(T(label), _href=link, _title=title, _class='btn btn-default'))
export_menu = \ export_menu = \
DIV(T('Export:'), _class="w2p_export_menu", *export_links) DIV(T('Export:'), _class="w2p_export_menu", *export_links)
@@ -2797,7 +2829,7 @@ class SQLFORM(FORM):
elif callable(table._format): elif callable(table._format):
return table._format(row) return table._format(row)
else: else:
return '#'+str(row.id) return '#' + str(row.id)
try: try:
nargs = len(args) + 1 nargs = len(args) + 1
previous_tablename, previous_fieldname, previous_id = \ previous_tablename, previous_fieldname, previous_id = \
@@ -2876,7 +2908,7 @@ class SQLFORM(FORM):
opts = [OPTION(T('References')+':', _value='')] opts = [OPTION(T('References')+':', _value='')]
linked = [] linked = []
if linked_tables: if linked_tables:
for item in linked_tables: for item in linked_tables:
tb = None tb = None
if isinstance(item, Table) and item._tablename in check: if isinstance(item, Table) and item._tablename in check:
tablename = item._tablename tablename = item._tablename