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