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

Source Code for Module 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   
  17  from http import HTTP 
  18  from html import XML, SPAN, TAG, A, DIV, UL, LI, TEXTAREA, BR, IMG, SCRIPT 
  19  from html import FORM, INPUT, LABEL, OPTION, SELECT 
  20  from html import TABLE, THEAD, TBODY, TR, TD, TH 
  21  from html import URL as Url 
  22  from dal import DAL, Table, Row, CALLABLETYPES 
  23  from storage import Storage 
  24  from utils import md5_hash 
  25  from validators import IS_EMPTY_OR 
  26   
  27  import urllib 
  28  import re 
  29  import cStringIO 
  30   
  31   
  32  table_field = re.compile('[\w_]+\.[\w_]+') 
  33  widget_class = re.compile('^\w*') 
  34   
35 -def safe_int(x):
36 try: 37 return int(x) 38 except ValueError: 39 return 0
40
41 -def safe_float(x):
42 try: 43 return float(x) 44 except ValueError: 45 return 0
46
47 -class FormWidget(object):
48 """ 49 helper for SQLFORM to generate form input fields (widget), 50 related to the fieldtype 51 """ 52 53 @staticmethod
54 - def _attributes(field, widget_attributes, **attributes):
55 """ 56 helper to build a common set of attributes 57 58 :param field: the field involved, some attributes are derived from this 59 :param widget_attributes: widget related attributes 60 :param attributes: any other supplied attributes 61 """ 62 attr = dict( 63 _id = '%s_%s' % (field._tablename, field.name), 64 _class = widget_class.match(str(field.type)).group(), 65 _name = field.name, 66 requires = field.requires, 67 ) 68 attr.update(widget_attributes) 69 attr.update(attributes) 70 return attr
71 72 @staticmethod
73 - def widget(field, value, **attributes):
74 """ 75 generates the widget for the field. 76 77 When serialized, will provide an INPUT tag: 78 79 - id = tablename_fieldname 80 - class = field.type 81 - name = fieldname 82 83 :param field: the field needing the widget 84 :param value: value 85 :param attributes: any other attributes to be applied 86 """ 87 88 raise NotImplementedError
89
90 -class StringWidget(FormWidget):
91 92 @staticmethod
93 - def widget(field, value, **attributes):
94 """ 95 generates an INPUT text tag. 96 97 see also: :meth:`FormWidget.widget` 98 """ 99 100 default = dict( 101 _type = 'text', 102 value = (value!=None and str(value)) or '', 103 ) 104 attr = StringWidget._attributes(field, default, **attributes) 105 106 return INPUT(**attr)
107 108
109 -class IntegerWidget(StringWidget):
110 111 pass
112 113
114 -class DoubleWidget(StringWidget):
115 116 pass
117 118
119 -class DecimalWidget(StringWidget):
120 121 pass
122 123
124 -class TimeWidget(StringWidget):
125 126 pass
127 128
129 -class DateWidget(StringWidget):
130 131 pass
132 133
134 -class DatetimeWidget(StringWidget):
135 136 pass
137 138
139 -class TextWidget(FormWidget):
140 141 @staticmethod
142 - def widget(field, value, **attributes):
143 """ 144 generates a TEXTAREA tag. 145 146 see also: :meth:`FormWidget.widget` 147 """ 148 149 default = dict( 150 value = value, 151 ) 152 attr = TextWidget._attributes(field, default, **attributes) 153 154 return TEXTAREA(**attr)
155 156
157 -class BooleanWidget(FormWidget):
158 159 @staticmethod
160 - def widget(field, value, **attributes):
161 """ 162 generates an INPUT checkbox tag. 163 164 see also: :meth:`FormWidget.widget` 165 """ 166 167 default=dict( 168 _type='checkbox', 169 value=value, 170 ) 171 attr = BooleanWidget._attributes(field, default, **attributes) 172 173 return INPUT(**attr)
174 175
176 -class OptionsWidget(FormWidget):
177 178 @staticmethod
179 - def has_options(field):
180 """ 181 checks if the field has selectable options 182 183 :param field: the field needing checking 184 :returns: True if the field has options 185 """ 186 187 return hasattr(field.requires, 'options')
188 189 @staticmethod
190 - def widget(field, value, **attributes):
191 """ 192 generates a SELECT tag, including OPTIONs (only 1 option allowed) 193 194 see also: :meth:`FormWidget.widget` 195 """ 196 default = dict( 197 value=value, 198 ) 199 attr = OptionsWidget._attributes(field, default, **attributes) 200 201 requires = field.requires 202 if not isinstance(requires, (list, tuple)): 203 requires = [requires] 204 if requires: 205 if hasattr(requires[0], 'options'): 206 options = requires[0].options() 207 else: 208 raise SyntaxError, 'widget cannot determine options of %s' \ 209 % field 210 opts = [OPTION(v, _value=k) for (k, v) in options] 211 212 return SELECT(*opts, **attr)
213
214 -class ListWidget(StringWidget):
215 @staticmethod
216 - def widget(field,value,**attributes):
217 _id = '%s_%s' % (field._tablename, field.name) 218 _name = field.name 219 if field.type=='list:integer': _class = 'integer' 220 else: _class = 'string' 221 items=[LI(INPUT(_id=_id,_class=_class,_name=_name,value=v,hideerror=True)) \ 222 for v in value or ['']] 223 script=SCRIPT(""" 224 // from http://refactormycode.com/codes/694-expanding-input-list-using-jquery 225 (function(){ 226 jQuery.fn.grow_input = function() { 227 return this.each(function() { 228 var ul = this; 229 jQuery(ul).find(":text").after('<a href="javascript:void(0)>+</a>').keypress(function (e) { return (e.which == 13) ? pe(ul) : true; }).next().click(function(){ pe(ul) }); 230 }); 231 }; 232 function pe(ul) { 233 var new_line = ml(ul); 234 rel(ul); 235 new_line.appendTo(ul); 236 new_line.find(":text").focus(); 237 return false; 238 } 239 function ml(ul) { 240 var line = jQuery(ul).find("li:first").clone(true); 241 line.find(':text').val(''); 242 return line; 243 } 244 function rel(ul) { 245 jQuery(ul).find("li").each(function() { 246 var trimmed = jQuery.trim(jQuery(this.firstChild).val()); 247 if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed); 248 }); 249 } 250 })(); 251 jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();}); 252 """ % _id) 253 attributes['_id']=_id+'_grow_input' 254 return TAG[''](UL(*items,**attributes),script)
255 256
257 -class MultipleOptionsWidget(OptionsWidget):
258 259 @staticmethod
260 - def widget(field, value, size=5, **attributes):
261 """ 262 generates a SELECT tag, including OPTIONs (multiple options allowed) 263 264 see also: :meth:`FormWidget.widget` 265 266 :param size: optional param (default=5) to indicate how many rows must 267 be shown 268 """ 269 270 attributes.update(dict(_size=size, _multiple=True)) 271 272 return OptionsWidget.widget(field, value, **attributes)
273 274
275 -class RadioWidget(OptionsWidget):
276 277 @staticmethod
278 - def widget(field, value, **attributes):
279 """ 280 generates a TABLE tag, including INPUT radios (only 1 option allowed) 281 282 see also: :meth:`FormWidget.widget` 283 """ 284 285 attr = OptionsWidget._attributes(field, {}, **attributes) 286 287 requires = field.requires 288 if not isinstance(requires, (list, tuple)): 289 requires = [requires] 290 if requires: 291 if hasattr(requires[0], 'options'): 292 options = requires[0].options() 293 else: 294 raise SyntaxError, 'widget cannot determine options of %s' \ 295 % field 296 297 options = [(k, v) for k, v in options if str(v)] 298 opts = [] 299 cols = attributes.get('cols',1) 300 totals = len(options) 301 mods = totals%cols 302 rows = totals/cols 303 if mods: 304 rows += 1 305 306 for r_index in range(rows): 307 tds = [] 308 for k, v in options[r_index*cols:(r_index+1)*cols]: 309 tds.append(TD(INPUT(_type='radio', _name=field.name, 310 requires=attr.get('requires',None), 311 hideerror=True, _value=k, 312 value=value), v)) 313 opts.append(TR(tds)) 314 315 if opts: 316 opts[-1][0][0]['hideerror'] = False 317 return TABLE(*opts, **attr)
318 319
320 -class CheckboxesWidget(OptionsWidget):
321 322 @staticmethod
323 - def widget(field, value, **attributes):
324 """ 325 generates a TABLE tag, including INPUT checkboxes (multiple allowed) 326 327 see also: :meth:`FormWidget.widget` 328 """ 329 330 # was values = re.compile('[\w\-:]+').findall(str(value)) 331 if isinstance(value, (list, tuple)): 332 values = [str(v) for v in value] 333 else: 334 values = [str(value)] 335 336 attr = OptionsWidget._attributes(field, {}, **attributes) 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 348 options = [(k, v) for k, v in options if k != ''] 349 opts = [] 350 cols = attributes.get('cols', 1) 351 totals = len(options) 352 mods = totals % cols 353 rows = totals / cols 354 if mods: 355 rows += 1 356 357 for r_index in range(rows): 358 tds = [] 359 for k, v in options[r_index*cols:(r_index+1)*cols]: 360 if k in values: 361 r_value = k 362 else: 363 r_value = [] 364 tds.append(TD(INPUT(_type='checkbox', _name=field.name, 365 requires=attr.get('requires', None), 366 hideerror=True, _value=k, 367 value=r_value), v)) 368 opts.append(TR(tds)) 369 370 if opts: 371 opts[-1][0][0]['hideerror'] = False 372 return TABLE(*opts, **attr)
373 374
375 -class PasswordWidget(FormWidget):
376 377 DEFAULT_PASSWORD_DISPLAY = 8*('*') 378 379 @staticmethod
380 - def widget(field, value, **attributes):
381 """ 382 generates a INPUT password tag. 383 If a value is present it will be shown as a number of '*', not related 384 to the length of the actual value. 385 386 see also: :meth:`FormWidget.widget` 387 """ 388 389 default=dict( 390 _type='password', 391 _value=(value and PasswordWidget.DEFAULT_PASSWORD_DISPLAY) or '', 392 ) 393 attr = PasswordWidget._attributes(field, default, **attributes) 394 395 return INPUT(**attr)
396 397
398 -class UploadWidget(FormWidget):
399 400 DEFAULT_WIDTH = '150px' 401 ID_DELETE_SUFFIX = '__delete' 402 GENERIC_DESCRIPTION = 'file' 403 DELETE_FILE = 'delete' 404 405 @staticmethod
406 - def widget(field, value, download_url=None, **attributes):
407 """ 408 generates a INPUT file tag. 409 410 Optionally provides an A link to the file, including a checkbox so 411 the file can be deleted. 412 All is wrapped in a DIV. 413 414 see also: :meth:`FormWidget.widget` 415 416 :param download_url: Optional URL to link to the file (default = None) 417 """ 418 419 default=dict( 420 _type='file', 421 ) 422 attr = UploadWidget._attributes(field, default, **attributes) 423 424 inp = INPUT(**attr) 425 426 if download_url and value: 427 url = download_url + '/' + value 428 (br, image) = ('', '') 429 if UploadWidget.is_image(value): 430 br = BR() 431 image = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH) 432 433 requires = attr["requires"] 434 if requires == [] or isinstance(requires, IS_EMPTY_OR): 435 inp = DIV(inp, '[', 436 A(UploadWidget.GENERIC_DESCRIPTION, _href = url), 437 '|', 438 INPUT(_type='checkbox', 439 _name=field.name + UploadWidget.ID_DELETE_SUFFIX), 440 UploadWidget.DELETE_FILE, 441 ']', br, image) 442 else: 443 inp = DIV(inp, '[', 444 A(UploadWidget.GENERIC_DESCRIPTION, _href = url), 445 ']', br, image) 446 return inp
447 448 @staticmethod
449 - def represent(field, value, download_url=None):
450 """ 451 how to represent the file: 452 453 - with download url and if it is an image: <A href=...><IMG ...></A> 454 - otherwise with download url: <A href=...>file</A> 455 - otherwise: file 456 457 :param field: the field 458 :param value: the field value 459 :param download_url: url for the file download (default = None) 460 """ 461 462 inp = UploadWidget.GENERIC_DESCRIPTION 463 464 if download_url and value: 465 url = download_url + '/' + value 466 if UploadWidget.is_image(value): 467 inp = IMG(_src = url, _width = UploadWidget.DEFAULT_WIDTH) 468 inp = A(inp, _href = url) 469 470 return inp
471 472 @staticmethod
473 - def is_image(value):
474 """ 475 Tries to check if the filename provided references to an image 476 477 Checking is based on filename extension. Currently recognized: 478 gif, png, jp(e)g, bmp 479 480 :param value: filename 481 """ 482 483 extension = value.split('.')[-1].lower() 484 if extension in ['gif', 'png', 'jpg', 'jpeg', 'bmp']: 485 return True 486 return False
487 488
489 -class AutocompleteWidget(object):
490
491 - def __init__(self, request, field, id_field=None, db=None, 492 orderby=None, limitby=(0,10), 493 keyword='_autocomplete_%(fieldname)s', 494 min_length=2):
495 self.request = request 496 self.keyword = keyword % dict(fieldname=field.name) 497 self.db = db or field._db 498 self.orderby = orderby 499 self.limitby = limitby 500 self.min_length = min_length 501 self.fields=[field] 502 if id_field: 503 self.is_reference = True 504 self.fields.append(id_field) 505 else: 506 self.is_reference = False 507 if hasattr(request,'application'): 508 self.url = Url(r=request, args=request.args) 509 self.callback() 510 else: 511 self.url = request
512 - def callback(self):
513 if self.keyword in self.request.vars: 514 field = self.fields[0] 515 rows = self.db(field.like(self.request.vars[self.keyword]+'%'))\ 516 .select(orderby=self.orderby,limitby=self.limitby,*self.fields) 517 if rows: 518 if self.is_reference: 519 id_field = self.fields[1] 520 raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', 521 _size=len(rows),_multiple=(len(rows)==1), 522 *[OPTION(s[field.name],_value=s[id_field.name], 523 _selected=(k==0)) \ 524 for k,s in enumerate(rows)]).xml()) 525 else: 526 raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', 527 _size=len(rows),_multiple=(len(rows)==1), 528 *[OPTION(s[field.name], 529 _selected=(k==0)) \ 530 for k,s in enumerate(rows)]).xml()) 531 else: 532 533 raise HTTP(200,'')
534 - def __call__(self,field,value,**attributes):
535 default = dict( 536 _type = 'text', 537 value = (value!=None and str(value)) or '', 538 ) 539 attr = StringWidget._attributes(field, default, **attributes) 540 div_id = self.keyword+'_div' 541 attr['_autocomplete']='off' 542 if self.is_reference: 543 key2 = self.keyword+'_aux' 544 key3 = self.keyword+'_auto' 545 attr['_class']='string' 546 name = attr['_name'] 547 if 'requires' in attr: del attr['requires'] 548 attr['_name'] = key2 549 value = attr['value'] 550 record = self.db(self.fields[1]==value).select(self.fields[0]).first() 551 attr['value'] = record and record[self.fields[0].name] 552 attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ 553 dict(div_id=div_id,u='F'+self.keyword) 554 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='+escape(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');" % \ 555 dict(url=self.url,min_length=self.min_length, 556 key=self.keyword,id=attr['_id'],key2=key2,key3=key3, 557 name=name,div_id=div_id,u='F'+self.keyword) 558 if self.min_length==0: 559 attr['_onfocus'] = attr['_onkeyup'] 560 return TAG[''](INPUT(**attr),INPUT(_type='hidden',_id=key3,_value=value, 561 _name=name,requires=field.requires), 562 DIV(_id=div_id,_style='position:absolute;')) 563 else: 564 attr['_name']=field.name 565 attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ 566 dict(div_id=div_id,u='F'+self.keyword) 567 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='+escape(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');" % \ 568 dict(url=self.url,min_length=self.min_length, 569 key=self.keyword,id=attr['_id'],div_id=div_id,u='F'+self.keyword) 570 if self.min_length==0: 571 attr['_onfocus'] = attr['_onkeyup'] 572 return TAG[''](INPUT(**attr),DIV(_id=div_id,_style='position:absolute;'))
573 574
575 -class SQLFORM(FORM):
576 577 """ 578 SQLFORM is used to map a table (and a current record) into an HTML form 579 580 given a SQLTable stored in db.table 581 582 generates an insert form:: 583 584 SQLFORM(db.table) 585 586 generates an update form:: 587 588 record=db.table[some_id] 589 SQLFORM(db.table, record) 590 591 generates an update with a delete button:: 592 593 SQLFORM(db.table, record, deletable=True) 594 595 if record is an int:: 596 597 record=db.table[record] 598 599 optional arguments: 600 601 :param fields: a list of fields that should be placed in the form, 602 default is all. 603 :param labels: a dictionary with labels for each field, keys are the field 604 names. 605 :param col3: a dictionary with content for an optional third column 606 (right of each field). keys are field names. 607 :param linkto: the URL of a controller/function to access referencedby 608 records 609 see controller appadmin.py for examples 610 :param upload: the URL of a controller/function to download an uploaded file 611 see controller appadmin.py for examples 612 613 any named optional attribute is passed to the <form> tag 614 for example _class, _id, _style, _action, _method, etc. 615 616 """ 617 618 # usability improvements proposal by fpp - 4 May 2008 : 619 # - correct labels (for points to field id, not field name) 620 # - add label for delete checkbox 621 # - add translatable label for record ID 622 # - add third column to right of fields, populated from the col3 dict 623 624 widgets = Storage(dict( 625 string = StringWidget, 626 text = TextWidget, 627 password = PasswordWidget, 628 integer = IntegerWidget, 629 double = DoubleWidget, 630 decimal = DecimalWidget, 631 time = TimeWidget, 632 date = DateWidget, 633 datetime = DatetimeWidget, 634 upload = UploadWidget, 635 boolean = BooleanWidget, 636 blob = None, 637 options = OptionsWidget, 638 multiple = MultipleOptionsWidget, 639 radio = RadioWidget, 640 checkboxes = CheckboxesWidget, 641 autocomplete = AutocompleteWidget, 642 list = ListWidget, 643 )) 644 645 FIELDNAME_REQUEST_DELETE = 'delete_this_record' 646 FIELDKEY_DELETE_RECORD = 'delete_record' 647 ID_LABEL_SUFFIX = '__label' 648 ID_ROW_SUFFIX = '__row' 649
650 - def __init__( 651 self, 652 table, 653 record = None, 654 deletable = False, 655 linkto = None, 656 upload = None, 657 fields = None, 658 labels = None, 659 col3 = {}, 660 submit_button = 'Submit', 661 delete_label = 'Check to delete:', 662 showid = True, 663 readonly = False, 664 comments = True, 665 keepopts = [], 666 ignore_rw = False, 667 record_id = None, 668 formstyle = 'table3cols', 669 buttons = ['submit'], 670 **attributes 671 ):
672 """ 673 SQLFORM(db.table, 674 record=None, 675 fields=['name'], 676 labels={'name': 'Your name'}, 677 linkto=URL(r=request, f='table/db/') 678 """ 679 680 self.ignore_rw = ignore_rw 681 self.formstyle = formstyle 682 nbsp = XML('&nbsp;') # Firefox2 does not display fields with blanks 683 FORM.__init__(self, *[], **attributes) 684 ofields = fields 685 keyed = hasattr(table,'_primarykey') 686 687 # if no fields are provided, build it from the provided table 688 # will only use writable or readable fields, unless forced to ignore 689 if fields == None: 690 fields = [f.name for f in table if (ignore_rw or f.writable or f.readable) and not f.compute] 691 self.fields = fields 692 693 # make sure we have an id 694 if self.fields[0] != table.fields[0] and \ 695 isinstance(table,Table) and not keyed: 696 self.fields.insert(0, table.fields[0]) 697 698 self.table = table 699 700 # try to retrieve the indicated record using its id 701 # otherwise ignore it 702 if record and isinstance(record, (int, long, str, unicode)): 703 if not str(record).isdigit(): 704 raise HTTP(404, "Object not found") 705 record = table._db(table._id == record).select().first() 706 if not record: 707 raise HTTP(404, "Object not found") 708 self.record = record 709 710 self.record_id = record_id 711 if keyed: 712 if record: 713 self.record_id = dict([(k,record[k]) for k in table._primarykey]) 714 else: 715 self.record_id = dict([(k,None) for k in table._primarykey]) 716 self.field_parent = {} 717 xfields = [] 718 self.fields = fields 719 self.custom = Storage() 720 self.custom.dspval = Storage() 721 self.custom.inpval = Storage() 722 self.custom.label = Storage() 723 self.custom.comment = Storage() 724 self.custom.widget = Storage() 725 self.custom.linkto = Storage() 726 727 for fieldname in self.fields: 728 if fieldname.find('.') >= 0: 729 continue 730 731 field = self.table[fieldname] 732 comment = None 733 734 if comments: 735 comment = col3.get(fieldname, field.comment) 736 if comment == None: 737 comment = '' 738 self.custom.comment[fieldname] = comment 739 740 if labels != None and fieldname in labels: 741 label = labels[fieldname] 742 colon = '' 743 else: 744 label = field.label 745 colon = ': ' 746 self.custom.label[fieldname] = label 747 748 field_id = '%s_%s' % (table._tablename, fieldname) 749 750 label = LABEL(label, colon, _for=field_id, 751 _id=field_id+SQLFORM.ID_LABEL_SUFFIX) 752 753 row_id = field_id+SQLFORM.ID_ROW_SUFFIX 754 if field.type == 'id': 755 self.custom.dspval.id = nbsp 756 self.custom.inpval.id = '' 757 widget = '' 758 if record: 759 if showid and 'id' in fields and field.readable: 760 v = record['id'] 761 widget = SPAN(v, _id=field_id) 762 self.custom.dspval.id = str(v) 763 xfields.append((row_id,label, widget,comment)) 764 self.record_id = str(record['id']) 765 self.custom.widget.id = widget 766 continue 767 768 if readonly and not ignore_rw and not field.readable: 769 continue 770 771 if record: 772 default = record[fieldname] 773 else: 774 default = field.default 775 if isinstance(default,CALLABLETYPES): 776 default=default() 777 778 cond = readonly or \ 779 (not ignore_rw and not field.writable and field.readable) 780 781 if default and not cond: 782 default = field.formatter(default) 783 dspval = default 784 inpval = default 785 786 if cond: 787 788 # ## if field.represent is available else 789 # ## ignore blob and preview uploaded images 790 # ## format everything else 791 792 if field.represent: 793 inp = field.represent(default) 794 elif field.type in ['blob']: 795 continue 796 elif field.type == 'upload': 797 inp = UploadWidget.represent(field, default, upload) 798 elif field.type == 'boolean': 799 inp = self.widgets.boolean.widget(field, default, _disabled=True) 800 else: 801 inp = field.formatter(default) 802 elif field.type == 'upload': 803 if hasattr(field, 'widget') and field.widget: 804 inp = field.widget(field, default, upload) 805 else: 806 inp = self.widgets.upload.widget(field, default, upload) 807 elif hasattr(field, 'widget') and field.widget: 808 inp = field.widget(field, default) 809 elif field.type == 'boolean': 810 inp = self.widgets.boolean.widget(field, default) 811 if default: 812 inpval = 'checked' 813 else: 814 inpval = '' 815 elif OptionsWidget.has_options(field): 816 if not field.requires.multiple: 817 inp = self.widgets.options.widget(field, default) 818 else: 819 inp = self.widgets.multiple.widget(field, default) 820 if fieldname in keepopts: 821 inpval = TAG[''](*inp.components) 822 elif field.type.startswith('list:'): 823 inp = self.widgets.list.widget(field,default) 824 elif field.type == 'text': 825 inp = self.widgets.text.widget(field, default) 826 elif field.type == 'password': 827 inp = self.widgets.password.widget(field, default) 828 if self.record: 829 dspval = PasswordWidget.DEFAULT_PASSWORD_DISPLAY 830 else: 831 dspval = '' 832 elif field.type == 'blob': 833 continue 834 else: 835 inp = self.widgets.string.widget(field, default) 836 837 xfields.append((row_id,label,inp,comment)) 838 self.custom.dspval[fieldname] = dspval or nbsp 839 self.custom.inpval[fieldname] = inpval or '' 840 self.custom.widget[fieldname] = inp 841 842 # if a record is provided and found, as is linkto 843 # build a link 844 if record and linkto: 845 db = linkto.split('/')[-1] 846 for (rtable, rfield) in table._referenced_by: 847 if keyed: 848 rfld = table._db[rtable][rfield] 849 query = urllib.quote('%s.%s==%s' % (db,rfld,record[rfld.type[10:].split('.')[1]])) 850 else: 851 # <block> 852 query = urllib.quote('%s.%s==%s' % (db,table._db[rtable][rfield],record.id)) 853 lname = olname = '%s.%s' % (rtable, rfield) 854 if ofields and not olname in ofields: 855 continue 856 if labels and lname in labels: 857 lname = labels[lname] 858 widget = A(lname, 859 _class='reference', 860 _href='%s/%s?query=%s' % (linkto, rtable, query)) 861 xfields.append((olname.replace('.', '__')+SQLFORM.ID_ROW_SUFFIX, 862 '',widget,col3.get(olname,''))) 863 self.custom.linkto[olname.replace('.', '__')] = widget 864 # </block> 865 866 # when deletable, add delete? checkbox 867 self.custom.deletable = '' 868 if record and deletable: 869 widget = INPUT(_type='checkbox', 870 _class='delete', 871 _id=self.FIELDKEY_DELETE_RECORD, 872 _name=self.FIELDNAME_REQUEST_DELETE, 873 ) 874 xfields.append((self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_ROW_SUFFIX, 875 LABEL( 876 delete_label, 877 _for=self.FIELDKEY_DELETE_RECORD, 878 _id=self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_LABEL_SUFFIX), 879 widget, 880 col3.get(self.FIELDKEY_DELETE_RECORD, ''))) 881 self.custom.deletable = widget 882 # when writable, add submit button 883 self.custom.submit = '' 884 if (not readonly) and ('submit' in buttons): 885 widget = INPUT(_type='submit', 886 _value=submit_button) 887 xfields.append(('submit_record'+SQLFORM.ID_ROW_SUFFIX, 888 '', widget,col3.get('submit_button', ''))) 889 self.custom.submit = widget 890 # if a record is provided and found 891 # make sure it's id is stored in the form 892 if record: 893 if not self['hidden']: 894 self['hidden'] = {} 895 if not keyed: 896 self['hidden']['id'] = record['id'] 897 898 (begin, end) = self._xml() 899 self.custom.begin = XML("<%s %s>" % (self.tag, begin)) 900 self.custom.end = XML("%s</%s>" % (end, self.tag)) 901 table = self.createform(xfields) 902 self.components = [table]
903
904 - def createform(self, xfields):
905 if self.formstyle == 'table3cols': 906 table = TABLE() 907 for id,a,b,c in xfields: 908 td_b = self.field_parent[id] = TD(b,_class='w2p_fw') 909 table.append(TR(TD(a,_class='w2p_fl'), 910 td_b, 911 TD(c,_class='w2p_fc'),_id=id)) 912 elif self.formstyle == 'table2cols': 913 table = TABLE() 914 for id,a,b,c in xfields: 915 td_b = self.field_parent[id] = TD(b,_class='w2p_fw',_colspan="2") 916 table.append(TR(TD(a,_class='w2p_fl'), 917 TD(c,_class='w2p_fc'),_id=id 918 +'1',_class='even')) 919 table.append(TR(td_b,_id=id+'2',_class='odd')) 920 elif self.formstyle == 'divs': 921 table = TAG['']() 922 for id,a,b,c in xfields: 923 div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') 924 table.append(DIV(DIV(a,_class='w2p_fl'), 925 div_b, 926 DIV(c,_class='w2p_fc'),_id=id)) 927 elif self.formstyle == 'ul': 928 table = UL() 929 for id,a,b,c in xfields: 930 div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') 931 table.append(LI(DIV(a,_class='w2p_fl'), 932 div_b, 933 DIV(c,_class='w2p_fc'),_id=id)) 934 elif type(self.formstyle) == type(lambda:None): 935 table = TABLE() 936 for id,a,b,c in xfields: 937 td_b = self.field_parent[id] = TD(b,_class='w2p_fw') 938 newrows = self.formstyle(id,a,td_b,c) 939 if type(newrows).__name__ != "tuple": 940 newrows = [newrows] 941 for newrow in newrows: 942 table.append(newrow) 943 else: 944 raise RuntimeError, 'formstyle not supported' 945 return table
946 947
948 - def accepts( 949 self, 950 request_vars, 951 session=None, 952 formname='%(tablename)s/%(record_id)s', 953 keepvalues=False, 954 onvalidation=None, 955 dbio=True, 956 hideerror=False, 957 detect_record_change=False, 958 ):
959 960 """ 961 similar FORM.accepts but also does insert, update or delete in DAL. 962 but if detect_record_change == True than: 963 form.record_changed = False (record is properly validated/submitted) 964 form.record_changed = True (record cannot be submitted because changed) 965 elseif detect_record_change == False than: 966 form.record_changed = None 967 """ 968 969 if request_vars.__class__.__name__ == 'Request': 970 request_vars = request_vars.post_vars 971 972 keyed = hasattr(self.table, '_primarykey') 973 974 # implement logic to detect whether record exist but has been modified 975 # server side 976 self.record_changed = None 977 if detect_record_change: 978 if self.record: 979 self.record_changed = False 980 serialized = '|'.join(str(self.record[k]) for k in self.table.fields()) 981 self.record_hash = md5_hash(serialized) 982 983 # logic to deal with record_id for keyed tables 984 if self.record: 985 if keyed: 986 formname_id = '.'.join(str(self.record[k]) 987 for k in self.table._primarykey 988 if hasattr(self.record,k)) 989 record_id = dict((k, request_vars[k]) for k in self.table._primarykey) 990 else: 991 (formname_id, record_id) = (self.record.id, 992 request_vars.get('id', None)) 993 keepvalues = True 994 else: 995 if keyed: 996 formname_id = 'create' 997 record_id = dict([(k, None) for k in self.table._primarykey]) 998 else: 999 (formname_id, record_id) = ('create', None) 1000 1001 if not keyed and isinstance(record_id, (list, tuple)): 1002 record_id = record_id[0] 1003 1004 if formname: 1005 formname = formname % dict(tablename = self.table._tablename, 1006 record_id = formname_id) 1007 1008 # ## THIS IS FOR UNIQUE RECORDS, read IS_NOT_IN_DB 1009 1010 for fieldname in self.fields: 1011 field = self.table[fieldname] 1012 requires = field.requires or [] 1013 if not isinstance(requires, (list, tuple)): 1014 requires = [requires] 1015 [item.set_self_id(self.record_id) for item in requires 1016 if hasattr(item, 'set_self_id') and self.record_id] 1017 1018 # ## END 1019 1020 fields = {} 1021 for key in self.vars: 1022 fields[key] = self.vars[key] 1023 1024 ret = FORM.accepts( 1025 self, 1026 request_vars, 1027 session, 1028 formname, 1029 keepvalues, 1030 onvalidation, 1031 hideerror=hideerror, 1032 ) 1033 1034 if not ret and self.record and self.errors: 1035 ### if there are errors in update mode 1036 # and some errors refers to an already uploaded file 1037 # delete error if 1038 # - user not trying to upload a new file 1039 # - there is existing file and user is not trying to delete it 1040 # this is because removing the file may not pass validation 1041 for key in self.errors.keys(): 1042 if key in self.table \ 1043 and self.table[key].type == 'upload' \ 1044 and request_vars.get(key, None) in (None, '') \ 1045 and self.record[key] \ 1046 and not key + UploadWidget.ID_DELETE_SUFFIX in request_vars: 1047 del self.errors[key] 1048 if not self.errors: 1049 ret = True 1050 1051 requested_delete = \ 1052 request_vars.get(self.FIELDNAME_REQUEST_DELETE, False) 1053 1054 self.custom.end = TAG[''](self.hidden_fields(), self.custom.end) 1055 1056 auch = record_id and self.errors and requested_delete 1057 1058 # auch is true when user tries to delete a record 1059 # that does not pass validation, yet it should be deleted 1060 1061 if not ret and not auch: 1062 for fieldname in self.fields: 1063 field = self.table[fieldname] 1064 ### this is a workaround! widgets should always have default not None! 1065 if not field.widget and field.type.startswith('list:') and \ 1066 not OptionsWidget.has_options(field): 1067 field.widget = self.widgets.list.widget 1068 if hasattr(field, 'widget') and field.widget and fieldname in request_vars: 1069 if fieldname in self.vars: 1070 value = self.vars[fieldname] 1071 elif self.record: 1072 value = self.record[fieldname] 1073 else: 1074 value = self.table[fieldname].default 1075 row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX) 1076 widget = field.widget(field, value) 1077 self.field_parent[row_id].components = [ widget ] 1078 if not field.type.startswith('list:'): 1079 self.field_parent[row_id]._traverse(False, hideerror) 1080 self.custom.widget[ fieldname ] = widget 1081 return ret 1082 1083 if record_id and str(record_id) != str(self.record_id): 1084 raise SyntaxError, 'user is tampering with form\'s record_id: ' \ 1085 '%s != %s' % (record_id, self.record_id) 1086 1087 if record_id and dbio: 1088 if keyed: 1089 self.vars.update(record_id) 1090 else: 1091 self.vars.id = self.record.id 1092 1093 if requested_delete and self.custom.deletable: 1094 if dbio: 1095 if keyed: 1096 qry = reduce(lambda x, y: x & y, 1097 [self.table[k] == record_id[k] for k in self.table._primarykey]) 1098 else: 1099 qry = self.table._id == self.record.id 1100 self.table._db(qry).delete() 1101 self.errors.clear() 1102 for component in self.elements('input, select, textarea'): 1103 component['_disabled'] = True 1104 return True 1105 1106 for fieldname in self.fields: 1107 if not fieldname in self.table.fields: 1108 continue 1109 1110 if not self.ignore_rw and not self.table[fieldname].writable: 1111 ### this happens because FORM has no knowledge of writable 1112 ### and thinks that a missing boolean field is a None 1113 if self.table[fieldname].type == 'boolean' and \ 1114 self.vars.get(fieldname, True) == None: 1115 del self.vars[fieldname] 1116 continue 1117 1118 field = self.table[fieldname] 1119 if field.type == 'id': 1120 continue 1121 if field.type == 'boolean': 1122 if self.vars.get(fieldname, False): 1123 self.vars[fieldname] = fields[fieldname] = True 1124 else: 1125 self.vars[fieldname] = fields[fieldname] = False 1126 elif field.type == 'password' and self.record\ 1127 and request_vars.get(fieldname, None) == \ 1128 PasswordWidget.DEFAULT_PASSWORD_DISPLAY: 1129 continue # do not update if password was not changed 1130 elif field.type == 'upload': 1131 f = self.vars[fieldname] 1132 fd = '%s__delete' % fieldname 1133 if f == '' or f == None: 1134 if self.vars.get(fd, False) or not self.record: 1135 fields[fieldname] = '' 1136 else: 1137 fields[fieldname] = self.record[fieldname] 1138 self.vars[fieldname] = fields[fieldname] 1139 continue 1140 elif hasattr(f, 'file'): 1141 (source_file, original_filename) = (f.file, f.filename) 1142 elif isinstance(f, (str, unicode)): 1143 ### do not know why this happens, it should not 1144 (source_file, original_filename) = \ 1145 (cStringIO.StringIO(f), 'file.txt') 1146 newfilename = field.store(source_file, original_filename) 1147 # this line is for backward compatibility only 1148 self.vars['%s_newfilename' % fieldname] = newfilename 1149 fields[fieldname] = newfilename 1150 if isinstance(field.uploadfield, str): 1151 fields[field.uploadfield] = source_file.read() 1152 # proposed by Hamdy (accept?) do we need fields at this point? 1153 self.vars[fieldname] = fields[fieldname] 1154 continue 1155 elif fieldname in self.vars: 1156 fields[fieldname] = self.vars[fieldname] 1157 elif field.default == None and field.type != 'blob': 1158 self.errors[fieldname] = 'no data' 1159 return False 1160 value = fields.get(fieldname,None) 1161 if field.type == 'list:string': 1162 if not isinstance(value, (tuple, list)): 1163 fields[fieldname] = value and [value] or [] 1164 elif isinstance(field.type,str) and field.type.startswith('list:'): 1165 if not isinstance(value, list): 1166 fields[fieldname] = [safe_int(x) for x in (value and [value] or [])] 1167 elif field.type == 'integer': 1168 if value != None: 1169 fields[fieldname] = safe_int(value) 1170 elif field.type.startswith('reference'): 1171 if value != None and isinstance(self.table, Table) and not keyed: 1172 fields[fieldname] = safe_int(value) 1173 elif field.type == 'double': 1174 if value != None: 1175 fields[fieldname] = safe_float(value) 1176 1177 for fieldname in self.vars: 1178 if fieldname != 'id' and fieldname in self.table.fields\ 1179 and not fieldname in fields and not fieldname\ 1180 in request_vars: 1181 fields[fieldname] = self.vars[fieldname] 1182 1183 if dbio: 1184 if 'delete_this_record' in fields: 1185 # this should never happen but seems to happen to some 1186 del fields['delete_this_record'] 1187 if keyed: 1188 if reduce(lambda x, y: x and y, record_id.values()): # if record_id 1189 if fields: 1190 qry = reduce(lambda x, y: x & y, 1191 [self.table[k] == self.record[k] for k in self.table._primarykey]) 1192 self.table._db(qry).update(**fields) 1193 else: 1194 pk = self.table.insert(**fields) 1195 if pk: 1196 self.vars.update(pk) 1197 else: 1198 ret = False 1199 else: 1200 if record_id: 1201 self.vars.id = self.record.id 1202 if fields: 1203 self.table._db(self.table._id == self.record.id).update(**fields) 1204 else: 1205 self.vars.id = self.table.insert(**fields) 1206 return ret
1207 1208 @staticmethod
1209 - def factory(*fields, **attributes):
1210 """ 1211 generates a SQLFORM for the given fields. 1212 1213 Internally will build a non-database based data model 1214 to hold the fields. 1215 """ 1216 # Define a table name, this way it can be logical to our CSS. 1217 # And if you switch from using SQLFORM to SQLFORM.factory 1218 # your same css definitions will still apply. 1219 1220 table_name = attributes.get('table_name', 'no_table') 1221 1222 # So it won't interfear with SQLDB.define_table 1223 if 'table_name' in attributes: 1224 del attributes['table_name'] 1225 1226 return SQLFORM(DAL(None).define_table(table_name, *fields), **attributes)
1227 1228
1229 -class SQLTABLE(TABLE):
1230 1231 """ 1232 given a Rows object, as returned by a db().select(), generates 1233 an html table with the rows. 1234 1235 optional arguments: 1236 1237 :param linkto: URL (or lambda to generate a URL) to edit individual records 1238 :param upload: URL to download uploaded files 1239 :param orderby: Add an orderby link to column headers. 1240 :param headers: dictionary of headers to headers redefinions 1241 headers can also be a string to gerenare the headers from data 1242 for now only headers="fieldname:capitalize", 1243 headers="labels" and headers=None are supported 1244 :param truncate: length at which to truncate text in table cells. 1245 Defaults to 16 characters. 1246 :param columns: a list or dict contaning the names of the columns to be shown 1247 Defaults to all 1248 1249 Optional names attributes for passed to the <table> tag 1250 1251 The keys of headers and columns must be of the form "tablename.fieldname" 1252 1253 Simple linkto example:: 1254 1255 rows = db.select(db.sometable.ALL) 1256 table = SQLTABLE(rows, linkto='someurl') 1257 1258 This will link rows[id] to .../sometable/value_of_id 1259 1260 1261 More advanced linkto example:: 1262 1263 def mylink(field, type, ref): 1264 return URL(r=request, args=[field]) 1265 1266 rows = db.select(db.sometable.ALL) 1267 table = SQLTABLE(rows, linkto=mylink) 1268 1269 This will link rows[id] to 1270 current_app/current_controlle/current_function/value_of_id 1271 1272 New Implements: 24 June 2011: 1273 ----------------------------- 1274 1275 :param selectid: The id you want to select 1276 :param renderstyle: Boolean render the style with the table 1277 1278 :param extracolums = [{'label':A('Extra',_href='#'), 1279 'class': '', #class name of the header 1280 'width':'', #width in pixels or % 1281 'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id), 1282 'selected': False #agregate class selected to this column 1283 }] 1284 1285 1286 :param headers = {'table.id':{'label':'Id', 1287 'class':'', #class name of the header 1288 'width':'', #width in pixels or % 1289 'truncate': 16, #truncate the content to... 1290 'selected': False #agregate class selected to this column 1291 }, 1292 'table.myfield':{'label':'My field', 1293 'class':'', #class name of the header 1294 'width':'', #width in pixels or % 1295 'truncate': 16, #truncate the content to... 1296 'selected': False #agregate class selected to this column 1297 }, 1298 } 1299 1300 table = SQLTABLE(rows, headers=headers, extracolums=extracolums) 1301 1302 1303 """ 1304
1305 - def __init__( 1306 self, 1307 sqlrows, 1308 linkto=None, 1309 upload=None, 1310 orderby=None, 1311 headers={}, 1312 truncate=16, 1313 columns=None, 1314 th_link='', 1315 extracolumns=None, 1316 selectid=None, 1317 renderstyle=False, 1318 **attributes 1319 ):
1320 1321 TABLE.__init__(self, **attributes) 1322 self.components = [] 1323 self.attributes = attributes 1324 self.sqlrows = sqlrows 1325 (components, row) = (self.components, []) 1326 if not sqlrows: 1327 return 1328 if not columns: 1329 columns = sqlrows.colnames 1330 if headers=='fieldname:capitalize': 1331 headers = {} 1332 for c in columns: 1333 headers[c] = ' '.join([w.capitalize() for w in c.split('.')[-1].split('_')]) 1334 elif headers=='labels': 1335 headers = {} 1336 for c in columns: 1337 (t,f) = c.split('.') 1338 field = sqlrows.db[t][f] 1339 headers[c] = field.label 1340 if headers!=None: 1341 for c in columns:#new implement dict 1342 if isinstance(headers.get(c, c), dict): 1343 coldict = headers.get(c, c) 1344 attrcol = dict() 1345 if coldict['width']!="": 1346 attrcol.update(_width=coldict['width']) 1347 if coldict['class']!="": 1348 attrcol.update(_class=coldict['class']) 1349 row.append(TH(coldict['label'],**attrcol)) 1350 elif orderby: 1351 row.append(TH(A(headers.get(c, c), 1352 _href=th_link+'?orderby=' + c))) 1353 else: 1354 row.append(TH(headers.get(c, c))) 1355 1356 if extracolumns:#new implement dict 1357 for c in extracolumns: 1358 attrcol = dict() 1359 if c['width']!="": 1360 attrcol.update(_width=c['width']) 1361 if c['class']!="": 1362 attrcol.update(_class=c['class']) 1363 row.append(TH(c['label'],**attrcol)) 1364 1365 components.append(THEAD(TR(*row))) 1366 1367 1368 tbody = [] 1369 for (rc, record) in enumerate(sqlrows): 1370 row = [] 1371 if rc % 2 == 0: 1372 _class = 'even' 1373 else: 1374 _class = 'odd' 1375 1376 if selectid!=None:#new implement 1377 if record.id==selectid: 1378 _class += ' rowselected' 1379 1380 for colname in columns: 1381 if not table_field.match(colname): 1382 if "_extra" in record and colname in record._extra: 1383 r = record._extra[colname] 1384 row.append(TD(r)) 1385 continue 1386 else: 1387 raise KeyError("Column %s not found (SQLTABLE)" % colname) 1388 (tablename, fieldname) = colname.split('.') 1389 try: 1390 field = sqlrows.db[tablename][fieldname] 1391 except KeyError: 1392 field = None 1393 if tablename in record \ 1394 and isinstance(record,Row) \ 1395 and isinstance(record[tablename],Row): 1396 r = record[tablename][fieldname] 1397 elif fieldname in record: 1398 r = record[fieldname] 1399 else: 1400 raise SyntaxError, 'something wrong in Rows object' 1401 r_old = r 1402 if not field: 1403 pass 1404 elif linkto and field.type == 'id': 1405 try: 1406 href = linkto(r, 'table', tablename) 1407 except TypeError: 1408 href = '%s/%s/%s' % (linkto, tablename, r_old) 1409 r = A(r, _href=href) 1410 elif field.type.startswith('reference'): 1411 if linkto: 1412 ref = field.type[10:] 1413 try: 1414 href = linkto(r, 'reference', ref) 1415 except TypeError: 1416 href = '%s/%s/%s' % (linkto, ref, r_old) 1417 if ref.find('.') >= 0: 1418 tref,fref = ref.split('.') 1419 if hasattr(sqlrows.db[tref],'_primarykey'): 1420 href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref:r})) 1421 if field.represent: 1422 r = A(field.represent(r), _href=str(href)) 1423 else: 1424 r = A(str(r), _href=str(href)) 1425 elif field.represent: 1426 r = field.represent(r) 1427 elif linkto and hasattr(field._table,'_primarykey') and fieldname in field._table._primarykey: 1428 # have to test this with multi-key tables 1429 key = urllib.urlencode(dict( [ \ 1430 ((tablename in record \ 1431 and isinstance(record, Row) \ 1432 and isinstance(record[tablename], Row)) and 1433 (k, record[tablename][k])) or (k, record[k]) \ 1434 for k in field._table._primarykey ] )) 1435 r = A(r, _href='%s/%s?%s' % (linkto, tablename, key)) 1436 elif field.type.startswith('list:'): 1437 r = field.represent(r or []) 1438 elif field.represent: 1439 r = field.represent(r) 1440 elif field.type == 'blob' and r: 1441 r = 'DATA' 1442 elif field.type == 'upload': 1443 if upload and r: 1444 r = A('file', _href='%s/%s' % (upload, r)) 1445 elif r: 1446 r = 'file' 1447 else: 1448 r = '' 1449 elif field.type in ['string','text']: 1450 r = str(field.formatter(r)) 1451 ur = unicode(r, 'utf8') 1452 if headers!={}: #new implement dict 1453 if isinstance(headers[colname],dict): 1454 if isinstance(headers[colname]['truncate'], int): 1455 r = ur[:headers[colname]['truncate'] - 3].encode('utf8') + '...' 1456 elif truncate!=None and len(ur) > truncate: 1457 r = ur[:truncate - 3].encode('utf8') + '...' 1458 1459 attrcol = dict()#new implement dict 1460 if headers!={}: 1461 if isinstance(headers[colname],dict): 1462 colclass=headers[colname]['class'] 1463 if headers[colname]['selected']: 1464 colclass= str(headers[colname]['class'] + " colselected").strip() 1465 if colclass!="": 1466 attrcol.update(_class=colclass) 1467 1468 row.append(TD(r,**attrcol)) 1469 1470 if extracolumns:#new implement dict 1471 for c in extracolumns: 1472 attrcol = dict() 1473 colclass=c['class'] 1474 if c['selected']: 1475 colclass= str(c['class'] + " colselected").strip() 1476 if colclass!="": 1477 attrcol.update(_class=colclass) 1478 contentfunc = c['content'] 1479 row.append(TD(contentfunc(record, rc),**attrcol)) 1480 1481 tbody.append(TR(_class=_class, *row)) 1482 1483 if renderstyle: 1484 components.append(STYLE(self.style())) 1485 1486 components.append(TBODY(*tbody))
1487 1488
1489 - def style(self):
1490 1491 css = ''' 1492 table tbody tr.odd { 1493 background-color: #DFD; 1494 } 1495 table tbody tr.even { 1496 background-color: #EFE; 1497 } 1498 table tbody tr.rowselected { 1499 background-color: #FDD; 1500 } 1501 table tbody tr td.colselected { 1502 background-color: #FDD; 1503 } 1504 table tbody tr:hover { 1505 background: #DDF; 1506 } 1507 ''' 1508 1509 return css
1510 1511 form_factory = SQLFORM.factory # for backward compatibility, deprecated 1512