Package Dropbox :: Package web2py :: Package gluon :: Module sqlhtml
[hide private]
[frames] | no frames]

Source Code for Module Dropbox.web2py.gluon.sqlhtml

   1  #!/usr/bin/env python 
   2  # -*- coding: utf-8 -*- 
   3   
   4  """ 
   5  This file is part of the web2py Web Framework 
   6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
   7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
   8   
   9  Holds: 
  10   
  11  - SQLFORM: provide a form for a table (with/without record) 
  12  - SQLTABLE: provides a table for a set of records 
  13  - form_factory: provides a SQLFORM for an non-db backed table 
  14   
  15  """ 
  16  try: 
  17      from urlparse import parse_qs as psq 
  18  except ImportError: 
  19      from cgi import parse_qs as psq 
  20  import os 
  21  from http import HTTP 
  22  from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT 
  23  from html import FORM, INPUT, LABEL, OPTION, SELECT, BUTTON 
  24  from html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE 
  25  from html import URL, truncate_string, FIELDSET 
  26  from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \ 
  27      bar_encode, Reference, REGEX_TABLE_DOT_FIELD 
  28  from storage import Storage 
  29  from utils import md5_hash 
  30  from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \ 
  31      IS_DATETIME, IS_INT_IN_RANGE, IS_FLOAT_IN_RANGE, IS_STRONG 
  32   
  33  import datetime 
  34  import urllib 
  35  import re 
  36  import cStringIO 
  37  from gluon import current, redirect, A, URL, DIV, H3, UL, LI, SPAN, INPUT 
  38  import inspect 
  39  import settings 
  40  is_gae = settings.global_settings.web2py_runtime_gae 
  41   
  42  table_field = re.compile('[\w_]+\.[\w_]+') 
  43  widget_class = re.compile('^\w*') 
44 45 46 -def trap_class(_class=None, trap=True):
47 return (trap and 'w2p_trap' or '') + (_class and ' ' + _class or '')
48
49 50 -def represent(field, value, record):
51 f = field.represent 52 if not callable(f): 53 return str(value) 54 n = f.func_code.co_argcount - len(f.func_defaults or []) 55 if getattr(f, 'im_self', None): 56 n -= 1 57 if n == 1: 58 return f(value) 59 elif n == 2: 60 return f(value, record) 61 else: 62 raise RuntimeError("field representation must take 1 or 2 args")
63
64 65 -def safe_int(x):
66 try: 67 return int(x) 68 except ValueError: 69 return 0
70
71 72 -def safe_float(x):
73 try: 74 return float(x) 75 except ValueError: 76 return 0
77
78 79 -class FormWidget(object):
80 """ 81 helper for SQLFORM to generate form input fields 82 (widget), related to the fieldtype 83 """ 84 85 _class = 'generic-widget' 86 87 @classmethod
88 - def _attributes(cls, field, 89 widget_attributes, **attributes):
90 """ 91 helper to build a common set of attributes 92 93 :param field: the field involved, 94 some attributes are derived from this 95 :param widget_attributes: widget related attributes 96 :param attributes: any other supplied attributes 97 """ 98 attr = dict( 99 _id='%s_%s' % (field._tablename, field.name), 100 _class=cls._class or 101 widget_class.match(str(field.type)).group(), 102 _name=field.name, 103 requires=field.requires, 104 ) 105 attr.update(widget_attributes) 106 attr.update(attributes) 107 return attr
108 109 @classmethod
110 - def widget(cls, field, value, **attributes):
111 """ 112 generates the widget for the field. 113 114 When serialized, will provide an INPUT tag: 115 116 - id = tablename_fieldname 117 - class = field.type 118 - name = fieldname 119 120 :param field: the field needing the widget 121 :param value: value 122 :param attributes: any other attributes to be applied 123 """ 124 125 raise NotImplementedError
126
127 128 -class StringWidget(FormWidget):
129 _class = 'string' 130 131 @classmethod
132 - def widget(cls, field, value, **attributes):
133 """ 134 generates an INPUT text tag. 135 136 see also: :meth:`FormWidget.widget` 137 """ 138 139 default = dict( 140 _type='text', 141 value=(not value is None and str(value)) or '', 142 ) 143 attr = cls._attributes(field, default, **attributes) 144 145 return INPUT(**attr)
146
147 148 -class IntegerWidget(StringWidget):
149 _class = 'integer'
150
151 152 -class DoubleWidget(StringWidget):
153 _class = 'double'
154
155 156 -class DecimalWidget(StringWidget):
157 _class = 'decimal'
158
159 160 -class TimeWidget(StringWidget):
161 _class = 'time'
162
163 164 -class DateWidget(StringWidget):
165 _class = 'date'
166
167 168 -class DatetimeWidget(StringWidget):
169 _class = 'datetime'
170
171 172 -class TextWidget(FormWidget):
173 _class = 'text' 174 175 @classmethod
176 - def widget(cls, field, value, **attributes):
177 """ 178 generates a TEXTAREA tag. 179 180 see also: :meth:`FormWidget.widget` 181 """ 182 183 default = dict(value=value) 184 attr = cls._attributes(field, default, **attributes) 185 return TEXTAREA(**attr)
186
187 188 -class BooleanWidget(FormWidget):
189 _class = 'boolean' 190 191 @classmethod
192 - def widget(cls, field, value, **attributes):
193 """ 194 generates an INPUT checkbox tag. 195 196 see also: :meth:`FormWidget.widget` 197 """ 198 199 default = dict(_type='checkbox', value=value) 200 attr = cls._attributes(field, default, 201 **attributes) 202 return INPUT(**attr)
203
204 205 -class OptionsWidget(FormWidget):
206 207 @staticmethod
208 - def has_options(field):
209 """ 210 checks if the field has selectable options 211 212 :param field: the field needing checking 213 :returns: True if the field has options 214 """ 215 216 return hasattr(field.requires, 'options')
217 218 @classmethod
219 - def widget(cls, field, value, **attributes):
220 """ 221 generates a SELECT tag, including OPTIONs (only 1 option allowed) 222 223 see also: :meth:`FormWidget.widget` 224 """ 225 default = dict(value=value) 226 attr = cls._attributes(field, default, 227 **attributes) 228 requires = field.requires 229 if not isinstance(requires, (list, tuple)): 230 requires = [requires] 231 if requires: 232 if hasattr(requires[0], 'options'): 233 options = requires[0].options() 234 else: 235 raise SyntaxError( 236 'widget cannot determine options of %s' % field) 237 opts = [OPTION(v, _value=k) for (k, v) in options] 238 239 return SELECT(*opts, **attr)
240
241 242 -class ListWidget(StringWidget):
243 244 @classmethod
245 - def widget(cls, field, value, **attributes):
246 _id = '%s_%s' % (field._tablename, field.name) 247 _name = field.name 248 if field.type == 'list:integer': 249 _class = 'integer' 250 else: 251 _class = 'string' 252 requires = field.requires if isinstance( 253 field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None 254 attributes['_style'] = 'list-style:none' 255 nvalue = value or [''] 256 items = [LI(INPUT(_id=_id, _class=_class, _name=_name, 257 value=v, hideerror=k < len(nvalue) - 1, 258 requires=requires), 259 **attributes) for (k, v) in enumerate(nvalue)] 260 script = SCRIPT(""" 261 // from http://refactormycode.com/codes/694-expanding-input-list-using-jquery 262 (function(){ 263 jQuery.fn.grow_input = function() { 264 return this.each(function() { 265 var ul = this; 266 jQuery(ul).find(":text").after('<a href="javascript:void(0)">+</a>&nbsp;<a href="javascript:void(0)">-</a>').keypress(function (e) { return (e.which == 13) ? pe(ul, e) : true; }).next().click(function(e){ pe(ul, e) }).next().click(function(e){ rl(ul, e)}); 267 }); 268 }; 269 function pe(ul, e) { 270 var new_line = ml(ul); 271 rel(ul); 272 if ($(e.target).parent().is(':visible')) { 273 //make sure we didn't delete the element before we insert after 274 new_line.insertAfter($(e.target).parent()); 275 } else { 276 //the line we clicked on was deleted, just add to end of list 277 new_line.appendTo(ul); 278 } 279 new_line.find(":text").focus(); 280 return false; 281 } 282 function rl(ul, e) { 283 if (jQuery(ul).children().length > 1) { 284 //only remove if we have more than 1 item so the list is never empty 285 $(e.target).parent().remove(); 286 } 287 } 288 function ml(ul) { 289 var line = jQuery(ul).find("li:first").clone(true); 290 line.find(':text').val(''); 291 return line; 292 } 293 function rel(ul) { 294 jQuery(ul).find("li").each(function() { 295 var trimmed = jQuery.trim(jQuery(this.firstChild).val()); 296 if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed); 297 }); 298 } 299 })(); 300 jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();}); 301 """ % _id) 302 attributes['_id'] = _id + '_grow_input' 303 attributes['_style'] = 'list-style:none' 304 return TAG[''](UL(*items, **attributes), script)
305
306 307 -class MultipleOptionsWidget(OptionsWidget):
308 309 @classmethod
310 - def widget(cls, field, value, size=5, **attributes):
311 """ 312 generates a SELECT tag, including OPTIONs (multiple options allowed) 313 314 see also: :meth:`FormWidget.widget` 315 316 :param size: optional param (default=5) to indicate how many rows must 317 be shown 318 """ 319 320 attributes.update(_size=size, _multiple=True) 321 322 return OptionsWidget.widget(field, value, **attributes)
323
324 325 -class RadioWidget(OptionsWidget):
326 327 @classmethod
328 - def widget(cls, field, value, **attributes):
329 """ 330 generates a TABLE tag, including INPUT radios (only 1 option allowed) 331 332 see also: :meth:`FormWidget.widget` 333 """ 334 335 attr = cls._attributes(field, {}, **attributes) 336 attr['_class'] = attr.get('_class', 'web2py_radiowidget') 337 338 requires = field.requires 339 if not isinstance(requires, (list, tuple)): 340 requires = [requires] 341 if requires: 342 if hasattr(requires[0], 'options'): 343 options = requires[0].options() 344 else: 345 raise SyntaxError('widget cannot determine options of %s' 346 % field) 347 options = [(k, v) for k, v in options if str(v)] 348 opts = [] 349 cols = attributes.get('cols', 1) 350 totals = len(options) 351 mods = totals % cols 352 rows = totals / cols 353 if mods: 354 rows += 1 355 356 #widget style 357 wrappers = dict( 358 table=(TABLE, TR, TD), 359 ul=(DIV, UL, LI), 360 divs=(CAT, DIV, DIV) 361 ) 362 parent, child, inner = wrappers[attributes.get('style', 'table')] 363 364 for r_index in range(rows): 365 tds = [] 366 for k, v in options[r_index * cols:(r_index + 1) * cols]: 367 checked = {'_checked': 'checked'} if k == value else {} 368 tds.append(inner(INPUT(_type='radio', 369 _id='%s%s' % (field.name, k), 370 _name=field.name, 371 requires=attr.get('requires', None), 372 hideerror=True, _value=k, 373 value=value, 374 **checked), 375 LABEL(v, _for='%s%s' % (field.name, k)))) 376 opts.append(child(tds)) 377 378 if opts: 379 opts[-1][0][0]['hideerror'] = False 380 return parent(*opts, **attr)
381
382 383 -class CheckboxesWidget(OptionsWidget):
384 385 @classmethod
386 - def widget(cls, field, value, **attributes):
387 """ 388 generates a TABLE tag, including INPUT checkboxes (multiple allowed) 389 390 see also: :meth:`FormWidget.widget` 391 """ 392 393 # was values = re.compile('[\w\-:]+').findall(str(value)) 394 if isinstance(value, (list, tuple)): 395 values = [str(v) for v in value] 396 else: 397 values = [str(value)] 398 399 attr = cls._attributes(field, {}, **attributes) 400 attr['_class'] = attr.get('_class', 'web2py_checkboxeswidget') 401 402 requires = field.requires 403 if not isinstance(requires, (list, tuple)): 404 requires = [requires] 405 if requires and hasattr(requires[0], 'options'): 406 options = requires[0].options() 407 else: 408 raise SyntaxError('widget cannot determine options of %s' 409 % field) 410 411 options = [(k, v) for k, v in options if k != ''] 412 opts = [] 413 cols = attributes.get('cols', 1) 414 totals = len(options) 415 mods = totals % cols 416 rows = totals / cols 417 if mods: 418 rows += 1 419 420 #widget style 421 wrappers = dict( 422 table=(TABLE, TR, TD), 423 ul=(DIV, UL, LI), 424 divs=(CAT, DIV, DIV) 425 ) 426 parent, child, inner = wrappers[attributes.get('style', 'table')] 427 428 for r_index in range(rows): 429 tds = [] 430 for k, v in options[r_index * cols:(r_index + 1) * cols]: 431 if k in values: 432 r_value = k 433 else: 434 r_value = [] 435 tds.append(inner(INPUT(_type='checkbox', 436 _id='%s%s' % (field.name, k), 437 _name=field.name, 438 requires=attr.get('requires', None), 439 hideerror=True, _value=k, 440 value=r_value), 441 LABEL(v, _for='%s%s' % (field.name, k)))) 442 opts.append(child(tds)) 443 444 if opts: 445 opts.append( 446 INPUT(_class="hidden", requires=attr.get('requires', None), 447 _disabled="disabled", _name=field.name, 448 hideerror=False)) 449 return parent(*opts, **attr)
450
451 452 -class PasswordWidget(FormWidget):
453 _class = 'password' 454 455 DEFAULT_PASSWORD_DISPLAY = 8 * ('*') 456 457 @classmethod
458 - def widget(cls, field, value, **attributes):
459 """ 460 generates a INPUT password tag. 461 If a value is present it will be shown as a number of '*', not related 462 to the length of the actual value. 463 464 see also: :meth:`FormWidget.widget` 465 """ 466 # detect if attached a IS_STRONG with entropy 467 default = dict( 468 _type='password', 469 _value=(value and cls.DEFAULT_PASSWORD_DISPLAY) or '', 470 ) 471 attr = cls._attributes(field, default, **attributes) 472 output = CAT(INPUT(**attr)) 473 474 # deal with entropy check! 475 requires = field.requires 476 if not isinstance(requires, (list, tuple)): 477 requires = [requires] 478 is_strong = [r for r in requires if isinstance(r, IS_STRONG)] 479 if is_strong: 480 output.append(SCRIPT("web2py_validate_entropy(jQuery('#%s'),%s);" % ( 481 attr['_id'], is_strong[0].entropy 482 if is_strong[0].entropy else "null"))) 483 # end entropy check 484 return output
485
486 487 -class UploadWidget(FormWidget):
488 _class = 'upload' 489 490 DEFAULT_WIDTH = '150px' 491 ID_DELETE_SUFFIX = '__delete' 492 GENERIC_DESCRIPTION = 'file' 493 DELETE_FILE = 'delete' 494 495 @classmethod
496 - def widget(cls, field, value, download_url=None, **attributes):
497 """ 498 generates a INPUT file tag. 499 500 Optionally provides an A link to the file, including a checkbox so 501 the file can be deleted. 502 All is wrapped in a DIV. 503 504 see also: :meth:`FormWidget.widget` 505 506 :param download_url: Optional URL to link to the file (default = None) 507 """ 508 509 default = dict(_type='file',) 510 attr = cls._attributes(field, default, **attributes) 511 512 inp = INPUT(**attr) 513 514 if download_url and value: 515 if callable(download_url): 516 url = download_url(value) 517 else: 518 url = download_url + '/' + value 519 (br, image) = ('', '') 520 if UploadWidget.is_image(value): 521 br = BR() 522 image = IMG(_src=url, _width=cls.DEFAULT_WIDTH) 523 524 requires = attr["requires"] 525 if requires == [] or isinstance(requires, IS_EMPTY_OR): 526 inp = DIV(inp, '[', 527 A(current.T( 528 UploadWidget.GENERIC_DESCRIPTION), _href=url), 529 '|', 530 INPUT(_type='checkbox', 531 _name=field.name + cls.ID_DELETE_SUFFIX, 532 _id=field.name + cls.ID_DELETE_SUFFIX), 533 LABEL(current.T(cls.DELETE_FILE), 534 _for=field.name + cls.ID_DELETE_SUFFIX), 535 ']', br, image) 536 else: 537 inp = DIV(inp, '[', 538 A(cls.GENERIC_DESCRIPTION, _href=url), 539 ']', br, image) 540 return inp
541 542 @classmethod
543 - def represent(cls, field, value, download_url=None):
544 """ 545 how to represent the file: 546 547 - with download url and if it is an image: <A href=...><IMG ...></A> 548 - otherwise with download url: <A href=...>file</A> 549 - otherwise: file 550 551 :param field: the field 552 :param value: the field value 553 :param download_url: url for the file download (default = None) 554 """ 555 556 inp = cls.GENERIC_DESCRIPTION 557 558 if download_url and value: 559 if callable(download_url): 560 url = download_url(value) 561 else: 562 url = download_url + '/' + value 563 if cls.is_image(value): 564 inp = IMG(_src=url, _width=cls.DEFAULT_WIDTH) 565 inp = A(inp, _href=url) 566 567 return inp
568 569 @staticmethod
570 - def is_image(value):
571 """ 572 Tries to check if the filename provided references to an image 573 574 Checking is based on filename extension. Currently recognized: 575 gif, png, jp(e)g, bmp 576 577 :param value: filename 578 """ 579 580 extension = value.split('.')[-1].lower() 581 if extension in ['gif', 'png', 'jpg', 'jpeg', 'bmp']: 582 return True 583 return False
584
585 586 -class AutocompleteWidget(object):
587 _class = 'string' 588
589 - def __init__(self, request, field, id_field=None, db=None, 590 orderby=None, limitby=(0, 10), distinct=False, 591 keyword='_autocomplete_%(tablename)s_%(fieldname)s', 592 min_length=2, help_fields=None, help_string=None):
593 594 self.help_fields = help_fields or [] 595 self.help_string = help_string 596 if self.help_fields and not self.help_string: 597 self.help_string = ' '.join('%%(%s)s' for f in self.help_fields) 598 599 self.request = request 600 self.keyword = keyword % dict(tablename=field.tablename, 601 fieldname=field.name) 602 self.db = db or field._db 603 self.orderby = orderby 604 self.limitby = limitby 605 self.distinct = distinct 606 self.min_length = min_length 607 self.fields = [field] 608 if id_field: 609 self.is_reference = True 610 self.fields.append(id_field) 611 else: 612 self.is_reference = False 613 if hasattr(request, 'application'): 614 self.url = URL(args=request.args) 615 self.callback() 616 else: 617 self.url = request
618
619 - def callback(self):
620 if self.keyword in self.request.vars: 621 field = self.fields[0] 622 if is_gae: 623 rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *self.fields) 624 else: 625 rows = self.db(field.like(self.request.vars[self.keyword] + '%')).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *self.fields) 626 if rows: 627 if self.is_reference: 628 id_field = self.fields[1] 629 if self.help_fields: 630 options = [OPTION( 631 self.help_string % dict([(h.name, s[h.name]) for h in self.fields[:1] + self.help_fields]), 632 _value=s[id_field.name], _selected=(k == 0)) for k, s in enumerate(rows)] 633 else: 634 options = [OPTION( 635 s[field.name], _value=s[id_field.name], 636 _selected=(k == 0)) for k, s in enumerate(rows)] 637 raise HTTP( 638 200, SELECT(_id=self.keyword, _class='autocomplete', 639 _size=len(rows), _multiple=(len(rows) == 1), 640 *options).xml()) 641 else: 642 raise HTTP( 643 200, SELECT(_id=self.keyword, _class='autocomplete', 644 _size=len(rows), _multiple=(len(rows) == 1), 645 *[OPTION(s[field.name], 646 _selected=(k == 0)) 647 for k, s in enumerate(rows)]).xml()) 648 else: 649 raise HTTP(200, '')
650
651 - def __call__(self, field, value, **attributes):
652 default = dict( 653 _type='text', 654 value=(not value is None and str(value)) or '', 655 ) 656 attr = StringWidget._attributes(field, default, **attributes) 657 div_id = self.keyword + '_div' 658 attr['_autocomplete'] = 'off' 659 if self.is_reference: 660 key2 = self.keyword + '_aux' 661 key3 = self.keyword + '_auto' 662 attr['_class'] = 'string' 663 name = attr['_name'] 664 if 'requires' in attr: 665 del attr['requires'] 666 attr['_name'] = key2 667 value = attr['value'] 668 record = self.db( 669 self.fields[1] == value).select(self.fields[0]).first() 670 attr['value'] = record and record[self.fields[0].name] 671 attr['_onblur'] = "jQuery('#%(div_id)s').delay(1000).fadeOut('slow');" % \ 672 dict(div_id=div_id, u='F' + self.keyword) 673 attr['_onkeyup'] = "jQuery('#%(key3)s').val('');var e=event.which?event.which:event.keyCode; function %(u)s(){jQuery('#%(id)s').val(jQuery('#%(key)s :selected').text());jQuery('#%(key3)s').val(jQuery('#%(key)s').val())}; if(e==39) %(u)s(); else if(e==40) {if(jQuery('#%(key)s option:selected').next().length)jQuery('#%(key)s option:selected').attr('selected',null).next().attr('selected','selected'); %(u)s();} else if(e==38) {if(jQuery('#%(key)s option:selected').prev().length)jQuery('#%(key)s option:selected').attr('selected',null).prev().attr('selected','selected'); %(u)s();} else if(jQuery('#%(id)s').val().length>=%(min_length)s) jQuery.get('%(url)s?%(key)s='+encodeURIComponent(jQuery('#%(id)s').val()),function(data){if(data=='')jQuery('#%(key3)s').val('');else{jQuery('#%(id)s').next('.error').hide();jQuery('#%(div_id)s').html(data).show().focus();jQuery('#%(div_id)s select').css('width',jQuery('#%(id)s').css('width'));jQuery('#%(key3)s').val(jQuery('#%(key)s').val());jQuery('#%(key)s').change(%(u)s);jQuery('#%(key)s').click(%(u)s);};}); else jQuery('#%(div_id)s').fadeOut('slow');" % \ 674 dict(url=self.url, min_length=self.min_length, 675 key=self.keyword, id=attr['_id'], key2=key2, key3=key3, 676 name=name, div_id=div_id, u='F' + self.keyword) 677 if self.min_length == 0: 678 attr['_onfocus'] = attr['_onkeyup'] 679 return TAG[''](INPUT(**attr), INPUT(_type='hidden', _id=key3, _value=value, 680 _name=name, requires=field.requires), 681 DIV(_id=div_id, _style='position:absolute;')) 682 else: 683 attr['_name'] = field.name 684 attr['_onblur'] = "jQuery('#%(div_id)s').delay(1000).fadeOut('slow');" % \ 685 dict(div_id=div_id, u='F' + self.keyword) 686 attr['_onkeyup'] = "var e=event.which?event.which:event.keyCode; function %(u)s(){jQuery('#%(id)s').val(jQuery('#%(key)s').val())}; if(e==39) %(u)s(); else if(e==40) {if(jQuery('#%(key)s option:selected').next().length)jQuery('#%(key)s option:selected').attr('selected',null).next().attr('selected','selected'); %(u)s();} else if(e==38) {if(jQuery('#%(key)s option:selected').prev().length)jQuery('#%(key)s option:selected').attr('selected',null).prev().attr('selected','selected'); %(u)s();} else if(jQuery('#%(id)s').val().length>=%(min_length)s) jQuery.get('%(url)s?%(key)s='+encodeURIComponent(jQuery('#%(id)s').val()),function(data){jQuery('#%(id)s').next('.error').hide();jQuery('#%(div_id)s').html(data).show().focus();jQuery('#%(div_id)s select').css('width',jQuery('#%(id)s').css('width'));jQuery('#%(key)s').change(%(u)s);jQuery('#%(key)s').click(%(u)s);}); else jQuery('#%(div_id)s').fadeOut('slow');" % \ 687 dict(url=self.url, min_length=self.min_length, 688 key=self.keyword, id=attr['_id'], div_id=div_id, u='F' + self.keyword) 689 if self.min_length == 0: 690 attr['_onfocus'] = attr['_onkeyup'] 691 return TAG[''](INPUT(**attr), DIV(_id=div_id, _style='position:absolute;'))
692
693 694 -def formstyle_table3cols(form, fields):
695 ''' 3 column table - default ''' 696 table = TABLE() 697 for id, label, controls, help in fields: 698 _help = TD(help, _class='w2p_fc') 699 _controls = TD(controls, _class='w2p_fw') 700 _label = TD(label, _class='w2p_fl') 701 table.append(TR(_label, _controls, _help, _id=id)) 702 return table
703
704 705 -def formstyle_table2cols(form, fields):
706 ''' 2 column table ''' 707 table = TABLE() 708 for id, label, controls, help in fields: 709 _help = TD(help, _class='w2p_fc', _width='50%') 710 _controls = TD(controls, _class='w2p_fw', _colspan='2') 711 _label = TD(label, _class='w2p_fl', _width='50%') 712 table.append(TR(_label, _help, _id=id + '1', _class='even')) 713 table.append(TR(_controls, _id=id + '2', _class='odd')) 714 return table
715
716 717 -def formstyle_divs(form, fields):
718 ''' divs only ''' 719 table = TAG['']() 720 for id, label, controls, help in fields: 721 _help = DIV(help, _class='w2p_fc') 722 _controls = DIV(controls, _class='w2p_fw') 723 _label = DIV(label, _class='w2p_fl') 724 table.append(DIV(_label, _controls, _help, _id=id)) 725 return table
726
727 728 -def formstyle_inline(form, fields):
729 ''' divs only ''' 730 if len(fields) != 2: 731 raise RuntimeError("Not possible") 732 id, label, controls, help = fields[0] 733 submit_button = fields[1][2] 734 return CAT(DIV(controls, _style='display:inline'), 735 submit_button)
736
737 738 -def formstyle_ul(form, fields):
739 ''' unordered list ''' 740 table = UL() 741 for id, label, controls, help in fields: 742 _help = DIV(help, _class='w2p_fc') 743 _controls = DIV(controls, _class='w2p_fw') 744 _label = DIV(label, _class='w2p_fl') 745 table.append(LI(_label, _controls, _help, _id=id)) 746 return table
747
748 749 -def formstyle_bootstrap(form, fields):
750 ''' bootstrap format form layout ''' 751 form['_class'] = 'form-horizontal' 752 parent = FIELDSET() 753 for id, label, controls, help in fields: 754 # wrappers 755 _help = SPAN(help, _class='help-inline') 756 # embed _help into _controls 757 _controls = DIV(controls, _help, _class='controls') 758 # submit unflag by default 759 _submit = False 760 761 if isinstance(controls, INPUT): 762 controls.add_class('input-xlarge') 763 if controls['_type'] == 'submit': 764 # flag submit button 765 _submit = True 766 controls['_class'] = 'btn btn-primary' 767 if controls['_type'] == 'file': 768 controls['_class'] = 'input-file' 769 770 # For password fields, which are wrapped in a CAT object. 771 if isinstance(controls, CAT) and isinstance(controls[0], INPUT): 772 controls[0].add_class('input-xlarge') 773 774 if isinstance(controls, SELECT): 775 controls.add_class('input-xlarge') 776 777 if isinstance(controls, TEXTAREA): 778 controls.add_class('input-xlarge') 779 780 if isinstance(label, LABEL): 781 label['_class'] = 'control-label' 782 783 if _submit: 784 # submit button has unwrapped label and controls, different class 785 parent.append(DIV(label, controls, _class='form-actions')) 786 # unflag submit (possible side effect) 787 _submit = False 788 else: 789 # unwrapped label 790 parent.append(DIV(label, _controls, _class='control-group')) 791 return parent
792
793 794 -class SQLFORM(FORM):
795 796 """ 797 SQLFORM is used to map a table (and a current record) into an HTML form 798 799 given a SQLTable stored in db.table 800 801 generates an insert form:: 802 803 SQLFORM(db.table) 804 805 generates an update form:: 806 807 record=db.table[some_id] 808 SQLFORM(db.table, record) 809 810 generates an update with a delete button:: 811 812 SQLFORM(db.table, record, deletable=True) 813 814 if record is an int:: 815 816 record=db.table[record] 817 818 optional arguments: 819 820 :param fields: a list of fields that should be placed in the form, 821 default is all. 822 :param labels: a dictionary with labels for each field, keys are the field 823 names. 824 :param col3: a dictionary with content for an optional third column 825 (right of each field). keys are field names. 826 :param linkto: the URL of a controller/function to access referencedby 827 records 828 see controller appadmin.py for examples 829 :param upload: the URL of a controller/function to download an uploaded file 830 see controller appadmin.py for examples 831 832 any named optional attribute is passed to the <form> tag 833 for example _class, _id, _style, _action, _method, etc. 834 835 """ 836 837 # usability improvements proposal by fpp - 4 May 2008 : 838 # - correct labels (for points to field id, not field name) 839 # - add label for delete checkbox 840 # - add translatable label for record ID 841 # - add third column to right of fields, populated from the col3 dict 842 843 widgets = Storage(dict( 844 string=StringWidget, 845 text=TextWidget, 846 password=PasswordWidget, 847 integer=IntegerWidget, 848 double=DoubleWidget, 849 decimal=DecimalWidget, 850 time=TimeWidget, 851 date=DateWidget, 852 datetime=DatetimeWidget, 853 upload=UploadWidget, 854 boolean=BooleanWidget, 855 blob=None, 856 options=OptionsWidget, 857 multiple=MultipleOptionsWidget, 858 radio=RadioWidget, 859 checkboxes=CheckboxesWidget, 860 autocomplete=AutocompleteWidget, 861 list=ListWidget, 862 )) 863 864 formstyles = Storage(dict( 865 table3cols=formstyle_table3cols, 866 table2cols=formstyle_table2cols, 867 divs=formstyle_divs, 868 ul=formstyle_ul, 869 bootstrap=formstyle_bootstrap, 870 inline=formstyle_inline, 871 )) 872 873 FIELDNAME_REQUEST_DELETE = 'delete_this_record' 874 FIELDKEY_DELETE_RECORD = 'delete_record' 875 ID_LABEL_SUFFIX = '__label' 876 ID_ROW_SUFFIX = '__row' 877
878 - def assert_status(self, status, request_vars):
879 if not status and self.record and self.errors: 880 ### if there are errors in update mode 881 # and some errors refers to an already uploaded file 882 # delete error if 883 # - user not trying to upload a new file 884 # - there is existing file and user is not trying to delete it 885 # this is because removing the file may not pass validation 886 for key in self.errors.keys(): 887 if key in self.table \ 888 and self.table[key].type == 'upload' \ 889 and request_vars.get(key, None) in (None, '') \ 890 and self.record[key] \ 891 and not key + UploadWidget.ID_DELETE_SUFFIX in request_vars: 892 del self.errors[key] 893 if not self.errors: 894 status = True 895 return status
896
897 - def __init__( 898 self, 899 table, 900 record=None, 901 deletable=False, 902 linkto=None, 903 upload=None, 904 fields=None, 905 labels=None, 906 col3={}, 907 submit_button='Submit', 908 delete_label='Check to delete', 909 showid=True, 910 readonly=False, 911 comments=True, 912 keepopts=[], 913 ignore_rw=False, 914 record_id=None, 915 formstyle='table3cols', 916 buttons=['submit'], 917 separator=': ', 918 **attributes 919 ):
920 """ 921 SQLFORM(db.table, 922 record=None, 923 fields=['name'], 924 labels={'name': 'Your name'}, 925 linkto=URL(f='table/db/') 926 """ 927 T = current.T 928 929 self.ignore_rw = ignore_rw 930 self.formstyle = formstyle 931 self.readonly = readonly 932 # Default dbio setting 933 self.detect_record_change = None 934 935 nbsp = XML('&nbsp;') # Firefox2 does not display fields with blanks 936 FORM.__init__(self, *[], **attributes) 937 ofields = fields 938 keyed = hasattr(table, '_primarykey') # for backward compatibility 939 940 # if no fields are provided, build it from the provided table 941 # will only use writable or readable fields, unless forced to ignore 942 if fields is None: 943 fields = [f.name for f in table if 944 (ignore_rw or f.writable or f.readable) and 945 (readonly or not f.compute)] 946 self.fields = fields 947 948 # make sure we have an id 949 if self.fields[0] != table.fields[0] and \ 950 isinstance(table, Table) and not keyed: 951 self.fields.insert(0, table.fields[0]) 952 953 self.table = table 954 955 # try to retrieve the indicated record using its id 956 # otherwise ignore it 957 if record and isinstance(record, (int, long, str, unicode)): 958 if not str(record).isdigit(): 959 raise HTTP(404, "Object not found") 960 record = table._db(table._id == record).select().first() 961 if not record: 962 raise HTTP(404, "Object not found") 963 self.record = record 964 965 self.record_id = record_id 966 if keyed: 967 self.record_id = dict([(k, record and str(record[k]) or None) 968 for k in table._primarykey]) 969 self.field_parent = {} 970 xfields = [] 971 self.fields = fields 972 self.custom = Storage() 973 self.custom.dspval = Storage() 974 self.custom.inpval = Storage() 975 self.custom.label = Storage() 976 self.custom.comment = Storage() 977 self.custom.widget = Storage() 978 self.custom.linkto = Storage() 979 980 # default id field name 981 if not keyed: 982 self.id_field_name = table._id.name 983 else: 984 self.id_field_name = table._primarykey[0] # only works if one key 985 986 sep = separator or '' 987 988 for fieldname in self.fields: 989 if fieldname.find('.') >= 0: 990 continue 991 992 field = self.table[fieldname] 993 comment = None 994 995 if comments: 996 comment = col3.get(fieldname, field.comment) 997 if comment is None: 998 comment = '' 999 self.custom.comment[fieldname] = comment 1000 1001 if not labels is None and fieldname in labels: 1002 label = labels[fieldname] 1003 else: 1004 label = field.label 1005 self.custom.label[fieldname] = label 1006 1007 field_id = '%s_%s' % (table._tablename, fieldname) 1008 1009 label = LABEL(label, label and sep, _for=field_id, 1010 _id=field_id + SQLFORM.ID_LABEL_SUFFIX) 1011 1012 row_id = field_id + SQLFORM.ID_ROW_SUFFIX 1013 if field.type == 'id': 1014 self.custom.dspval.id = nbsp 1015 self.custom.inpval.id = '' 1016 widget = '' 1017 1018 # store the id field name (for legacy databases) 1019 self.id_field_name = field.name 1020 1021 if record: 1022 if showid and field.name in record and field.readable: 1023 v = record[field.name] 1024 widget = SPAN(v, _id=field_id) 1025 self.custom.dspval.id = str(v) 1026 xfields.append((row_id, label, widget, comment)) 1027 self.record_id = str(record[field.name]) 1028 self.custom.widget.id = widget 1029 continue 1030 1031 if readonly and not ignore_rw and not field.readable: 1032 continue 1033 1034 if record: 1035 default = record[fieldname] 1036 else: 1037 default = field.default 1038 if isinstance(default, CALLABLETYPES): 1039 default = default() 1040 1041 cond = readonly or \ 1042 (not ignore_rw and not field.writable and field.readable) 1043 1044 if default and not cond: 1045 default = field.formatter(default) 1046 dspval = default 1047 inpval = default 1048 1049 if cond: 1050 1051 # ## if field.represent is available else 1052 # ## ignore blob and preview uploaded images 1053 # ## format everything else 1054 1055 if field.represent: 1056 inp = represent(field, default, record) 1057 elif field.type in ['blob']: 1058 continue 1059 elif field.type == 'upload': 1060 inp = UploadWidget.represent(field, default, upload) 1061 elif field.type == 'boolean': 1062 inp = self.widgets.boolean.widget( 1063 field, default, _disabled=True) 1064 else: 1065 inp = field.formatter(default) 1066 elif field.type == 'upload': 1067 if field.widget: 1068 inp = field.widget(field, default, upload) 1069 else: 1070 inp = self.widgets.upload.widget(field, default, upload) 1071 elif field.widget: 1072 inp = field.widget(field, default) 1073 elif field.type == 'boolean': 1074 inp = self.widgets.boolean.widget(field, default) 1075 if default: 1076 inpval = 'checked' 1077 else: 1078 inpval = '' 1079 elif OptionsWidget.has_options(field): 1080 if not field.requires.multiple: 1081 inp = self.widgets.options.widget(field, default) 1082 else: 1083 inp = self.widgets.multiple.widget(field, default) 1084 if fieldname in keepopts: 1085 inpval = TAG[''](*inp.components) 1086 elif field.type.startswith('list:'): 1087 inp = self.widgets.list.widget(field, default) 1088 elif field.type == 'text': 1089 inp = self.widgets.text.widget(field, default) 1090 elif field.type == 'password': 1091 inp = self.widgets.password.widget(field, default) 1092 if self.record: 1093 dspval = PasswordWidget.DEFAULT_PASSWORD_DISPLAY 1094 else: 1095 dspval = '' 1096 elif field.type == 'blob': 1097 continue 1098 else: 1099 field_type = widget_class.match(str(field.type)).group() 1100 field_type = field_type in self.widgets and field_type or 'string' 1101 inp = self.widgets[field_type].widget(field, default) 1102 1103 xfields.append((row_id, label, inp, comment)) 1104 self.custom.dspval[fieldname] = dspval or nbsp 1105 self.custom.inpval[ 1106 fieldname] = inpval if not inpval is None else '' 1107 self.custom.widget[fieldname] = inp 1108 1109 # if a record is provided and found, as is linkto 1110 # build a link 1111 if record and linkto: 1112 db = linkto.split('/')[-1] 1113 for rfld in table._referenced_by: 1114 if keyed: 1115 query = urllib.quote('%s.%s==%s' % ( 1116 db, rfld, record[rfld.type[10:].split('.')[1]])) 1117 else: 1118 query = urllib.quote( 1119 '%s.%s==%s' % (db, rfld, record[self.id_field_name])) 1120 lname = olname = '%s.%s' % (rfld.tablename, rfld.name) 1121 if ofields and not olname in ofields: 1122 continue 1123 if labels and lname in labels: 1124 lname = labels[lname] 1125 widget = A(lname, 1126 _class='reference', 1127 _href='%s/%s?query=%s' % (linkto, rfld.tablename, query)) 1128 xfields.append( 1129 (olname.replace('.', '__') + SQLFORM.ID_ROW_SUFFIX, 1130 '', widget, col3.get(olname, ''))) 1131 self.custom.linkto[olname.replace('.', '__')] = widget 1132 # </block> 1133 1134 # when deletable, add delete? checkbox 1135 self.custom.deletable = '' 1136 if record and deletable: 1137 #add secondary css class for cascade delete warning 1138 css = 'delete' 1139 for f in self.table.fields: 1140 on_del = self.table[f].ondelete 1141 if isinstance(on_del,str) and 'cascade' in on_del.lower(): 1142 css += ' cascade_delete' 1143 break 1144 widget = INPUT(_type='checkbox', 1145 _class=css, 1146 _id=self.FIELDKEY_DELETE_RECORD, 1147 _name=self.FIELDNAME_REQUEST_DELETE, 1148 ) 1149 xfields.append( 1150 (self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_ROW_SUFFIX, 1151 LABEL( 1152 delete_label, separator, 1153 _for=self.FIELDKEY_DELETE_RECORD, 1154 _id=self.FIELDKEY_DELETE_RECORD + SQLFORM.ID_LABEL_SUFFIX), 1155 widget, 1156 col3.get(self.FIELDKEY_DELETE_RECORD, ''))) 1157 self.custom.deletable = widget 1158 1159 # when writable, add submit button 1160 self.custom.submit = '' 1161 if not readonly: 1162 if 'submit' in buttons: 1163 widget = self.custom.submit = INPUT(_type='submit', 1164 _value=T(submit_button)) 1165 elif buttons: 1166 widget = self.custom.submit = DIV(*buttons) 1167 if self.custom.submit: 1168 xfields.append(('submit_record' + SQLFORM.ID_ROW_SUFFIX, 1169 '', widget, col3.get('submit_button', ''))) 1170 1171 # if a record is provided and found 1172 # make sure it's id is stored in the form 1173 if record: 1174 if not self['hidden']: 1175 self['hidden'] = {} 1176 if not keyed: 1177 self['hidden']['id'] = record[table._id.name] 1178 1179 (begin, end) = self._xml() 1180 self.custom.begin = XML("<%s %s>" % (self.tag, begin)) 1181 self.custom.end = XML("%s</%s>" % (end, self.tag)) 1182 table = self.createform(xfields) 1183 self.components = [table]
1184
1185 - def createform(self, xfields):
1186 formstyle = self.formstyle 1187 if isinstance(formstyle, basestring): 1188 if formstyle in SQLFORM.formstyles: 1189 formstyle = SQLFORM.formstyles[formstyle] 1190 else: 1191 raise RuntimeError('formstyle not found') 1192 1193 if callable(formstyle): 1194 # backward compatibility, 4 argument function is the old style 1195 args, varargs, keywords, defaults = inspect.getargspec(formstyle) 1196 if defaults and len(args) - len(defaults) == 4 or len(args) == 4: 1197 table = TABLE() 1198 for id, a, b, c in xfields: 1199 newrows = formstyle(id, a, b, c) 1200 self.field_parent[id] = getattr(b, 'parent', None) 1201 if type(newrows).__name__ != "tuple": 1202 newrows = [newrows] 1203 for newrow in newrows: 1204 table.append(newrow) 1205 else: 1206 table = formstyle(self, xfields) 1207 for id, a, b, c in xfields: 1208 self.field_parent[id] = getattr(b, 'parent', None) 1209 else: 1210 raise RuntimeError('formstyle not supported') 1211 return table
1212
1213 - def accepts( 1214 self, 1215 request_vars, 1216 session=None, 1217 formname='%(tablename)s/%(record_id)s', 1218 keepvalues=None, 1219 onvalidation=None, 1220 dbio=True, 1221 hideerror=False, 1222 detect_record_change=False, 1223 **kwargs 1224 ):
1225 1226 """ 1227 similar FORM.accepts but also does insert, update or delete in DAL. 1228 but if detect_record_change == True than: 1229 form.record_changed = False (record is properly validated/submitted) 1230 form.record_changed = True (record cannot be submitted because changed) 1231 elseif detect_record_change == False than: 1232 form.record_changed = None 1233 """ 1234 1235 if keepvalues is None: 1236 keepvalues = True if self.record else False 1237 1238 if self.readonly: 1239 return False 1240 1241 if request_vars.__class__.__name__ == 'Request': 1242 request_vars = request_vars.post_vars 1243 1244 keyed = hasattr(self.table, '_primarykey') 1245 1246 # implement logic to detect whether record exist but has been modified 1247 # server side 1248 self.record_changed = None 1249 self.detect_record_change = detect_record_change 1250 if self.detect_record_change: 1251 if self.record: 1252 self.record_changed = False 1253 serialized = '|'.join( 1254 str(self.record[k]) for k in self.table.fields()) 1255 self.record_hash = md5_hash(serialized) 1256 1257 # logic to deal with record_id for keyed tables 1258 if self.record: 1259 if keyed: 1260 formname_id = '.'.join(str(self.record[k]) 1261 for k in self.table._primarykey 1262 if hasattr(self.record, k)) 1263 record_id = dict((k, request_vars.get(k, None)) 1264 for k in self.table._primarykey) 1265 else: 1266 (formname_id, record_id) = (self.record[self.id_field_name], 1267 request_vars.get('id', None)) 1268 keepvalues = True 1269 else: 1270 if keyed: 1271 formname_id = 'create' 1272 record_id = dict([(k, None) for k in self.table._primarykey]) 1273 else: 1274 (formname_id, record_id) = ('create', None) 1275 1276 if not keyed and isinstance(record_id, (list, tuple)): 1277 record_id = record_id[0] 1278 1279 if formname: 1280 formname = formname % dict(tablename=self.table._tablename, 1281 record_id=formname_id) 1282 1283 # ## THIS IS FOR UNIQUE RECORDS, read IS_NOT_IN_DB 1284 1285 for fieldname in self.fields: 1286 field = self.table[fieldname] 1287 requires = field.requires or [] 1288 if not isinstance(requires, (list, tuple)): 1289 requires = [requires] 1290 [item.set_self_id(self.record_id) for item in requires 1291 if hasattr(item, 'set_self_id') and self.record_id] 1292 1293 # ## END 1294 1295 fields = {} 1296 for key in self.vars: 1297 fields[key] = self.vars[key] 1298 1299 ret = FORM.accepts( 1300 self, 1301 request_vars, 1302 session, 1303 formname, 1304 keepvalues, 1305 onvalidation, 1306 hideerror=hideerror, 1307 **kwargs 1308 ) 1309 1310 self.deleted = \ 1311 request_vars.get(self.FIELDNAME_REQUEST_DELETE, False) 1312 1313 self.custom.end = TAG[''](self.hidden_fields(), self.custom.end) 1314 1315 auch = record_id and self.errors and self.deleted 1316 1317 if self.record_changed and self.detect_record_change: 1318 message_onchange = \ 1319 kwargs.setdefault("message_onchange", 1320 current.T("A record change was detected. " + 1321 "Consecutive update self-submissions " + 1322 "are not allowed. Try re-submitting or " + 1323 "refreshing the form page.")) 1324 if message_onchange is not None: 1325 current.response.flash = message_onchange 1326 return ret 1327 elif (not ret) and (not auch): 1328 # auch is true when user tries to delete a record 1329 # that does not pass validation, yet it should be deleted 1330 for fieldname in self.fields: 1331 field = self.table[fieldname] 1332 ### this is a workaround! widgets should always have default not None! 1333 if not field.widget and field.type.startswith('list:') and \ 1334 not OptionsWidget.has_options(field): 1335 field.widget = self.widgets.list.widget 1336 if field.widget and fieldname in request_vars: 1337 if fieldname in self.request_vars: 1338 value = self.request_vars[fieldname] 1339 elif self.record: 1340 value = self.record[fieldname] 1341 else: 1342 value = self.table[fieldname].default 1343 if field.type.startswith('list:') and isinstance(value, str): 1344 value = [value] 1345 row_id = '%s_%s%s' % ( 1346 self.table, fieldname, SQLFORM.ID_ROW_SUFFIX) 1347 widget = field.widget(field, value) 1348 parent = self.field_parent[row_id] 1349 if parent: 1350 parent.components = [widget] 1351 if self.errors.get(fieldname): 1352 parent._traverse(False, hideerror) 1353 self.custom.widget[fieldname] = widget 1354 self.accepted = ret 1355 return ret 1356 1357 if record_id and str(record_id) != str(self.record_id): 1358 raise SyntaxError('user is tampering with form\'s record_id: ' 1359 '%s != %s' % (record_id, self.record_id)) 1360 1361 if record_id and dbio and not keyed: 1362 self.vars.id = self.record[self.id_field_name] 1363 1364 if self.deleted and self.custom.deletable: 1365 if dbio: 1366 if keyed: 1367 qry = reduce(lambda x, y: x & y, 1368 [self.table[k] == record_id[k] 1369 for k in self.table._primarykey]) 1370 else: 1371 qry = self.table._id == self.record[self.id_field_name] 1372 self.table._db(qry).delete() 1373 self.errors.clear() 1374 for component in self.elements('input, select, textarea'): 1375 component['_disabled'] = True 1376 self.accepted = True 1377 return True 1378 1379 for fieldname in self.fields: 1380 if not fieldname in self.table.fields: 1381 continue 1382 1383 if not self.ignore_rw and not self.table[fieldname].writable: 1384 ### this happens because FORM has no knowledge of writable 1385 ### and thinks that a missing boolean field is a None 1386 if self.table[fieldname].type == 'boolean' and \ 1387 self.vars.get(fieldname, True) is None: 1388 del self.vars[fieldname] 1389 continue 1390 1391 field = self.table[fieldname] 1392 if field.type == 'id': 1393 continue 1394 if field.type == 'boolean': 1395 if self.vars.get(fieldname, False): 1396 self.vars[fieldname] = fields[fieldname] = True 1397 else: 1398 self.vars[fieldname] = fields[fieldname] = False 1399 elif field.type == 'password' and self.record\ 1400 and request_vars.get(fieldname, None) == \ 1401 PasswordWidget.DEFAULT_PASSWORD_DISPLAY: 1402 continue # do not update if password was not changed 1403 elif field.type == 'upload': 1404 f = self.vars[fieldname] 1405 fd = '%s__delete' % fieldname 1406 if f == '' or f is None: 1407 if self.vars.get(fd, False): 1408 f = self.table[fieldname].default or '' 1409 fields[fieldname] = f 1410 elif self.record: 1411 if self.record[fieldname]: 1412 fields[fieldname] = self.record[fieldname] 1413 else: 1414 f = self.table[fieldname].default or '' 1415 fields[fieldname] = f 1416 else: 1417 fields[fieldname] = '' 1418 self.vars[fieldname] = fields[fieldname] 1419 if not f: 1420 continue 1421 else: 1422 f = os.path.join(current.request.folder, 1423 os.path.normpath(f)) 1424 source_file = open(f, 'rb') 1425 original_filename = os.path.split(f)[1] 1426 elif hasattr(f, 'file'): 1427 (source_file, original_filename) = (f.file, f.filename) 1428 elif isinstance(f, (str, unicode)): 1429 ### do not know why this happens, it should not 1430 (source_file, original_filename) = \ 1431 (cStringIO.StringIO(f), 'file.txt') 1432 newfilename = field.store(source_file, original_filename, 1433 field.uploadfolder) 1434 # this line was for backward compatibility but problematic 1435 # self.vars['%s_newfilename' % fieldname] = newfilename 1436 fields[fieldname] = newfilename 1437 if isinstance(field.uploadfield, str): 1438 fields[field.uploadfield] = source_file.read() 1439 # proposed by Hamdy (accept?) do we need fields at this point? 1440 self.vars[fieldname] = fields[fieldname] 1441 continue 1442 elif fieldname in self.vars: 1443 fields[fieldname] = self.vars[fieldname] 1444 elif field.default is None and field.type != 'blob': 1445 self.errors[fieldname] = 'no data' 1446 self.accepted = False 1447 return False 1448 value = fields.get(fieldname, None) 1449 if field.type == 'list:string': 1450 if not isinstance(value, (tuple, list)): 1451 fields[fieldname] = value and [value] or [] 1452 elif isinstance(field.type, str) and field.type.startswith('list:'): 1453 if not isinstance(value, list): 1454 fields[fieldname] = [safe_int( 1455 x) for x in (value and [value] or [])] 1456 elif field.type == 'integer': 1457 if not value is None: 1458 fields[fieldname] = safe_int(value) 1459 elif field.type.startswith('reference'): 1460 if not value is None and isinstance(self.table, Table) and not keyed: 1461 fields[fieldname] = safe_int(value) 1462 elif field.type == 'double': 1463 if not value is None: 1464 fields[fieldname] = safe_float(value) 1465 1466 for fieldname in self.vars: 1467 if fieldname != 'id' and fieldname in self.table.fields\ 1468 and not fieldname in fields and not fieldname\ 1469 in request_vars: 1470 fields[fieldname] = self.vars[fieldname] 1471 1472 if dbio: 1473 if 'delete_this_record' in fields: 1474 # this should never happen but seems to happen to some 1475 del fields['delete_this_record'] 1476 for field in self.table: 1477 if not field.name in fields and field.writable is False \ 1478 and field.update is None and field.compute is None: 1479 if record_id and self.record: 1480 fields[field.name] = self.record[field.name] 1481 elif not self.table[field.name].default is None: 1482 fields[field.name] = self.table[field.name].default 1483 if keyed: 1484 if reduce(lambda x, y: x and y, record_id.values()): # if record_id 1485 if fields: 1486 qry = reduce(lambda x, y: x & y, 1487 [self.table[k] == self.record[k] for k in self.table._primarykey]) 1488 self.table._db(qry).update(**fields) 1489 else: 1490 pk = self.table.insert(**fields) 1491 if pk: 1492 self.vars.update(pk) 1493 else: 1494 ret = False 1495 else: 1496 if record_id: 1497 self.vars.id = self.record[self.id_field_name] 1498 if fields: 1499 self.table._db(self.table._id == self.record[ 1500 self.id_field_name]).update(**fields) 1501 else: 1502 self.vars.id = self.table.insert(**fields) 1503 self.accepted = ret 1504 return ret
1505 1506 AUTOTYPES = { 1507 type(''): ('string', None), 1508 type(True): ('boolean', None), 1509 type(1): ('integer', IS_INT_IN_RANGE(-1e12, +1e12)), 1510 type(1.0): ('double', IS_FLOAT_IN_RANGE()), 1511 type([]): ('list:string', None), 1512 type(datetime.date.today()): ('date', IS_DATE()), 1513 type(datetime.datetime.today()): ('datetime', IS_DATETIME()) 1514 } 1515 1516 @staticmethod
1517 - def dictform(dictionary, **kwargs):
1518 fields = [] 1519 for key, value in sorted(dictionary.items()): 1520 t, requires = SQLFORM.AUTOTYPES.get(type(value), (None, None)) 1521 if t: 1522 fields.append(Field(key, t, requires=requires, 1523 default=value)) 1524 return SQLFORM.factory(*fields, **kwargs)
1525 1526 @staticmethod
1527 - def smartdictform(session, name, filename=None, query=None, **kwargs):
1528 import os 1529 if query: 1530 session[name] = query.db(query).select().first().as_dict() 1531 elif os.path.exists(filename): 1532 env = {'datetime': datetime} 1533 session[name] = eval(open(filename).read(), {}, env) 1534 form = SQLFORM.dictform(session[name]) 1535 if form.process().accepted: 1536 session[name].update(form.vars) 1537 if query: 1538 query.db(query).update(**form.vars) 1539 else: 1540 open(filename, 'w').write(repr(session[name])) 1541 return form
1542 1543 @staticmethod
1544 - def factory(*fields, **attributes):
1545 """ 1546 generates a SQLFORM for the given fields. 1547 1548 Internally will build a non-database based data model 1549 to hold the fields. 1550 """ 1551 # Define a table name, this way it can be logical to our CSS. 1552 # And if you switch from using SQLFORM to SQLFORM.factory 1553 # your same css definitions will still apply. 1554 1555 table_name = attributes.get('table_name', 'no_table') 1556 1557 # So it won't interfear with SQLDB.define_table 1558 if 'table_name' in attributes: 1559 del attributes['table_name'] 1560 1561 return SQLFORM(DAL(None).define_table(table_name, *fields), 1562 **attributes)
1563 1564 @staticmethod
1565 - def build_query(fields, keywords):
1566 request = current.request 1567 if isinstance(keywords, (tuple, list)): 1568 keywords = keywords[0] 1569 request.vars.keywords = keywords 1570 key = keywords.strip() 1571 if key and not ' ' in key and not '"' in key and not "'" in key: 1572 SEARCHABLE_TYPES = ('string', 'text', 'list:string') 1573 parts = [field.contains( 1574 key) for field in fields if field.type in SEARCHABLE_TYPES] 1575 else: 1576 parts = None 1577 if parts: 1578 return reduce(lambda a, b: a | b, parts) 1579 else: 1580 return smart_query(fields, key)
1581 1582 @staticmethod
1583 - def search_menu(fields, 1584 search_options=None, 1585 prefix='w2p' 1586 ):
1587 T = current.T 1588 panel_id='%s_query_panel' % prefix 1589 fields_id='%s_query_fields' % prefix 1590 keywords_id='%s_keywords' % prefix 1591 field_id='%s_field' % prefix 1592 value_id='%s_value' % prefix 1593 search_options = search_options or { 1594 'string': ['=', '!=', '<', '>', '<=', '>=', 'starts with', 'contains', 'in', 'not in'], 1595 'text': ['=', '!=', '<', '>', '<=', '>=', 'starts with', 'contains', 'in', 'not in'], 1596 'date': ['=', '!=', '<', '>', '<=', '>='], 1597 'time': ['=', '!=', '<', '>', '<=', '>='], 1598 'datetime': ['=', '!=', '<', '>', '<=', '>='], 1599 'integer': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'], 1600 'double': ['=', '!=', '<', '>', '<=', '>='], 1601 'id': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'], 1602 'reference': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'], 1603 'boolean': ['=', '!=']} 1604 if fields[0]._db._adapter.dbengine == 'google:datastore': 1605 search_options['string'] = ['=', '!=', '<', '>', '<=', '>='] 1606 search_options['text'] = ['=', '!=', '<', '>', '<=', '>='] 1607 search_options['list:string'] = ['contains'] 1608 search_options['list:integer'] = ['contains'] 1609 search_options['list:reference'] = ['contains'] 1610 criteria = [] 1611 selectfields = [] 1612 for field in fields: 1613 name = str(field).replace('.', '-') 1614 options = search_options.get(field.type.split(' ')[0], None) 1615 if options: 1616 label = isinstance( 1617 field.label, str) and T(field.label) or field.label 1618 selectfields.append(OPTION(label, _value=str(field))) 1619 operators = SELECT(*[OPTION(T(option), _value=option) for option in options]) 1620 if field.type == 'boolean': 1621 value_input = SELECT( 1622 OPTION(T("True"), _value="T"), 1623 OPTION(T("False"), _value="F"), 1624 _id="%s_%s" % (value_id,name)) 1625 else: 1626 value_input = INPUT(_type='text', 1627 _id="%s_%s" % (value_id,name), 1628 _class=field.type) 1629 new_button = INPUT( 1630 _type="button", _value=T('New'), _class="btn", 1631 _onclick="%s_build_query('new','%s')" % (prefix,field)) 1632 and_button = INPUT( 1633 _type="button", _value=T('And'), _class="btn", 1634 _onclick="%s_build_query('and','%s')" % (prefix, field)) 1635 or_button = INPUT( 1636 _type="button", _value=T('Or'), _class="btn", 1637 _onclick="%s_build_query('or','%s')" % (prefix, field)) 1638 close_button = INPUT( 1639 _type="button", _value=T('Close'), _class="btn", 1640 _onclick="jQuery('#%s').slideUp()" % panel_id) 1641 1642 criteria.append(DIV( 1643 operators, value_input, new_button, 1644 and_button, or_button, close_button, 1645 _id='%s_%s' % (field_id, name), 1646 _class='w2p_query_row hidden', 1647 _style='display:inline')) 1648 1649 criteria.insert(0, SELECT( 1650 _id=fields_id, 1651 _onchange="jQuery('.w2p_query_row').hide();jQuery('#%s_'+jQuery('#%s').val().replace('.','-')).show();" % (field_id,fields_id), 1652 _style='float:left', 1653 *selectfields)) 1654 1655 fadd = SCRIPT(""" 1656 jQuery('#%(fields_id)s input,#%(fields_id)s select').css( 1657 'width','auto'); 1658 jQuery(function(){web2py_ajax_fields('#%(fields_id)s');}); 1659 function %(prefix)s_build_query(aggregator,a) { 1660 var b=a.replace('.','-'); 1661 var option = jQuery('#%(field_id)s_'+b+' select').val(); 1662 var value = jQuery('#%(value_id)s_'+b).val().replace('"','\\\\"'); 1663 var s=a+' '+option+' "'+value+'"'; 1664 var k=jQuery('#%(keywords_id)s'); 1665 var v=k.val(); 1666 if(aggregator=='new') k.val(s); else k.val((v?(v+' '+ aggregator +' '):'')+s); 1667 } 1668 """ % dict( 1669 prefix=prefix,fields_id=fields_id,keywords_id=keywords_id, 1670 field_id=field_id,value_id=value_id 1671 ) 1672 ) 1673 return CAT( 1674 DIV(_id=panel_id, _class='hidden', *criteria), fadd)
1675 1676 @staticmethod
1677 - def grid(query, 1678 fields=None, 1679 field_id=None, 1680 left=None, 1681 headers={}, 1682 orderby=None, 1683 groupby=None, 1684 searchable=True, 1685 sortable=True, 1686 paginate=20, 1687 deletable=True, 1688 editable=True, 1689 details=True, 1690 selectable=None, 1691 create=True, 1692 csv=True, 1693 links=None, 1694 links_in_grid=True, 1695 upload='<default>', 1696 args=[], 1697 user_signature=True, 1698 maxtextlengths={}, 1699 maxtextlength=20, 1700 onvalidation=None, 1701 onfailure=None, 1702 oncreate=None, 1703 onupdate=None, 1704 ondelete=None, 1705 sorter_icons=(XML('&#x2191;'), XML('&#x2193;')), 1706 ui = 'web2py', 1707 showbuttontext=True, 1708 _class="web2py_grid", 1709 formname='web2py_grid', 1710 search_widget='default', 1711 ignore_rw = False, 1712 formstyle = 'table3cols', 1713 exportclasses = None, 1714 formargs={}, 1715 createargs={}, 1716 editargs={}, 1717 viewargs={}, 1718 buttons_placement = 'right', 1719 links_placement = 'right' 1720 ):
1721 1722 # jQuery UI ThemeRoller classes (empty if ui is disabled) 1723 if ui == 'jquery-ui': 1724 ui = dict(widget='ui-widget', 1725 header='ui-widget-header', 1726 content='ui-widget-content', 1727 default='ui-state-default', 1728 cornerall='ui-corner-all', 1729 cornertop='ui-corner-top', 1730 cornerbottom='ui-corner-bottom', 1731 button='ui-button-text-icon-primary', 1732 buttontext='ui-button-text', 1733 buttonadd='ui-icon ui-icon-plusthick', 1734 buttonback='ui-icon ui-icon-arrowreturnthick-1-w', 1735 buttonexport='ui-icon ui-icon-transferthick-e-w', 1736 buttondelete='ui-icon ui-icon-trash', 1737 buttonedit='ui-icon ui-icon-pencil', 1738 buttontable='ui-icon ui-icon-triangle-1-e', 1739 buttonview='ui-icon ui-icon-zoomin', 1740 ) 1741 elif ui == 'web2py': 1742 ui = dict(widget='', 1743 header='', 1744 content='', 1745 default='', 1746 cornerall='', 1747 cornertop='', 1748 cornerbottom='', 1749 button='button btn', 1750 buttontext='buttontext button', 1751 buttonadd='icon plus icon-plus', 1752 buttonback='icon leftarrow icon-arrow-left', 1753 buttonexport='icon downarrow icon-download', 1754 buttondelete='icon trash icon-trash', 1755 buttonedit='icon pen icon-pencil', 1756 buttontable='icon rightarrow icon-arrow-right', 1757 buttonview='icon magnifier icon-zoom-in', 1758 ) 1759 elif not isinstance(ui, dict): 1760 raise RuntimeError('SQLFORM.grid ui argument must be a dictionary') 1761 1762 db = query._db 1763 T = current.T 1764 request = current.request 1765 session = current.session 1766 response = current.response 1767 logged = session.auth and session.auth.user 1768 wenabled = (not user_signature or logged) 1769 create = wenabled and create 1770 editable = wenabled and editable 1771 deletable = wenabled and deletable 1772 1773 def url(**b): 1774 b['args'] = args + b.get('args', []) 1775 localvars = request.vars.copy() 1776 localvars.update(b.get('vars', {})) 1777 b['vars'] = localvars 1778 b['hash_vars'] = False 1779 b['user_signature'] = user_signature 1780 return URL(**b)
1781 1782 def url2(**b): 1783 b['args'] = request.args + b.get('args', []) 1784 localvars = request.vars.copy() 1785 localvars.update(b.get('vars', {})) 1786 b['vars'] = localvars 1787 b['hash_vars'] = False 1788 b['user_signature'] = user_signature 1789 return URL(**b)
1790 1791 referrer = session.get('_web2py_grid_referrer_' + formname, url()) 1792 # if not user_signature every action is accessible 1793 # else forbid access unless 1794 # - url is based url 1795 # - url has valid signature (vars are not signed, only path_info) 1796 # = url does not contain 'create','delete','edit' (readonly) 1797 if user_signature: 1798 if not ( 1799 '/'.join(str(a) for a in args) == '/'.join(request.args) or 1800 URL.verify(request,user_signature=user_signature, 1801 hash_vars=False) or 1802 (request.args(len(args))=='view' and not logged)): 1803 session.flash = T('not authorized') 1804 redirect(referrer) 1805 1806 def gridbutton(buttonclass='buttonadd', buttontext=T('Add'), 1807 buttonurl=url(args=[]), callback=None, 1808 delete=None, trap=True): 1809 if showbuttontext: 1810 if callback: 1811 return A(SPAN(_class=ui.get(buttonclass)), 1812 SPAN(T(buttontext), _title=buttontext, 1813 _class=ui.get('buttontext')), 1814 callback=callback, delete=delete, 1815 _class=trap_class(ui.get('button'), trap)) 1816 else: 1817 return A(SPAN(_class=ui.get(buttonclass)), 1818 SPAN(T(buttontext), _title=buttontext, 1819 _class=ui.get('buttontext')), 1820 _href=buttonurl, 1821 _class=trap_class(ui.get('button'), trap)) 1822 else: 1823 if callback: 1824 return A(SPAN(_class=ui.get(buttonclass)), 1825 callback=callback, delete=delete, 1826 _title=buttontext, 1827 _class=trap_class(ui.get('buttontext'), trap)) 1828 else: 1829 return A(SPAN(_class=ui.get(buttonclass)), 1830 _href=buttonurl, _title=buttontext, 1831 _class=trap_class(ui.get('buttontext'), trap)) 1832 dbset = db(query) 1833 tablenames = db._adapter.tables(dbset.query) 1834 if left is not None: 1835 tablenames += db._adapter.tables(left) 1836 tables = [db[tablename] for tablename in tablenames] 1837 if not fields: 1838 fields = reduce(lambda a, b: a + b, 1839 [[field for field in table] for table in tables]) 1840 if not field_id: 1841 field_id = tables[0]._id 1842 columns = [str(field) for field in fields 1843 if field._tablename in tablenames] 1844 1845 if not any(str(f)==str(field_id) for f in fields): 1846 fields = [f for f in fields]+[field_id] 1847 table = field_id.table 1848 tablename = table._tablename 1849 if upload == '<default>': 1850 upload = lambda filename: url(args=['download', filename]) 1851 if len(request.args) > 1 and request.args[-2] == 'download': 1852 stream = response.download(request, db) 1853 raise HTTP(200, stream, **response.headers) 1854 1855 def buttons(edit=False, view=False, record=None): 1856 buttons = DIV(gridbutton('buttonback', 'Back', referrer), 1857 _class='form_header row_buttons %(header)s %(cornertop)s' % ui) 1858 if edit and (not callable(edit) or edit(record)): 1859 args = ['edit', table._tablename, request.args[-1]] 1860 buttons.append(gridbutton('buttonedit', 'Edit', 1861 url(args=args))) 1862 if view: 1863 args = ['view', table._tablename, request.args[-1]] 1864 buttons.append(gridbutton('buttonview', 'View', 1865 url(args=args))) 1866 if record and links: 1867 for link in links: 1868 if isinstance(link, dict): 1869 buttons.append(link['body'](record)) 1870 elif link(record): 1871 buttons.append(link(record)) 1872 return buttons 1873 1874 def linsert(lst, i, x): 1875 """ 1876 a = [1,2] 1877 linsert(a, 1, [0,3]) 1878 a = [1, 0, 3, 2] 1879 """ 1880 lst[i:i] = x 1881 1882 formfooter = DIV( 1883 _class='form_footer row_buttons %(header)s %(cornerbottom)s' % ui) 1884 1885 create_form = update_form = view_form = search_form = None 1886 sqlformargs = dict(formargs) 1887 1888 if create and len(request.args) > 1 and request.args[-2] == 'new': 1889 table = db[request.args[-1]] 1890 sqlformargs.update(createargs) 1891 create_form = SQLFORM( 1892 table, ignore_rw=ignore_rw, formstyle=formstyle, 1893 _class='web2py_form', 1894 **sqlformargs) 1895 create_form.process(formname=formname, 1896 next=referrer, 1897 onvalidation=onvalidation, 1898 onfailure=onfailure, 1899 onsuccess=oncreate) 1900 res = DIV(buttons(), create_form, formfooter, _class=_class) 1901 res.create_form = create_form 1902 res.update_form = update_form 1903 res.view_form = view_form 1904 res.search_form = search_form 1905 return res 1906 1907 elif details and len(request.args) > 2 and request.args[-3] == 'view': 1908 table = db[request.args[-2]] 1909 record = table(request.args[-1]) or redirect(referrer) 1910 sqlformargs.update(viewargs) 1911 view_form = SQLFORM( 1912 table, record, upload=upload, ignore_rw=ignore_rw, 1913 formstyle=formstyle, readonly=True, _class='web2py_form', 1914 **sqlformargs) 1915 res = DIV(buttons(edit=editable, record=record), view_form, 1916 formfooter, _class=_class) 1917 res.create_form = create_form 1918 res.update_form = update_form 1919 res.view_form = view_form 1920 res.search_form = search_form 1921 return res 1922 elif editable and len(request.args) > 2 and request.args[-3] == 'edit': 1923 table = db[request.args[-2]] 1924 record = table(request.args[-1]) or redirect(URL('error')) 1925 sqlformargs.update(editargs) 1926 update_form = SQLFORM( 1927 table, 1928 record, upload=upload, ignore_rw=ignore_rw, 1929 formstyle=formstyle, deletable=deletable, 1930 _class='web2py_form', 1931 submit_button=T('Submit'), 1932 delete_label=T('Check to delete'), 1933 **sqlformargs) 1934 update_form.process( 1935 formname=formname, 1936 onvalidation=onvalidation, 1937 onfailure=onfailure, 1938 onsuccess=onupdate, 1939 next=referrer) 1940 res = DIV(buttons(view=details, record=record), 1941 update_form, formfooter, _class=_class) 1942 res.create_form = create_form 1943 res.update_form = update_form 1944 res.view_form = view_form 1945 res.search_form = search_form 1946 return res 1947 elif deletable and len(request.args) > 2 and request.args[-3] == 'delete': 1948 table = db[request.args[-2]] 1949 if ondelete: 1950 ondelete(table, request.args[-1]) 1951 ret = db(table[table._id.name] == request.args[-1]).delete() 1952 return ret 1953 1954 exportManager = dict( 1955 csv_with_hidden_cols=(ExporterCSV, 'CSV (hidden cols)'), 1956 csv=(ExporterCSV, 'CSV'), 1957 xml=(ExporterXML, 'XML'), 1958 html=(ExporterHTML, 'HTML'), 1959 tsv_with_hidden_cols= 1960 (ExporterTSV, 'TSV (Excel compatible, hidden cols)'), 1961 tsv=(ExporterTSV, 'TSV (Excel compatible)')) 1962 if not exportclasses is None: 1963 """ 1964 remember: allow to set exportclasses=dict(csv=False) to disable the csv format 1965 """ 1966 exportManager.update(exportclasses) 1967 1968 export_type = request.vars._export_type 1969 if export_type: 1970 order = request.vars.order or '' 1971 if sortable: 1972 if order and not order == 'None': 1973 if order[:1] == '~': 1974 sign, rorder = '~', order[1:] 1975 else: 1976 sign, rorder = '', order 1977 tablename, fieldname = rorder.split('.', 1) 1978 orderby = db[tablename][fieldname] 1979 if sign == '~': 1980 orderby = ~orderby 1981 1982 expcolumns = columns 1983 if export_type.endswith('with_hidden_cols'): 1984 expcolumns = [f for f in fields if f._tablename in tablenames] 1985 if export_type in exportManager and exportManager[export_type]: 1986 if request.vars.keywords: 1987 try: 1988 dbset = dbset(SQLFORM.build_query( 1989 fields, request.vars.get('keywords', ''))) 1990 rows = dbset.select(cacheable=True, *expcolumns) 1991 except Exception, e: 1992 response.flash = T('Internal Error') 1993 rows = [] 1994 else: 1995 rows = dbset.select(left=left, orderby=orderby, 1996 cacheable=True, *expcolumns) 1997 1998 value = exportManager[export_type] 1999 clazz = value[0] if hasattr(value, '__getitem__') else value 2000 oExp = clazz(rows) 2001 filename = '.'.join(('rows', oExp.file_ext)) 2002 response.headers['Content-Type'] = oExp.content_type 2003 response.headers['Content-Disposition'] = \ 2004 'attachment;filename=' + filename + ';' 2005 raise HTTP(200, oExp.export(), **response.headers) 2006 2007 elif request.vars.records and not isinstance( 2008 request.vars.records, list): 2009 request.vars.records = [request.vars.records] 2010 elif not request.vars.records: 2011 request.vars.records = [] 2012 2013 session['_web2py_grid_referrer_' + formname] = url2(vars=request.vars) 2014 console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui) 2015 error = None 2016 if create: 2017 add = gridbutton( 2018 buttonclass='buttonadd', 2019 buttontext=T('Add'), 2020 buttonurl=url(args=['new', tablename])) 2021 if not searchable: 2022 console.append(add) 2023 else: 2024 add = '' 2025 2026 if searchable: 2027 sfields = reduce(lambda a, b: a + b, 2028 [[f for f in t if f.readable] for t in tables]) 2029 if isinstance(search_widget, dict): 2030 search_widget = search_widget[tablename] 2031 if search_widget == 'default': 2032 prefix = formname == 'web2py_grid' and 'w2p' or 'w2p_%s' % formname 2033 search_menu = SQLFORM.search_menu(sfields, prefix=prefix) 2034 spanel_id = '%s_query_fields' % prefix 2035 sfields_id = '%s_query_panel' % prefix 2036 skeywords_id = '%s_keywords' % prefix 2037 search_widget = lambda sfield, url: CAT(FORM( 2038 INPUT(_name='keywords', _value=request.vars.keywords, 2039 _id=skeywords_id, 2040 _onfocus="jQuery('#%s').change();jQuery('#%s').slideDown();" % (spanel_id, sfields_id)), 2041 INPUT(_type='submit', _value=T('Search'), _class="btn"), 2042 INPUT(_type='submit', _value=T('Clear'), _class="btn", 2043 _onclick="jQuery('#%s').val('');" % skeywords_id), 2044 _method="GET", _action=url), search_menu) 2045 form = search_widget and search_widget(sfields, url()) or '' 2046 console.append(add) 2047 console.append(form) 2048 keywords = request.vars.get('keywords', '') 2049 try: 2050 if callable(searchable): 2051 subquery = searchable(sfields, keywords) 2052 else: 2053 subquery = SQLFORM.build_query(sfields, keywords) 2054 except RuntimeError: 2055 subquery = None 2056 error = T('Invalid query') 2057 else: 2058 subquery = None 2059 2060 if subquery: 2061 dbset = dbset(subquery) 2062 try: 2063 if left or groupby: 2064 c = 'count(*)' 2065 nrows = dbset.select(c, left=left, cacheable=True, 2066 groupby=groupby).first()[c] 2067 elif dbset._db._adapter.dbengine=='google:datastore': 2068 #if we don't set a limit, this can timeout for a large table 2069 nrows = dbset.db._adapter.count(dbset.query, limit=1000) 2070 else: 2071 nrows = dbset.count() 2072 except: 2073 nrows = 0 2074 error = T('Unsupported query') 2075 2076 order = request.vars.order or '' 2077 if sortable: 2078 if order and not order == 'None': 2079 tablename, fieldname = order.split('~')[-1].split('.', 1) 2080 sort_field = db[tablename][fieldname] 2081 exception = sort_field.type in ('date', 'datetime', 'time') 2082 if exception: 2083 orderby = (order[:1] == '~' and sort_field) or ~sort_field 2084 else: 2085 orderby = (order[:1] == '~' and ~sort_field) or sort_field 2086 2087 headcols = [] 2088 if selectable: 2089 headcols.append(TH(_class=ui.get('default'))) 2090 for field in fields: 2091 if columns and not str(field) in columns: 2092 continue 2093 if not field.readable: 2094 continue 2095 key = str(field) 2096 header = headers.get(str(field), field.label or key) 2097 if sortable: 2098 if key == order: 2099 key, marker = '~' + order, sorter_icons[0] 2100 elif key == order[1:]: 2101 marker = sorter_icons[1] 2102 else: 2103 marker = '' 2104 header = A(header, marker, _href=url(vars=dict( 2105 keywords=request.vars.keywords or '', 2106 order=key)), _class=trap_class()) 2107 headcols.append(TH(header, _class=ui.get('default'))) 2108 2109 toadd = [] 2110 if links and links_in_grid: 2111 for link in links: 2112 if isinstance(link, dict): 2113 toadd.append(TH(link['header'], _class=ui.get('default'))) 2114 if links_placement in ['right', 'both']: 2115 headcols.extend(toadd) 2116 if links_placement in ['left', 'both']: 2117 linsert(headcols, 0, toadd) 2118 2119 # Include extra column for buttons if needed. 2120 include_buttons_column = (details or editable or deletable or 2121 (links and links_in_grid and 2122 not all([isinstance(link, dict) for link in links]))) 2123 if include_buttons_column: 2124 if buttons_placement in ['right', 'both']: 2125 headcols.append(TH(_class=ui.get('default',''))) 2126 if buttons_placement in ['left', 'both']: 2127 headcols.insert(0, TH(_class=ui.get('default',''))) 2128 2129 head = TR(*headcols, **dict(_class=ui.get('header'))) 2130 2131 cursor = True 2132 #figure out what page we are one to setup the limitby 2133 if paginate and dbset._db._adapter.dbengine=='google:datastore': 2134 cursor = request.vars.cursor or True 2135 limitby = (0, paginate) 2136 try: page = int(request.vars.page or 1)-1 2137 except ValueError: page = 0 2138 elif paginate and paginate<nrows: 2139 try: page = int(request.vars.page or 1)-1 2140 except ValueError: page = 0 2141 limitby = (paginate*page,paginate*(page+1)) 2142 else: 2143 limitby = None 2144 2145 try: 2146 table_fields = [f for f in fields if f._tablename in tablenames] 2147 if dbset._db._adapter.dbengine=='google:datastore': 2148 rows = dbset.select(left=left,orderby=orderby, 2149 groupby=groupby,limitby=limitby, 2150 reusecursor=cursor, 2151 cacheable=True,*table_fields) 2152 next_cursor = dbset._db.get('_lastcursor', None) 2153 else: 2154 rows = dbset.select(left=left,orderby=orderby, 2155 groupby=groupby,limitby=limitby, 2156 cacheable=True,*table_fields) 2157 except SyntaxError: 2158 rows = None 2159 next_cursor = None 2160 error = T("Query Not Supported") 2161 except Exception, e: 2162 rows = None 2163 next_cursor = None 2164 error = T("Query Not Supported: %s")%e 2165 2166 message = error 2167 if not message and nrows: 2168 if dbset._db._adapter.dbengine=='google:datastore' and nrows>=1000: 2169 message = T('at least %(nrows)s records found') % dict(nrows=nrows) 2170 else: 2171 message = T('%(nrows)s records found') % dict(nrows=nrows) 2172 console.append(DIV(message,_class='web2py_counter')) 2173 2174 paginator = UL() 2175 if paginate and dbset._db._adapter.dbengine=='google:datastore': 2176 #this means we may have a large table with an unknown number of rows. 2177 try: 2178 page = int(request.vars.page or 1)-1 2179 except ValueError: 2180 page = 0 2181 paginator.append(LI('page %s'%(page+1))) 2182 if next_cursor: 2183 d = dict(page=page+2, cursor=next_cursor) 2184 if order: d['order']=order 2185 if request.vars.keywords: d['keywords']=request.vars.keywords 2186 paginator.append(LI( 2187 A('next',_href=url(vars=d),_class=trap_class()))) 2188 elif paginate and paginate<nrows: 2189 npages, reminder = divmod(nrows, paginate) 2190 if reminder: 2191 npages += 1 2192 try: 2193 page = int(request.vars.page or 1) - 1 2194 except ValueError: 2195 page = 0 2196 2197 def self_link(name, p): 2198 d = dict(page=p + 1) 2199 if order: 2200 d['order'] = order 2201 if request.vars.keywords: 2202 d['keywords'] = request.vars.keywords 2203 return A(name, _href=url(vars=d), _class=trap_class()) 2204 NPAGES = 5 # window is 2*NPAGES 2205 if page > NPAGES + 1: 2206 paginator.append(LI(self_link('<<', 0))) 2207 if page > NPAGES: 2208 paginator.append(LI(self_link('<', page - 1))) 2209 pages = range(max(0, page - NPAGES), min(page + NPAGES, npages)) 2210 for p in pages: 2211 if p == page: 2212 paginator.append(LI(A(p + 1, _onclick='return false'), 2213 _class=trap_class('current'))) 2214 else: 2215 paginator.append(LI(self_link(p + 1, p))) 2216 if page < npages - NPAGES: 2217 paginator.append(LI(self_link('>', page + 1))) 2218 if page < npages - NPAGES - 1: 2219 paginator.append(LI(self_link('>>', npages - 1))) 2220 else: 2221 limitby = None 2222 2223 if rows: 2224 htmltable = TABLE(THEAD(head)) 2225 tbody = TBODY() 2226 numrec = 0 2227 for row in rows: 2228 trcols = [] 2229 id = row[field_id] 2230 if selectable: 2231 trcols.append( 2232 INPUT(_type="checkbox", _name="records", _value=id, 2233 value=request.vars.records)) 2234 for field in fields: 2235 if not str(field) in columns: 2236 continue 2237 if not field.readable: 2238 continue 2239 if field.type == 'blob': 2240 continue 2241 value = row[field] 2242 maxlength = maxtextlengths.get(str(field), maxtextlength) 2243 if field.represent: 2244 try: 2245 value = field.represent(value, row) 2246 except KeyError: 2247 try: 2248 value = field.represent( 2249 value, row[field._tablename]) 2250 except KeyError: 2251 pass 2252 elif field.type == 'boolean': 2253 value = INPUT(_type="checkbox", _checked=value, 2254 _disabled=True) 2255 elif field.type == 'upload': 2256 if value: 2257 if callable(upload): 2258 value = A( 2259 current.T('file'), _href=upload(value)) 2260 elif upload: 2261 value = A(current.T('file'), 2262 _href='%s/%s' % (upload, value)) 2263 else: 2264 value = '' 2265 if isinstance(value, str): 2266 value = truncate_string(value, maxlength) 2267 elif not isinstance(value, DIV): 2268 value = field.formatter(value) 2269 trcols.append(TD(value)) 2270 row_buttons = TD(_class='row_buttons') 2271 if links and links_in_grid: 2272 toadd = [] 2273 for link in links: 2274 if isinstance(link, dict): 2275 toadd.append(TD(link['body'](row))) 2276 else: 2277 if link(row): 2278 row_buttons.append(link(row)) 2279 if links_placement in ['right', 'both']: 2280 trcols.extend(toadd) 2281 if links_placement in ['left', 'both']: 2282 linsert(trcols, 0, toadd) 2283 2284 if include_buttons_column: 2285 if details and (not callable(details) or details(row)): 2286 row_buttons.append(gridbutton( 2287 'buttonview', 'View', 2288 url(args=['view', tablename, id]))) 2289 if editable and (not callable(editable) or editable(row)): 2290 row_buttons.append(gridbutton( 2291 'buttonedit', 'Edit', 2292 url(args=['edit', tablename, id]))) 2293 if deletable and (not callable(deletable) or deletable(row)): 2294 row_buttons.append(gridbutton( 2295 'buttondelete', 'Delete', 2296 callback=url(args=['delete', tablename, id]), 2297 delete='tr')) 2298 if buttons_placement in ['right', 'both']: 2299 trcols.append(row_buttons) 2300 if buttons_placement in ['left', 'both']: 2301 trcols.insert(0, row_buttons) 2302 if numrec % 2 == 0: 2303 classtr = 'even' 2304 else: 2305 classtr = 'odd' 2306 numrec += 1 2307 if id: 2308 rid = id 2309 if callable(rid): # can this ever be callable? 2310 rid = rid(row) 2311 tr = TR(*trcols, **dict( 2312 _id=rid, 2313 _class='%s %s' % (classtr, 'with_id'))) 2314 else: 2315 tr = TR(*trcols, **dict(_class=classtr)) 2316 tbody.append(tr) 2317 htmltable.append(tbody) 2318 htmltable = DIV(htmltable, _style='width:100%;overflow-x:auto') 2319 if selectable: 2320 htmltable = FORM(htmltable, INPUT(_type="submit")) 2321 if htmltable.process(formname=formname).accepted: 2322 htmltable.vars.records = htmltable.vars.records or [] 2323 htmltable.vars.records = htmltable.vars.records if type(htmltable.vars.records) == list else [htmltable.vars.records] 2324 records = [int(r) for r in htmltable.vars.records] 2325 selectable(records) 2326 redirect(referrer) 2327 else: 2328 htmltable = DIV(current.T('No records found')) 2329 2330 if csv and nrows: 2331 export_links = [] 2332 for k, v in sorted(exportManager.items()): 2333 if not v: 2334 continue 2335 label = v[1] if hasattr(v, "__getitem__") else k 2336 link = url2(vars=dict( 2337 order=request.vars.order or '', 2338 _export_type=k, 2339 keywords=request.vars.keywords or '')) 2340 export_links.append(A(T(label), _href=link)) 2341 export_menu = \ 2342 DIV(T('Export:'), _class="w2p_export_menu", *export_links) 2343 else: 2344 export_menu = None 2345 2346 res = DIV(console, DIV(htmltable, _class="web2py_table"), 2347 _class='%s %s' % (_class, ui.get('widget'))) 2348 if paginator.components: 2349 res.append( 2350 DIV(paginator, 2351 _class="web2py_paginator %(header)s %(cornerbottom)s" % ui)) 2352 if export_menu: 2353 res.append(export_menu) 2354 res.create_form = create_form 2355 res.update_form = update_form 2356 res.view_form = view_form 2357 res.search_form = search_form 2358 return res 2359 2360 @staticmethod
2361 - def smartgrid(table, constraints=None, linked_tables=None, 2362 links=None, links_in_grid=True, 2363 args=None, user_signature=True, 2364 divider='>', breadcrumbs_class='', 2365 **kwargs):
2366 """ 2367 @auth.requires_login() 2368 def index(): 2369 db.define_table('person',Field('name'),format='%(name)s') 2370 db.define_table('dog', 2371 Field('name'),Field('owner',db.person),format='%(name)s') 2372 db.define_table('comment',Field('body'),Field('dog',db.dog)) 2373 if db(db.person).isempty(): 2374 from gluon.contrib.populate import populate 2375 populate(db.person,300) 2376 populate(db.dog,300) 2377 populate(db.comment,1000) 2378 db.commit() 2379 form=SQLFORM.smartgrid(db[request.args(0) or 'person']) #*** 2380 return dict(form=form) 2381 2382 *** builds a complete interface to navigate all tables links 2383 to the request.args(0) 2384 table: pagination, search, view, edit, delete, 2385 children, parent, etc. 2386 2387 constraints is a dict {'table':query} that limits which 2388 records can be accessible 2389 links is a dict like 2390 {'tablename':[lambda row: A(....), ...]} 2391 that will add buttons when table tablename is displayed 2392 linked_tables is a optional list of tablenames of tables 2393 to be linked 2394 """ 2395 request, T = current.request, current.T 2396 if args is None: 2397 args = [] 2398 2399 def url(**b): 2400 b['args'] = request.args[:nargs] + b.get('args', []) 2401 b['hash_vars'] = False 2402 b['user_signature'] = user_signature 2403 return URL(**b)
2404 2405 db = table._db 2406 breadcrumbs = [] 2407 if request.args(len(args)) != table._tablename: 2408 request.args[:] = args + [table._tablename] 2409 if links is None: 2410 links = {} 2411 if constraints is None: 2412 constraints = {} 2413 field = None 2414 try: 2415 nargs = len(args) + 1 2416 previous_tablename, previous_fieldname, previous_id = \ 2417 table._tablename, None, None 2418 while len(request.args) > nargs: 2419 key = request.args(nargs) 2420 if '.' in key: 2421 id = request.args(nargs + 1) 2422 tablename, fieldname = key.split('.', 1) 2423 table = db[tablename] 2424 field = table[fieldname] 2425 field.default = id 2426 referee = field.type[10:] 2427 if referee != previous_tablename: 2428 raise HTTP(400) 2429 cond = constraints.get(referee, None) 2430 if cond: 2431 record = db( 2432 db[referee].id == id)(cond).select().first() 2433 else: 2434 record = db[referee](id) 2435 if previous_id: 2436 if record[previous_fieldname] != int(previous_id): 2437 raise HTTP(400) 2438 previous_tablename, previous_fieldname, previous_id = \ 2439 tablename, fieldname, id 2440 try: 2441 format = db[referee]._format 2442 if callable(format): 2443 name = format(record) 2444 else: 2445 name = format % record 2446 except TypeError: 2447 name = id 2448 breadcrumbs.append( 2449 LI(A(T(db[referee]._plural), 2450 _class=trap_class(), 2451 _href=url()), 2452 SPAN(divider, _class='divider'), 2453 _class='w2p_grid_breadcrumb_elem')) 2454 if kwargs.get('details', True): 2455 breadcrumbs.append( 2456 LI(A(name, _class=trap_class(), 2457 _href=url(args=['view', referee, id])), 2458 SPAN(divider, _class='divider'), 2459 _class='w2p_grid_breadcrumb_elem')) 2460 nargs += 2 2461 else: 2462 break 2463 if nargs > len(args) + 1: 2464 query = (field == id) 2465 if isinstance(linked_tables, dict): 2466 linked_tables = linked_tables.get(table._tablename, []) 2467 if linked_tables is None or referee in linked_tables: 2468 field.represent = lambda id, r=None, referee=referee, rep=field.represent: A(callable(rep) and rep(id) or id, _class=trap_class(), _href=url(args=['view', referee, id])) 2469 except (KeyError, ValueError, TypeError): 2470 redirect(URL(args=table._tablename)) 2471 if nargs == len(args) + 1: 2472 query = table.id > 0 2473 2474 # filter out data info for displayed table 2475 if table._tablename in constraints: 2476 query = query & constraints[table._tablename] 2477 if isinstance(links, dict): 2478 links = links.get(table._tablename, []) 2479 for key in 'columns,orderby,searchable,sortable,paginate,deletable,editable,details,selectable,create,fields'.split(','): 2480 if isinstance(kwargs.get(key, None), dict): 2481 if table._tablename in kwargs[key]: 2482 kwargs[key] = kwargs[key][table._tablename] 2483 else: 2484 del kwargs[key] 2485 check = {} 2486 id_field_name = table._id.name 2487 for rfield in table._referenced_by: 2488 check[rfield.tablename] = \ 2489 check.get(rfield.tablename, []) + [rfield.name] 2490 if isinstance(linked_tables, dict): 2491 linked_tables = linked_tables.get(table._tablename, []) 2492 for tablename in sorted(check): 2493 linked_fieldnames = check[tablename] 2494 tb = db[tablename] 2495 multiple_links = len(linked_fieldnames) > 1 2496 for fieldname in linked_fieldnames: 2497 if linked_tables is None or tablename in linked_tables: 2498 t = T(tb._plural) if not multiple_links else \ 2499 T(tb._plural + '(' + fieldname + ')') 2500 args0 = tablename + '.' + fieldname 2501 links.append( 2502 lambda row, t=t, nargs=nargs, args0=args0: 2503 A(SPAN(t), _class=trap_class(), _href=url( 2504 args=[args0, row[id_field_name]]))) 2505 2506 grid = SQLFORM.grid(query, args=request.args[:nargs], links=links, 2507 links_in_grid=links_in_grid, 2508 user_signature=user_signature, **kwargs) 2509 if isinstance(grid, DIV): 2510 header = table._plural + (field and ' for ' + field.name or '') 2511 breadcrumbs.append(LI(A(T(header), _class=trap_class(), 2512 _href=url()), _class='active w2p_grid_breadcrumb_elem')) 2513 grid.insert( 2514 0, DIV(UL(*breadcrumbs, **{'_class': breadcrumbs_class}), 2515 _class='web2py_breadcrumbs')) 2516 return grid 2517
2518 2519 -class SQLTABLE(TABLE):
2520 2521 """ 2522 given a Rows object, as returned by a db().select(), generates 2523 an html table with the rows. 2524 2525 optional arguments: 2526 2527 :param linkto: URL (or lambda to generate a URL) to edit individual records 2528 :param upload: URL to download uploaded files 2529 :param orderby: Add an orderby link to column headers. 2530 :param headers: dictionary of headers to headers redefinions 2531 headers can also be a string to gerenare the headers from data 2532 for now only headers="fieldname:capitalize", 2533 headers="labels" and headers=None are supported 2534 :param truncate: length at which to truncate text in table cells. 2535 Defaults to 16 characters. 2536 :param columns: a list or dict contaning the names of the columns to be shown 2537 Defaults to all 2538 2539 Optional names attributes for passed to the <table> tag 2540 2541 The keys of headers and columns must be of the form "tablename.fieldname" 2542 2543 Simple linkto example:: 2544 2545 rows = db.select(db.sometable.ALL) 2546 table = SQLTABLE(rows, linkto='someurl') 2547 2548 This will link rows[id] to .../sometable/value_of_id 2549 2550 2551 More advanced linkto example:: 2552 2553 def mylink(field, type, ref): 2554 return URL(args=[field]) 2555 2556 rows = db.select(db.sometable.ALL) 2557 table = SQLTABLE(rows, linkto=mylink) 2558 2559 This will link rows[id] to 2560 current_app/current_controlle/current_function/value_of_id 2561 2562 New Implements: 24 June 2011: 2563 ----------------------------- 2564 2565 :param selectid: The id you want to select 2566 :param renderstyle: Boolean render the style with the table 2567 2568 :param extracolums = [{'label':A('Extra',_href='#'), 2569 'class': '', #class name of the header 2570 'width':'', #width in pixels or % 2571 'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id), 2572 'selected': False #agregate class selected to this column 2573 }] 2574 2575 2576 :param headers = {'table.id':{'label':'Id', 2577 'class':'', #class name of the header 2578 'width':'', #width in pixels or % 2579 'truncate': 16, #truncate the content to... 2580 'selected': False #agregate class selected to this column 2581 }, 2582 'table.myfield':{'label':'My field', 2583 'class':'', #class name of the header 2584 'width':'', #width in pixels or % 2585 'truncate': 16, #truncate the content to... 2586 'selected': False #agregate class selected to this column 2587 }, 2588 } 2589 2590 table = SQLTABLE(rows, headers=headers, extracolums=extracolums) 2591 2592 2593 """ 2594
2595 - def __init__( 2596 self, 2597 sqlrows, 2598 linkto=None, 2599 upload=None, 2600 orderby=None, 2601 headers={}, 2602 truncate=16, 2603 columns=None, 2604 th_link='', 2605 extracolumns=None, 2606 selectid=None, 2607 renderstyle=False, 2608 cid=None, 2609 **attributes 2610 ):
2611 2612 TABLE.__init__(self, **attributes) 2613 2614 self.components = [] 2615 self.attributes = attributes 2616 self.sqlrows = sqlrows 2617 (components, row) = (self.components, []) 2618 if not sqlrows: 2619 return 2620 if not columns: 2621 columns = sqlrows.colnames 2622 if headers == 'fieldname:capitalize': 2623 headers = {} 2624 for c in columns: 2625 headers[c] = c.split('.')[-1].replace('_', ' ').title() 2626 elif headers == 'labels': 2627 headers = {} 2628 for c in columns: 2629 (t, f) = c.split('.') 2630 field = sqlrows.db[t][f] 2631 headers[c] = field.label 2632 if headers is None: 2633 headers = {} 2634 else: 2635 for c in columns: # new implement dict 2636 if isinstance(headers.get(c, c), dict): 2637 coldict = headers.get(c, c) 2638 attrcol = dict() 2639 if coldict['width'] != "": 2640 attrcol.update(_width=coldict['width']) 2641 if coldict['class'] != "": 2642 attrcol.update(_class=coldict['class']) 2643 row.append(TH(coldict['label'], **attrcol)) 2644 elif orderby: 2645 row.append(TH(A(headers.get(c, c), 2646 _href=th_link + '?orderby=' + c, cid=cid))) 2647 else: 2648 row.append(TH(headers.get(c, c))) 2649 2650 if extracolumns: # new implement dict 2651 for c in extracolumns: 2652 attrcol = dict() 2653 if c['width'] != "": 2654 attrcol.update(_width=c['width']) 2655 if c['class'] != "": 2656 attrcol.update(_class=c['class']) 2657 row.append(TH(c['label'], **attrcol)) 2658 2659 components.append(THEAD(TR(*row))) 2660 2661 tbody = [] 2662 for (rc, record) in enumerate(sqlrows): 2663 row = [] 2664 if rc % 2 == 0: 2665 _class = 'even' 2666 else: 2667 _class = 'odd' 2668 2669 if not selectid is None: # new implement 2670 if record.get('id') == selectid: 2671 _class += ' rowselected' 2672 2673 for colname in columns: 2674 if not table_field.match(colname): 2675 if "_extra" in record and colname in record._extra: 2676 r = record._extra[colname] 2677 row.append(TD(r)) 2678 continue 2679 else: 2680 raise KeyError( 2681 "Column %s not found (SQLTABLE)" % colname) 2682 (tablename, fieldname) = colname.split('.') 2683 try: 2684 field = sqlrows.db[tablename][fieldname] 2685 except (KeyError, AttributeError): 2686 field = None 2687 if tablename in record \ 2688 and isinstance(record, Row) \ 2689 and isinstance(record[tablename], Row): 2690 r = record[tablename][fieldname] 2691 elif fieldname in record: 2692 r = record[fieldname] 2693 else: 2694 raise SyntaxError('something wrong in Rows object') 2695 r_old = r 2696 if not field or isinstance(field, (Field.Virtual, Field.Lazy)): 2697 pass 2698 elif linkto and field.type == 'id': 2699 try: 2700 href = linkto(r, 'table', tablename) 2701 except TypeError: 2702 href = '%s/%s/%s' % (linkto, tablename, r_old) 2703 r = A(r, _href=href) 2704 elif isinstance(field.type, str) and field.type.startswith('reference'): 2705 if linkto: 2706 ref = field.type[10:] 2707 try: 2708 href = linkto(r, 'reference', ref) 2709 except TypeError: 2710 href = '%s/%s/%s' % (linkto, ref, r_old) 2711 if ref.find('.') >= 0: 2712 tref, fref = ref.split('.') 2713 if hasattr(sqlrows.db[tref], '_primarykey'): 2714 href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref: r})) 2715 r = A(represent(field, r, record), _href=str(href)) 2716 elif field.represent: 2717 r = represent(field, r, record) 2718 elif linkto and hasattr(field._table, '_primarykey')\ 2719 and fieldname in field._table._primarykey: 2720 # have to test this with multi-key tables 2721 key = urllib.urlencode(dict([ 2722 ((tablename in record 2723 and isinstance(record, Row) 2724 and isinstance(record[tablename], Row)) and 2725 (k, record[tablename][k])) or (k, record[k]) 2726 for k in field._table._primarykey])) 2727 r = A(r, _href='%s/%s?%s' % (linkto, tablename, key)) 2728 elif isinstance(field.type, str) and field.type.startswith('list:'): 2729 r = represent(field, r or [], record) 2730 elif field.represent: 2731 r = represent(field, r, record) 2732 elif field.type == 'blob' and r: 2733 r = 'DATA' 2734 elif field.type == 'upload': 2735 if upload and r: 2736 r = A(current.T('file'), _href='%s/%s' % (upload, r)) 2737 elif r: 2738 r = current.T('file') 2739 else: 2740 r = '' 2741 elif field.type in ['string', 'text']: 2742 r = str(field.formatter(r)) 2743 if headers != {}: # new implement dict 2744 if isinstance(headers[colname], dict): 2745 if isinstance(headers[colname]['truncate'], int): 2746 r = truncate_string( 2747 r, headers[colname]['truncate']) 2748 elif not truncate is None: 2749 r = truncate_string(r, truncate) 2750 attrcol = dict() # new implement dict 2751 if headers != {}: 2752 if isinstance(headers[colname], dict): 2753 colclass = headers[colname]['class'] 2754 if headers[colname]['selected']: 2755 colclass = str(headers[colname] 2756 ['class'] + " colselected").strip() 2757 if colclass != "": 2758 attrcol.update(_class=colclass) 2759 2760 row.append(TD(r, **attrcol)) 2761 2762 if extracolumns: # new implement dict 2763 for c in extracolumns: 2764 attrcol = dict() 2765 colclass = c['class'] 2766 if c['selected']: 2767 colclass = str(c['class'] + " colselected").strip() 2768 if colclass != "": 2769 attrcol.update(_class=colclass) 2770 contentfunc = c['content'] 2771 row.append(TD(contentfunc(record, rc), **attrcol)) 2772 2773 tbody.append(TR(_class=_class, *row)) 2774 2775 if renderstyle: 2776 components.append(STYLE(self.style())) 2777 2778 components.append(TBODY(*tbody))
2779
2780 - def style(self):
2781 2782 css = ''' 2783 table tbody tr.odd { 2784 background-color: #DFD; 2785 } 2786 table tbody tr.even { 2787 background-color: #EFE; 2788 } 2789 table tbody tr.rowselected { 2790 background-color: #FDD; 2791 } 2792 table tbody tr td.colselected { 2793 background-color: #FDD; 2794 } 2795 table tbody tr:hover { 2796 background: #DDF; 2797 } 2798 ''' 2799 2800 return css
2801 2802 form_factory = SQLFORM.factory # for backward compatibility, deprecated
2803 2804 2805 -class ExportClass(object):
2806 label = None 2807 file_ext = None 2808 content_type = None 2809
2810 - def __init__(self, rows):
2811 self.rows = rows
2812
2813 - def represented(self):
2814 def none_exception(value): 2815 """ 2816 returns a cleaned up value that can be used for csv export: 2817 - unicode text is encoded as such 2818 - None values are replaced with the given representation (default <NULL>) 2819 """ 2820 if value is None: 2821 return '<NULL>' 2822 elif isinstance(value, unicode): 2823 return value.encode('utf8') 2824 elif isinstance(value, Reference): 2825 return int(value) 2826 elif hasattr(value, 'isoformat'): 2827 return value.isoformat()[:19].replace('T', ' ') 2828 elif isinstance(value, (list, tuple)): # for type='list:..' 2829 return bar_encode(value) 2830 return value
2831 2832 represented = [] 2833 for record in self.rows: 2834 row = [] 2835 for col in self.rows.colnames: 2836 if not REGEX_TABLE_DOT_FIELD.match(col): 2837 row.append(record._extra[col]) 2838 else: 2839 (t, f) = col.split('.') 2840 field = self.rows.db[t][f] 2841 if isinstance(record.get(t, None), (Row, dict)): 2842 value = record[t][f] 2843 else: 2844 value = record[f] 2845 if field.type == 'blob' and not value is None: 2846 value = '' 2847 elif field.represent: 2848 value = field.represent(value, record) 2849 row.append(none_exception(value)) 2850 2851 represented.append(row) 2852 return represented
2853
2854 - def export(self):
2855 raise NotImplementedError
2856
2857 2858 -class ExporterTSV(ExportClass):
2859 2860 label = 'TSV' 2861 file_ext = "csv" 2862 content_type = "text/tab-separated-values" 2863
2864 - def __init__(self, rows):
2866
2867 - def export(self):
2868 2869 out = cStringIO.StringIO() 2870 final = cStringIO.StringIO() 2871 import csv 2872 writer = csv.writer(out, delimiter='\t') 2873 if self.rows: 2874 import codecs 2875 final.write(codecs.BOM_UTF16) 2876 writer.writerow( 2877 [unicode(col).encode("utf8") for col in self.rows.colnames]) 2878 data = out.getvalue().decode("utf8") 2879 data = data.encode("utf-16") 2880 data = data[2:] 2881 final.write(data) 2882 out.truncate(0) 2883 records = self.represented() 2884 for row in records: 2885 writer.writerow( 2886 [str(col).decode('utf8').encode("utf-8") for col in row]) 2887 data = out.getvalue().decode("utf8") 2888 data = data.encode("utf-16") 2889 data = data[2:] 2890 final.write(data) 2891 out.truncate(0) 2892 return str(final.getvalue())
2893
2894 2895 -class ExporterCSV(ExportClass):
2896 label = 'CSV' 2897 file_ext = "csv" 2898 content_type = "text/csv" 2899
2900 - def __init__(self, rows):
2902
2903 - def export(self):
2904 if self.rows: 2905 return str(self.rows) 2906 else: 2907 return ''
2908
2909 2910 -class ExporterHTML(ExportClass):
2911 label = 'HTML' 2912 file_ext = "html" 2913 content_type = "text/html" 2914
2915 - def __init__(self, rows):
2917
2918 - def export(self):
2919 out = cStringIO.StringIO() 2920 out.write('<html>\n<body>\n<table>\n') 2921 if self.rows: 2922 colnames = [a.split('.') for a in self.rows.colnames] 2923 for row in self.rows.records: 2924 out.write('<tr>\n') 2925 for col in colnames: 2926 out.write('<td>' + str(row[col[0]][col[1]]) + '</td>\n') 2927 out.write('</tr>\n') 2928 out.write('</table>\n</body>\n</html>') 2929 return str(out.getvalue())
2930
2931 2932 -class ExporterXML(ExportClass):
2933 label = 'XML' 2934 file_ext = "xml" 2935 content_type = "text/xml" 2936
2937 - def __init__(self, rows):
2939
2940 - def export(self):
2941 out = cStringIO.StringIO() 2942 out.write('<rows>\n') 2943 if self.rows: 2944 colnames = [a.split('.') for a in self.rows.colnames] 2945 for row in self.rows.records: 2946 out.write('<row>\n') 2947 for col in colnames: 2948 out.write( 2949 '<%s>' % col + str(row[col[0]][col[1]]) + '</%s>\n' % col) 2950 out.write('</row>\n') 2951 out.write('</rows>') 2952 return str(out.getvalue())
2953