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, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT 19 from html import FORM, INPUT, LABEL, OPTION, SELECT, MENU 20 from html import TABLE, THEAD, TBODY, TR, TD, TH 21 from html import URL 22 from dal import DAL, Table, Row, CALLABLETYPES, smart_query 23 from storage import Storage 24 from utils import md5_hash 25 from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF 26 27 import urllib 28 import re 29 import cStringIO 30 31 table_field = re.compile('[\w_]+\.[\w_]+') 32 widget_class = re.compile('^\w*') 33 3638 f = field.represent 39 if not callable(f): 40 return str(value) 41 n = f.func_code.co_argcount-len(f.func_defaults or []) 42 if n==1: 43 return f(value) 44 elif n==2: 45 return f(value,record) 46 else: 47 raise RuntimeError, "field representation must take 1 or 2 args"48 54 6062 """ 63 helper for SQLFORM to generate form input fields 64 (widget), related to the fieldtype 65 """ 66 67 _class = 'generic-widget' 68 69 @classmethod10872 """ 73 helper to build a common set of attributes 74 75 :param field: the field involved, 76 some attributes are derived from this 77 :param widget_attributes: widget related attributes 78 :param attributes: any other supplied attributes 79 """ 80 attr = dict( 81 _id = '%s_%s' % (field._tablename, field.name), 82 _class = cls._class or \ 83 widget_class.match(str(field.type)).group(), 84 _name = field.name, 85 requires = field.requires, 86 ) 87 attr.update(widget_attributes) 88 attr.update(attributes) 89 return attr90 91 @classmethod93 """ 94 generates the widget for the field. 95 96 When serialized, will provide an INPUT tag: 97 98 - id = tablename_fieldname 99 - class = field.type 100 - name = fieldname 101 102 :param field: the field needing the widget 103 :param value: value 104 :param attributes: any other attributes to be applied 105 """ 106 107 raise NotImplementedError110 _class = 'string' 111 112 @classmethod127 128114 """ 115 generates an INPUT text tag. 116 117 see also: :meth:`FormWidget.widget` 118 """ 119 120 default = dict( 121 _type = 'text', 122 value = (not value is None and str(value)) or '', 123 ) 124 attr = cls._attributes(field, default, **attributes) 125 126 return INPUT(**attr)130 _class = 'integer'131 132134 _class = 'double'135 136138 _class = 'double'139 140142 _class = 'time'143 144146 _class = 'date'147 148150 _class = 'datetime'151 152154 _class = 'text' 155 156 @classmethod168 169158 """ 159 generates a TEXTAREA tag. 160 161 see also: :meth:`FormWidget.widget` 162 """ 163 164 default = dict(value = value) 165 attr = cls._attributes(field, default, 166 **attributes) 167 return TEXTAREA(**attr)171 _class = 'boolean' 172 173 @classmethod185 186175 """ 176 generates an INPUT checkbox tag. 177 178 see also: :meth:`FormWidget.widget` 179 """ 180 181 default=dict(_type='checkbox', value=value) 182 attr = cls._attributes(field, default, 183 **attributes) 184 return INPUT(**attr)188 189 @staticmethod221191 """ 192 checks if the field has selectable options 193 194 :param field: the field needing checking 195 :returns: True if the field has options 196 """ 197 198 return hasattr(field.requires, 'options')199 200 @classmethod202 """ 203 generates a SELECT tag, including OPTIONs (only 1 option allowed) 204 205 see also: :meth:`FormWidget.widget` 206 """ 207 default = dict(value=value) 208 attr = cls._attributes(field, default, 209 **attributes) 210 requires = field.requires 211 if not isinstance(requires, (list, tuple)): 212 requires = [requires] 213 if requires: 214 if hasattr(requires[0], 'options'): 215 options = requires[0].options() 216 else: 217 raise SyntaxError, 'widget cannot determine options of %s' % field 218 opts = [OPTION(v, _value=k) for (k, v) in options] 219 220 return SELECT(*opts, **attr)223 224 @classmethod265 266226 _id = '%s_%s' % (field._tablename, field.name) 227 _name = field.name 228 if field.type=='list:integer': _class = 'integer' 229 else: _class = 'string' 230 requires = field.requires if isinstance(field.requires, (IS_NOT_EMPTY, IS_LIST_OF)) else None 231 items=[LI(INPUT(_id=_id, _class=_class, _name=_name, value=v, hideerror=True, requires=requires)) \ 232 for v in value or ['']] 233 script=SCRIPT(""" 234 // from http://refactormycode.com/codes/694-expanding-input-list-using-jquery 235 (function(){ 236 jQuery.fn.grow_input = function() { 237 return this.each(function() { 238 var ul = this; 239 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) }); 240 }); 241 }; 242 function pe(ul) { 243 var new_line = ml(ul); 244 rel(ul); 245 new_line.appendTo(ul); 246 new_line.find(":text").focus(); 247 return false; 248 } 249 function ml(ul) { 250 var line = jQuery(ul).find("li:first").clone(true); 251 line.find(':text').val(''); 252 return line; 253 } 254 function rel(ul) { 255 jQuery(ul).find("li").each(function() { 256 var trimmed = jQuery.trim(jQuery(this.firstChild).val()); 257 if (trimmed=='') jQuery(this).remove(); else jQuery(this.firstChild).val(trimmed); 258 }); 259 } 260 })(); 261 jQuery(document).ready(function(){jQuery('#%s_grow_input').grow_input();}); 262 """ % _id) 263 attributes['_id']=_id+'_grow_input' 264 return TAG[''](UL(*items,**attributes),script)268 269 @classmethod283 284271 """ 272 generates a SELECT tag, including OPTIONs (multiple options allowed) 273 274 see also: :meth:`FormWidget.widget` 275 276 :param size: optional param (default=5) to indicate how many rows must 277 be shown 278 """ 279 280 attributes.update(dict(_size=size, _multiple=True)) 281 282 return OptionsWidget.widget(field, value, **attributes)286 287 @classmethod341 342289 """ 290 generates a TABLE tag, including INPUT radios (only 1 option allowed) 291 292 see also: :meth:`FormWidget.widget` 293 """ 294 295 attr = cls._attributes(field, {}, **attributes) 296 attr['_class'] = attr.get('_class','web2py_radiowidget') 297 298 requires = field.requires 299 if not isinstance(requires, (list, tuple)): 300 requires = [requires] 301 if requires: 302 if hasattr(requires[0], 'options'): 303 options = requires[0].options() 304 else: 305 raise SyntaxError, 'widget cannot determine options of %s' \ 306 % field 307 options = [(k, v) for k, v in options if str(v)] 308 opts = [] 309 cols = attributes.get('cols',1) 310 totals = len(options) 311 mods = totals%cols 312 rows = totals/cols 313 if mods: 314 rows += 1 315 316 #widget style 317 wrappers = dict( 318 table=(TABLE,TR,TD), 319 ul=(DIV,UL,LI), 320 divs=(CAT,DIV,DIV) 321 ) 322 parent, child, inner = wrappers[attributes.get('style','table')] 323 324 for r_index in range(rows): 325 tds = [] 326 for k, v in options[r_index*cols:(r_index+1)*cols]: 327 checked={'_checked':'checked'} if k==value else {} 328 tds.append(inner(INPUT(_type='radio', 329 _id='%s%s' % (field.name,k), 330 _name=field.name, 331 requires=attr.get('requires',None), 332 hideerror=True, _value=k, 333 value=value, 334 **checked), 335 LABEL(v,_for='%s%s' % (field.name,k)))) 336 opts.append(child(tds)) 337 338 if opts: 339 opts[-1][0][0]['hideerror'] = False 340 return parent(*opts, **attr)344 345 @classmethod408 409347 """ 348 generates a TABLE tag, including INPUT checkboxes (multiple allowed) 349 350 see also: :meth:`FormWidget.widget` 351 """ 352 353 # was values = re.compile('[\w\-:]+').findall(str(value)) 354 if isinstance(value, (list, tuple)): 355 values = [str(v) for v in value] 356 else: 357 values = [str(value)] 358 359 attr = cls._attributes(field, {}, **attributes) 360 attr['_class'] = attr.get('_class','web2py_checkboxeswidget') 361 362 requires = field.requires 363 if not isinstance(requires, (list, tuple)): 364 requires = [requires] 365 if requires: 366 if hasattr(requires[0], 'options'): 367 options = requires[0].options() 368 else: 369 raise SyntaxError, 'widget cannot determine options of %s' \ 370 % field 371 372 options = [(k, v) for k, v in options if k != ''] 373 opts = [] 374 cols = attributes.get('cols', 1) 375 totals = len(options) 376 mods = totals % cols 377 rows = totals / cols 378 if mods: 379 rows += 1 380 381 #widget style 382 wrappers = dict( 383 table=(TABLE,TR,TD), 384 ul=(DIV,UL,LI), 385 divs=(CAT,DIV,DIV) 386 ) 387 parent, child, inner = wrappers[attributes.get('style','table')] 388 389 for r_index in range(rows): 390 tds = [] 391 for k, v in options[r_index*cols:(r_index+1)*cols]: 392 if k in values: 393 r_value = k 394 else: 395 r_value = [] 396 tds.append(inner(INPUT(_type='checkbox', 397 _id='%s%s' % (field.name,k), 398 _name=field.name, 399 requires=attr.get('requires', None), 400 hideerror=True, _value=k, 401 value=r_value), 402 LABEL(v,_for='%s%s' % (field.name,k)))) 403 opts.append(child(tds)) 404 405 if opts: 406 opts[-1][0][0]['hideerror'] = False 407 return parent(*opts, **attr)411 _class = 'password' 412 413 DEFAULT_PASSWORD_DISPLAY = 8*('*') 414 415 @classmethod432 433417 """ 418 generates a INPUT password tag. 419 If a value is present it will be shown as a number of '*', not related 420 to the length of the actual value. 421 422 see also: :meth:`FormWidget.widget` 423 """ 424 425 default=dict( 426 _type='password', 427 _value=(value and cls.DEFAULT_PASSWORD_DISPLAY) or '', 428 ) 429 attr = cls._attributes(field, default, **attributes) 430 431 return INPUT(**attr)435 _class = 'upload' 436 437 DEFAULT_WIDTH = '150px' 438 ID_DELETE_SUFFIX = '__delete' 439 GENERIC_DESCRIPTION = 'file' 440 DELETE_FILE = 'delete' 441 442 @classmethod530 531444 """ 445 generates a INPUT file tag. 446 447 Optionally provides an A link to the file, including a checkbox so 448 the file can be deleted. 449 All is wrapped in a DIV. 450 451 see also: :meth:`FormWidget.widget` 452 453 :param download_url: Optional URL to link to the file (default = None) 454 """ 455 456 default=dict(_type='file',) 457 attr = cls._attributes(field, default, **attributes) 458 459 inp = INPUT(**attr) 460 461 if download_url and value: 462 if callable(download_url): 463 url = download_url(value) 464 else: 465 url = download_url + '/' + value 466 (br, image) = ('', '') 467 if UploadWidget.is_image(value): 468 br = BR() 469 image = IMG(_src = url, _width = cls.DEFAULT_WIDTH) 470 471 requires = attr["requires"] 472 if requires == [] or isinstance(requires, IS_EMPTY_OR): 473 inp = DIV(inp, '[', 474 A(UploadWidget.GENERIC_DESCRIPTION, _href = url), 475 '|', 476 INPUT(_type='checkbox', 477 _name=field.name + cls.ID_DELETE_SUFFIX, 478 _id=field.name + cls.ID_DELETE_SUFFIX), 479 LABEL(cls.DELETE_FILE, 480 _for=field.name + cls.ID_DELETE_SUFFIX), 481 ']', br, image) 482 else: 483 inp = DIV(inp, '[', 484 A(cls.GENERIC_DESCRIPTION, _href = url), 485 ']', br, image) 486 return inp487 488 @classmethod490 """ 491 how to represent the file: 492 493 - with download url and if it is an image: <A href=...><IMG ...></A> 494 - otherwise with download url: <A href=...>file</A> 495 - otherwise: file 496 497 :param field: the field 498 :param value: the field value 499 :param download_url: url for the file download (default = None) 500 """ 501 502 inp = cls.GENERIC_DESCRIPTION 503 504 if download_url and value: 505 if callable(download_url): 506 url = download_url(value) 507 else: 508 url = download_url + '/' + value 509 if cls.is_image(value): 510 inp = IMG(_src = url, _width = cls.DEFAULT_WIDTH) 511 inp = A(inp, _href = url) 512 513 return inp514 515 @staticmethod517 """ 518 Tries to check if the filename provided references to an image 519 520 Checking is based on filename extension. Currently recognized: 521 gif, png, jp(e)g, bmp 522 523 :param value: filename 524 """ 525 526 extension = value.split('.')[-1].lower() 527 if extension in ['gif', 'png', 'jpg', 'jpeg', 'bmp']: 528 return True 529 return False533 _class = 'string' 534617 618535 - def __init__(self, request, field, id_field=None, db=None, 536 orderby=None, limitby=(0,10), 537 keyword='_autocomplete_%(fieldname)s', 538 min_length=2):539 self.request = request 540 self.keyword = keyword % dict(fieldname=field.name) 541 self.db = db or field._db 542 self.orderby = orderby 543 self.limitby = limitby 544 self.min_length = min_length 545 self.fields=[field] 546 if id_field: 547 self.is_reference = True 548 self.fields.append(id_field) 549 else: 550 self.is_reference = False 551 if hasattr(request,'application'): 552 self.url = URL(args=request.args) 553 self.callback() 554 else: 555 self.url = request557 if self.keyword in self.request.vars: 558 field = self.fields[0] 559 rows = self.db(field.like(self.request.vars[self.keyword]+'%'))\ 560 .select(orderby=self.orderby,limitby=self.limitby,*self.fields) 561 if rows: 562 if self.is_reference: 563 id_field = self.fields[1] 564 raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', 565 _size=len(rows),_multiple=(len(rows)==1), 566 *[OPTION(s[field.name],_value=s[id_field.name], 567 _selected=(k==0)) \ 568 for k,s in enumerate(rows)]).xml()) 569 else: 570 raise HTTP(200,SELECT(_id=self.keyword,_class='autocomplete', 571 _size=len(rows),_multiple=(len(rows)==1), 572 *[OPTION(s[field.name], 573 _selected=(k==0)) \ 574 for k,s in enumerate(rows)]).xml()) 575 else: 576 577 raise HTTP(200,'')579 default = dict( 580 _type = 'text', 581 value = (not value is None and str(value)) or '', 582 ) 583 attr = StringWidget._attributes(field, default, **attributes) 584 div_id = self.keyword+'_div' 585 attr['_autocomplete']='off' 586 if self.is_reference: 587 key2 = self.keyword+'_aux' 588 key3 = self.keyword+'_auto' 589 attr['_class']='string' 590 name = attr['_name'] 591 if 'requires' in attr: del attr['requires'] 592 attr['_name'] = key2 593 value = attr['value'] 594 record = self.db(self.fields[1]==value).select(self.fields[0]).first() 595 attr['value'] = record and record[self.fields[0].name] 596 attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ 597 dict(div_id=div_id,u='F'+self.keyword) 598 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');" % \ 599 dict(url=self.url,min_length=self.min_length, 600 key=self.keyword,id=attr['_id'],key2=key2,key3=key3, 601 name=name,div_id=div_id,u='F'+self.keyword) 602 if self.min_length==0: 603 attr['_onfocus'] = attr['_onkeyup'] 604 return TAG[''](INPUT(**attr),INPUT(_type='hidden',_id=key3,_value=value, 605 _name=name,requires=field.requires), 606 DIV(_id=div_id,_style='position:absolute;')) 607 else: 608 attr['_name']=field.name 609 attr['_onblur']="jQuery('#%(div_id)s').delay(3000).fadeOut('slow');" % \ 610 dict(div_id=div_id,u='F'+self.keyword) 611 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');" % \ 612 dict(url=self.url,min_length=self.min_length, 613 key=self.keyword,id=attr['_id'],div_id=div_id,u='F'+self.keyword) 614 if self.min_length==0: 615 attr['_onfocus'] = attr['_onkeyup'] 616 return TAG[''](INPUT(**attr),DIV(_id=div_id,_style='position:absolute;'))620 621 """ 622 SQLFORM is used to map a table (and a current record) into an HTML form 623 624 given a SQLTable stored in db.table 625 626 generates an insert form:: 627 628 SQLFORM(db.table) 629 630 generates an update form:: 631 632 record=db.table[some_id] 633 SQLFORM(db.table, record) 634 635 generates an update with a delete button:: 636 637 SQLFORM(db.table, record, deletable=True) 638 639 if record is an int:: 640 641 record=db.table[record] 642 643 optional arguments: 644 645 :param fields: a list of fields that should be placed in the form, 646 default is all. 647 :param labels: a dictionary with labels for each field, keys are the field 648 names. 649 :param col3: a dictionary with content for an optional third column 650 (right of each field). keys are field names. 651 :param linkto: the URL of a controller/function to access referencedby 652 records 653 see controller appadmin.py for examples 654 :param upload: the URL of a controller/function to download an uploaded file 655 see controller appadmin.py for examples 656 657 any named optional attribute is passed to the <form> tag 658 for example _class, _id, _style, _action, _method, etc. 659 660 """ 661 662 # usability improvements proposal by fpp - 4 May 2008 : 663 # - correct labels (for points to field id, not field name) 664 # - add label for delete checkbox 665 # - add translatable label for record ID 666 # - add third column to right of fields, populated from the col3 dict 667 668 widgets = Storage(dict( 669 string = StringWidget, 670 text = TextWidget, 671 password = PasswordWidget, 672 integer = IntegerWidget, 673 double = DoubleWidget, 674 decimal = DecimalWidget, 675 time = TimeWidget, 676 date = DateWidget, 677 datetime = DatetimeWidget, 678 upload = UploadWidget, 679 boolean = BooleanWidget, 680 blob = None, 681 options = OptionsWidget, 682 multiple = MultipleOptionsWidget, 683 radio = RadioWidget, 684 checkboxes = CheckboxesWidget, 685 autocomplete = AutocompleteWidget, 686 list = ListWidget, 687 )) 688 689 FIELDNAME_REQUEST_DELETE = 'delete_this_record' 690 FIELDKEY_DELETE_RECORD = 'delete_record' 691 ID_LABEL_SUFFIX = '__label' 692 ID_ROW_SUFFIX = '__row' 6931496 dbset = db(query) 1497 tablenames = db._adapter.tables(dbset.query) 1498 tables = [db[tablename] for tablename in tablenames] 1499 if not fields: 1500 fields = reduce(lambda a,b:a+b, 1501 [[field for field in table] for table in tables]) 1502 if not field_id: 1503 field_id = tables[0]._id 1504 columns = [str(field) for field in fields \ 1505 if field._tablename in tablenames] 1506 if not str(field_id) in [str(f) for f in fields]: 1507 fields.append(field_id) 1508 table = field_id.table 1509 tablename = table._tablename 1510 referrer = session.get('_web2py_grid_referrer_'+formname, url()) 1511 def check_authorization(): 1512 if user_signature: 1513 if not URL.verify(request,user_signature=user_signature): 1514 session.flash = T('not authorized') 1515 redirect(referrer) 1516 if upload=='<default>': 1517 upload = lambda filename: url(args=['download',filename]) 1518 if len(request.args)>1 and request.args[-2]=='download': 1519 check_authorization() 1520 stream = response.download(request,db) 1521 raise HTTP(200,stream,**response.headers) 1522 1523 def buttons(edit=False,view=False,record=None): 1524 buttons = DIV(gridbutton('buttonback', 'Back', referrer), 1525 _class='form_header row_buttons %(header)s %(cornertop)s' % ui) 1526 if edit: 1527 args = ['edit',table._tablename,request.args[-1]] 1528 buttons.append(gridbutton('buttonedit', 'Edit', 1529 url(args=args))) 1530 if view: 1531 args = ['view',table._tablename,request.args[-1]] 1532 buttons.append(gridbutton('buttonview', 'View', 1533 url(args=args))) 1534 if record and links: 1535 for link in links: 1536 if isinstance(link,dict): 1537 buttons.append(link['body'](record)) 1538 elif link(record): 1539 buttons.append(link(record)) 1540 return buttons 1541 1542 formfooter = DIV( 1543 _class='form_footer row_buttons %(header)s %(cornerbottom)s' % ui) 1544 1545 create_form = edit_form = None 1546 1547 if create and len(request.args)>1 and request.args[-2]=='new': 1548 check_authorization() 1549 table = db[request.args[-1]] 1550 create_form = SQLFORM( 1551 table, ignore_rw = ignore_rw, formstyle = formstyle, 1552 _class='web2py_form').process( 1553 next=referrer, 1554 onvalidation=onvalidation, 1555 onsuccess=oncreate, 1556 formname=formname) 1557 res = DIV(buttons(),create_form,formfooter,_class=_class) 1558 res.create_form = create_form 1559 res.edit_form = None 1560 res.update_form = None 1561 return res 1562 elif details and len(request.args)>2 and request.args[-3]=='view': 1563 check_authorization() 1564 table = db[request.args[-2]] 1565 record = table(request.args[-1]) or redirect(URL('error')) 1566 form = SQLFORM(table,record,upload=upload, 1567 readonly=True,_class='web2py_form') 1568 res = DIV(buttons(edit=editable,record=record),form, 1569 formfooter,_class=_class) 1570 res.create_form = None 1571 res.edit_form = None 1572 res.update_form = None 1573 return res 1574 elif editable and len(request.args)>2 and request.args[-3]=='edit': 1575 check_authorization() 1576 table = db[request.args[-2]] 1577 record = table(request.args[-1]) or redirect(URL('error')) 1578 edit_form = SQLFORM(table,record,upload=upload, 1579 deletable=deletable, 1580 _class='web2py_form') 1581 edit_form.process(formname=formname, 1582 onvalidation=onvalidation, 1583 onsuccess=onupdate, 1584 next=referrer) 1585 res = DIV(buttons(view=details,record=record), 1586 edit_form,formfooter,_class=_class) 1587 res.create_form = None 1588 res.edit_form = edit_form 1589 res.update_form = None 1590 return res 1591 elif deletable and len(request.args)>2 and request.args[-3]=='delete': 1592 check_authorization() 1593 table = db[request.args[-2]] 1594 if ondelete: 1595 ondelete(table,request.args[-1],ret) 1596 ret = db(table.id==request.args[-1]).delete() 1597 return ret 1598 elif csv and len(request.args)>0 and request.args[-1]=='csv': 1599 if request.vars.keywords: 1600 try: 1601 dbset=dbset(SQLFORM.build_query( 1602 fields, 1603 request.vars.get('keywords',''))) 1604 except: 1605 raise HTTP(400) 1606 check_authorization() 1607 response.headers['Content-Type'] = 'text/csv' 1608 response.headers['Content-Disposition'] = \ 1609 'attachment;filename=rows.csv;' 1610 raise HTTP(200,str(dbset.select()), 1611 **{'Content-Type':'text/csv', 1612 'Content-Disposition':'attachment;filename=rows.csv;'}) 1613 elif request.vars.records and not isinstance( 1614 request.vars.records,list): 1615 request.vars.records=[request.vars.records] 1616 elif not request.vars.records: 1617 request.vars.records=[] 1618 1619 session['_web2py_grid_referrer_'+formname] = \ 1620 URL(args=request.args,vars=request.vars, 1621 user_signature=user_signature) 1622 console = DIV(_class='web2py_console %(header)s %(cornertop)s' % ui) 1623 error = None 1624 search_form = None 1625 if searchable: 1626 sfields = reduce(lambda a,b:a+b, 1627 [[f for f in t if f.readable] for t in tables]) 1628 form = FORM( 1629 search_widget and search_widget(sfields) or '', 1630 INPUT(_name='keywords',_value=request.vars.keywords, 1631 _id='web2py_keywords'), 1632 INPUT(_type='submit',_value=T('Search')), 1633 INPUT(_type='submit',_value=T('Clear'), 1634 _onclick="jQuery('#web2py_keywords').val('');"), 1635 _method="GET",_action=url()) 1636 search_form = form 1637 console.append(form) 1638 keywords = request.vars.get('keywords','') 1639 try: 1640 subquery = SQLFORM.build_query(sfields, keywords) 1641 except RuntimeError: 1642 subquery = None 1643 error = T('Invalid query') 1644 elif callable(searchable): 1645 subquery = searchable(keywords,fields) 1646 else: 1647 subquery = None 1648 if subquery: 1649 dbset = dbset(subquery) 1650 try: 1651 if left: 1652 nrows = dbset.select('count(*)',left=left).first()['count(*)'] 1653 else: 1654 nrows = dbset.count() 1655 except: 1656 nrows = 0 1657 error = T('Unsupported query') 1658 1659 search_actions = DIV(_class='web2py_search_actions') 1660 if create: 1661 search_actions.append(gridbutton( 1662 buttonclass='buttonadd', 1663 buttontext=T('Add'), 1664 buttonurl=url(args=['new',tablename]))) 1665 if csv: 1666 search_actions.append(gridbutton( 1667 buttonclass='buttonexport', 1668 buttontext=T('Export'), 1669 trap = False, 1670 buttonurl=url(args=['csv'], 1671 vars=dict(keywords=request.vars.keywords or '')))) 1672 1673 console.append(search_actions) 1674 1675 order = request.vars.order or '' 1676 if sortable: 1677 if order and not order=='None': 1678 tablename,fieldname = order.split('~')[-1].split('.',1) 1679 sort_field = db[tablename][fieldname] 1680 exception = sort_field.type in ('date','datetime','time') 1681 if exception: 1682 orderby = (order[:1]=='~' and sort_field) or ~sort_field 1683 else: 1684 orderby = (order[:1]=='~' and ~sort_field) or sort_field 1685 1686 head = TR(_class=ui.get('header','')) 1687 if selectable: 1688 head.append(TH(_class=ui.get('default',''))) 1689 for field in fields: 1690 if columns and not str(field) in columns: continue 1691 if not field.readable: continue 1692 key = str(field) 1693 header = headers.get(str(field), 1694 hasattr(field,'label') and field.label or key) 1695 if sortable: 1696 if key == order: 1697 key, marker = '~'+order, sorter_icons[0] 1698 elif key == order[1:]: 1699 marker = sorter_icons[1] 1700 else: 1701 marker = '' 1702 header = A(header,marker,_href=url(vars=dict( 1703 keywords=request.vars.keywords or '', 1704 order=key)),_class=trap_class()) 1705 head.append(TH(header, _class=ui.get('default',''))) 1706 1707 if links and links_in_grid: 1708 for link in links: 1709 if isinstance(link,dict): 1710 head.append(TH(link['header'], _class=ui.get('default',''))) 1711 1712 # Include extra column for buttons if needed. 1713 include_buttons_column = (details or editable or deletable or 1714 (links and links_in_grid and 1715 not all([isinstance(link, dict) for link in links]))) 1716 if include_buttons_column: 1717 head.append(TH(_class=ui.get('default',''))) 1718 1719 paginator = UL() 1720 if paginate and paginate<nrows: 1721 npages,reminder = divmod(nrows,paginate) 1722 if reminder: npages+=1 1723 try: page = int(request.vars.page or 1)-1 1724 except ValueError: page = 0 1725 limitby = (paginate*page,paginate*(page+1)) 1726 def self_link(name,p): 1727 d = dict(page=p+1) 1728 if order: d['order']=order 1729 if request.vars.keywords: d['keywords']=request.vars.keywords 1730 return A(name,_href=url(vars=d),_class=trap_class()) 1731 NPAGES = 5 # window is 2*NPAGES 1732 if page>NPAGES+1: 1733 paginator.append(LI(self_link('<<',0))) 1734 if page>NPAGES: 1735 paginator.append(LI(self_link('<',page-1))) 1736 pages = range(max(0,page-NPAGES),min(page+NPAGES,npages)) 1737 for p in pages: 1738 if p == page: 1739 paginator.append(LI(A(p+1,_onclick='return false'), 1740 _class=trap_class('current'))) 1741 else: 1742 paginator.append(LI(self_link(p+1,p))) 1743 if page<npages-NPAGES: 1744 paginator.append(LI(self_link('>',page+1))) 1745 if page<npages-NPAGES-1: 1746 paginator.append(LI(self_link('>>',npages-1))) 1747 else: 1748 limitby = None 1749 1750 try: 1751 table_fields = [f for f in fields if f._tablename in tablenames] 1752 rows = dbset.select(left=left,orderby=orderby,limitby=limitby,*table_fields) 1753 except SyntaxError: 1754 rows = None 1755 error = T("Query Not Supported") 1756 message = error or T('%(nrows)s records found') % dict(nrows=nrows) 1757 console.append(DIV(message,_class='web2py_counter')) 1758 1759 if rows: 1760 htmltable = TABLE(THEAD(head)) 1761 tbody = TBODY() 1762 numrec=0 1763 for row in rows: 1764 if numrec % 2 == 0: 1765 classtr = 'even' 1766 else: 1767 classtr = 'odd' 1768 numrec+=1 1769 id = row[field_id] 1770 tr = TR(_class=classtr) 1771 if selectable: 1772 tr.append(INPUT(_type="checkbox",_name="records",_value=id, 1773 value=request.vars.records)) 1774 for field in fields: 1775 if not str(field) in columns: continue 1776 if not field.readable: continue 1777 if field.type=='blob': continue 1778 value = row[field] 1779 if field.represent: 1780 try: 1781 value=field.represent(value,row) 1782 except KeyError: 1783 try: 1784 value=field.represent(value,row[field._tablename]) 1785 except KeyError: 1786 pass 1787 elif field.type=='boolean': 1788 value = INPUT(_type="checkbox",_checked = value, 1789 _disabled=True) 1790 elif field.type=='upload': 1791 if value: 1792 if callable(upload): 1793 value = A('File', _href=upload(value)) 1794 elif upload: 1795 value = A('File', 1796 _href='%s/%s' % (upload, value)) 1797 else: 1798 value = '' 1799 elif isinstance(value,str) and len(value)>maxtextlength: 1800 value=value[:maxtextlengths.get(str(field),maxtextlength)]+'...' 1801 else: 1802 value=field.formatter(value) 1803 tr.append(TD(value)) 1804 row_buttons = TD(_class='row_buttons') 1805 if links and links_in_grid: 1806 for link in links: 1807 if isinstance(link, dict): 1808 tr.append(TD(link['body'](row))) 1809 else: 1810 if link(row): 1811 row_buttons.append(link(row)) 1812 if include_buttons_column: 1813 if details and (not callable(details) or details(row)): 1814 row_buttons.append(gridbutton( 1815 'buttonview', 'View', 1816 url(args=['view',tablename,id]))) 1817 if editable and (not callable(editable) or editable(row)): 1818 row_buttons.append(gridbutton( 1819 'buttonedit', 'Edit', 1820 url(args=['edit',tablename,id]))) 1821 if deletable and (not callable(deletable) or deletable(row)): 1822 row_buttons.append(gridbutton( 1823 'buttondelete', 'Delete', 1824 callback=url(args=['delete',tablename,id]), 1825 delete='tr')) 1826 tr.append(row_buttons) 1827 tbody.append(tr) 1828 htmltable.append(tbody) 1829 htmltable = DIV(htmltable,_style='width:100%;overflow-x:auto') 1830 if selectable: 1831 htmltable = FORM(htmltable,INPUT(_type="submit")) 1832 if htmltable.process(formname=formname).accepted:# 1833 htmltable.vars.records = htmltable.vars.records or [] 1834 htmltable.vars.records = htmltable.vars.records if type(htmltable.vars.records) == list else [htmltable.vars.records] 1835 records = [int(r) for r in htmltable.vars.records] 1836 selectable(records) 1837 redirect(referrer) 1838 else: 1839 htmltable = DIV(T('No records found')) 1840 res = DIV(console, 1841 DIV(htmltable,_class="web2py_table"), 1842 DIV(paginator,_class=\ 1843 "web2py_paginator %(header)s %(cornerbottom)s" % ui), 1844 _class='%s %s' % (_class, ui.get('widget',''))) 1845 res.create_form = create_form 1846 res.edit_form = edit_form 1847 res.search_form = search_form 1848 return res 1849 1850 @staticmethod694 - def __init__( 695 self, 696 table, 697 record = None, 698 deletable = False, 699 linkto = None, 700 upload = None, 701 fields = None, 702 labels = None, 703 col3 = {}, 704 submit_button = 'Submit', 705 delete_label = 'Check to delete:', 706 showid = True, 707 readonly = False, 708 comments = True, 709 keepopts = [], 710 ignore_rw = False, 711 record_id = None, 712 formstyle = 'table3cols', 713 buttons = ['submit'], 714 separator = ': ', 715 **attributes 716 ):717 """ 718 SQLFORM(db.table, 719 record=None, 720 fields=['name'], 721 labels={'name': 'Your name'}, 722 linkto=URL(f='table/db/') 723 """ 724 725 self.ignore_rw = ignore_rw 726 self.formstyle = formstyle 727 nbsp = XML(' ') # Firefox2 does not display fields with blanks 728 FORM.__init__(self, *[], **attributes) 729 ofields = fields 730 keyed = hasattr(table,'_primarykey') 731 732 # if no fields are provided, build it from the provided table 733 # will only use writable or readable fields, unless forced to ignore 734 if fields is None: 735 fields = [f.name for f in table if (ignore_rw or f.writable or f.readable) and not f.compute] 736 self.fields = fields 737 738 # make sure we have an id 739 if self.fields[0] != table.fields[0] and \ 740 isinstance(table,Table) and not keyed: 741 self.fields.insert(0, table.fields[0]) 742 743 self.table = table 744 745 # try to retrieve the indicated record using its id 746 # otherwise ignore it 747 if record and isinstance(record, (int, long, str, unicode)): 748 if not str(record).isdigit(): 749 raise HTTP(404, "Object not found") 750 record = table._db(table._id == record).select().first() 751 if not record: 752 raise HTTP(404, "Object not found") 753 self.record = record 754 755 self.record_id = record_id 756 if keyed: 757 if record: 758 self.record_id = dict([(k,record[k]) for k in table._primarykey]) 759 else: 760 self.record_id = dict([(k,None) for k in table._primarykey]) 761 self.field_parent = {} 762 xfields = [] 763 self.fields = fields 764 self.custom = Storage() 765 self.custom.dspval = Storage() 766 self.custom.inpval = Storage() 767 self.custom.label = Storage() 768 self.custom.comment = Storage() 769 self.custom.widget = Storage() 770 self.custom.linkto = Storage() 771 772 sep = separator or '' 773 774 for fieldname in self.fields: 775 if fieldname.find('.') >= 0: 776 continue 777 778 field = self.table[fieldname] 779 comment = None 780 781 if comments: 782 comment = col3.get(fieldname, field.comment) 783 if comment is None: 784 comment = '' 785 self.custom.comment[fieldname] = comment 786 787 if not labels is None and fieldname in labels: 788 label = labels[fieldname] 789 else: 790 label = field.label 791 self.custom.label[fieldname] = label 792 793 field_id = '%s_%s' % (table._tablename, fieldname) 794 795 label = LABEL(label, label and sep, _for=field_id, 796 _id=field_id+SQLFORM.ID_LABEL_SUFFIX) 797 798 row_id = field_id+SQLFORM.ID_ROW_SUFFIX 799 if field.type == 'id': 800 self.custom.dspval.id = nbsp 801 self.custom.inpval.id = '' 802 widget = '' 803 if record: 804 if showid and 'id' in fields and field.readable: 805 v = record['id'] 806 widget = SPAN(v, _id=field_id) 807 self.custom.dspval.id = str(v) 808 xfields.append((row_id,label, widget,comment)) 809 self.record_id = str(record['id']) 810 self.custom.widget.id = widget 811 continue 812 813 if readonly and not ignore_rw and not field.readable: 814 continue 815 816 if record: 817 default = record[fieldname] 818 else: 819 default = field.default 820 if isinstance(default,CALLABLETYPES): 821 default=default() 822 823 cond = readonly or \ 824 (not ignore_rw and not field.writable and field.readable) 825 826 if default and not cond: 827 default = field.formatter(default) 828 dspval = default 829 inpval = default 830 831 if cond: 832 833 # ## if field.represent is available else 834 # ## ignore blob and preview uploaded images 835 # ## format everything else 836 837 if field.represent: 838 inp = represent(field,default,record) 839 elif field.type in ['blob']: 840 continue 841 elif field.type == 'upload': 842 inp = UploadWidget.represent(field, default, upload) 843 elif field.type == 'boolean': 844 inp = self.widgets.boolean.widget(field, default, _disabled=True) 845 else: 846 inp = field.formatter(default) 847 elif field.type == 'upload': 848 if hasattr(field, 'widget') and field.widget: 849 inp = field.widget(field, default, upload) 850 else: 851 inp = self.widgets.upload.widget(field, default, upload) 852 elif hasattr(field, 'widget') and field.widget: 853 inp = field.widget(field, default) 854 elif field.type == 'boolean': 855 inp = self.widgets.boolean.widget(field, default) 856 if default: 857 inpval = 'checked' 858 else: 859 inpval = '' 860 elif OptionsWidget.has_options(field): 861 if not field.requires.multiple: 862 inp = self.widgets.options.widget(field, default) 863 else: 864 inp = self.widgets.multiple.widget(field, default) 865 if fieldname in keepopts: 866 inpval = TAG[''](*inp.components) 867 elif field.type.startswith('list:'): 868 inp = self.widgets.list.widget(field,default) 869 elif field.type == 'text': 870 inp = self.widgets.text.widget(field, default) 871 elif field.type == 'password': 872 inp = self.widgets.password.widget(field, default) 873 if self.record: 874 dspval = PasswordWidget.DEFAULT_PASSWORD_DISPLAY 875 else: 876 dspval = '' 877 elif field.type == 'blob': 878 continue 879 elif field.type in self.widgets: 880 inp = self.widgets[field.type].widget(field, default) 881 else: 882 inp = self.widgets.string.widget(field, default) 883 884 xfields.append((row_id,label,inp,comment)) 885 self.custom.dspval[fieldname] = dspval or nbsp 886 self.custom.inpval[fieldname] = inpval or '' 887 self.custom.widget[fieldname] = inp 888 889 # if a record is provided and found, as is linkto 890 # build a link 891 if record and linkto: 892 db = linkto.split('/')[-1] 893 for (rtable, rfield) in table._referenced_by: 894 if keyed: 895 rfld = table._db[rtable][rfield] 896 query = urllib.quote('%s.%s==%s' % (db,rfld,record[rfld.type[10:].split('.')[1]])) 897 else: 898 query = urllib.quote('%s.%s==%s' % (db,table._db[rtable][rfield],record.id)) 899 lname = olname = '%s.%s' % (rtable, rfield) 900 if ofields and not olname in ofields: 901 continue 902 if labels and lname in labels: 903 lname = labels[lname] 904 widget = A(lname, 905 _class='reference', 906 _href='%s/%s?query=%s' % (linkto, rtable, query)) 907 xfields.append((olname.replace('.', '__')+SQLFORM.ID_ROW_SUFFIX, 908 '',widget,col3.get(olname,''))) 909 self.custom.linkto[olname.replace('.', '__')] = widget 910 # </block> 911 912 # when deletable, add delete? checkbox 913 self.custom.deletable = '' 914 if record and deletable: 915 widget = INPUT(_type='checkbox', 916 _class='delete', 917 _id=self.FIELDKEY_DELETE_RECORD, 918 _name=self.FIELDNAME_REQUEST_DELETE, 919 ) 920 xfields.append((self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_ROW_SUFFIX, 921 LABEL( 922 delete_label, 923 _for=self.FIELDKEY_DELETE_RECORD, 924 _id=self.FIELDKEY_DELETE_RECORD+SQLFORM.ID_LABEL_SUFFIX), 925 widget, 926 col3.get(self.FIELDKEY_DELETE_RECORD, ''))) 927 self.custom.deletable = widget 928 929 # when writable, add submit button 930 self.custom.submit = '' 931 if not readonly: 932 if 'submit' in buttons: 933 widget = self.custom.submit = INPUT(_type='submit', 934 _value=submit_button) 935 elif buttons: 936 widget = self.custom.submit = DIV(*buttons) 937 if self.custom.submit: 938 xfields.append(('submit_record' + SQLFORM.ID_ROW_SUFFIX, 939 '', widget, col3.get('submit_button', ''))) 940 941 # if a record is provided and found 942 # make sure it's id is stored in the form 943 if record: 944 if not self['hidden']: 945 self['hidden'] = {} 946 if not keyed: 947 self['hidden']['id'] = record['id'] 948 949 (begin, end) = self._xml() 950 self.custom.begin = XML("<%s %s>" % (self.tag, begin)) 951 self.custom.end = XML("%s</%s>" % (end, self.tag)) 952 table = self.createform(xfields) 953 self.components = [table]954956 if self.formstyle == 'table3cols': 957 table = TABLE() 958 for id,a,b,c in xfields: 959 td_b = self.field_parent[id] = TD(b,_class='w2p_fw') 960 table.append(TR(TD(a,_class='w2p_fl'), 961 td_b, 962 TD(c,_class='w2p_fc'),_id=id)) 963 elif self.formstyle == 'table2cols': 964 table = TABLE() 965 for id,a,b,c in xfields: 966 td_b = self.field_parent[id] = TD(b,_class='w2p_fw',_colspan="2") 967 table.append(TR(TD(a,_class='w2p_fl'), 968 TD(c,_class='w2p_fc'),_id=id 969 +'1',_class='even')) 970 table.append(TR(td_b,_id=id+'2',_class='odd')) 971 elif self.formstyle == 'divs': 972 table = TAG['']() 973 for id,a,b,c in xfields: 974 div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') 975 table.append(DIV(DIV(a,_class='w2p_fl'), 976 div_b, 977 DIV(c,_class='w2p_fc'),_id=id)) 978 elif self.formstyle == 'ul': 979 table = UL() 980 for id,a,b,c in xfields: 981 div_b = self.field_parent[id] = DIV(b,_class='w2p_fw') 982 table.append(LI(DIV(a,_class='w2p_fl'), 983 div_b, 984 DIV(c,_class='w2p_fc'),_id=id)) 985 elif type(self.formstyle) == type(lambda:None): 986 table = TABLE() 987 for id,a,b,c in xfields: 988 td_b = self.field_parent[id] = TD(b,_class='w2p_fw') 989 newrows = self.formstyle(id,a,td_b,c) 990 if type(newrows).__name__ != "tuple": 991 newrows = [newrows] 992 for newrow in newrows: 993 table.append(newrow) 994 else: 995 raise RuntimeError, 'formstyle not supported' 996 return table997 998999 - def accepts( 1000 self, 1001 request_vars, 1002 session=None, 1003 formname='%(tablename)s/%(record_id)s', 1004 keepvalues=False, 1005 onvalidation=None, 1006 dbio=True, 1007 hideerror=False, 1008 detect_record_change=False, 1009 ):1010 1011 """ 1012 similar FORM.accepts but also does insert, update or delete in DAL. 1013 but if detect_record_change == True than: 1014 form.record_changed = False (record is properly validated/submitted) 1015 form.record_changed = True (record cannot be submitted because changed) 1016 elseif detect_record_change == False than: 1017 form.record_changed = None 1018 """ 1019 1020 if request_vars.__class__.__name__ == 'Request': 1021 request_vars = request_vars.post_vars 1022 1023 keyed = hasattr(self.table, '_primarykey') 1024 1025 # implement logic to detect whether record exist but has been modified 1026 # server side 1027 self.record_changed = None 1028 if detect_record_change: 1029 if self.record: 1030 self.record_changed = False 1031 serialized = '|'.join(str(self.record[k]) for k in self.table.fields()) 1032 self.record_hash = md5_hash(serialized) 1033 1034 # logic to deal with record_id for keyed tables 1035 if self.record: 1036 if keyed: 1037 formname_id = '.'.join(str(self.record[k]) 1038 for k in self.table._primarykey 1039 if hasattr(self.record,k)) 1040 record_id = dict((k, request_vars[k]) for k in self.table._primarykey) 1041 else: 1042 (formname_id, record_id) = (self.record.id, 1043 request_vars.get('id', None)) 1044 keepvalues = True 1045 else: 1046 if keyed: 1047 formname_id = 'create' 1048 record_id = dict([(k, None) for k in self.table._primarykey]) 1049 else: 1050 (formname_id, record_id) = ('create', None) 1051 1052 if not keyed and isinstance(record_id, (list, tuple)): 1053 record_id = record_id[0] 1054 1055 if formname: 1056 formname = formname % dict(tablename = self.table._tablename, 1057 record_id = formname_id) 1058 1059 # ## THIS IS FOR UNIQUE RECORDS, read IS_NOT_IN_DB 1060 1061 for fieldname in self.fields: 1062 field = self.table[fieldname] 1063 requires = field.requires or [] 1064 if not isinstance(requires, (list, tuple)): 1065 requires = [requires] 1066 [item.set_self_id(self.record_id) for item in requires 1067 if hasattr(item, 'set_self_id') and self.record_id] 1068 1069 # ## END 1070 1071 fields = {} 1072 for key in self.vars: 1073 fields[key] = self.vars[key] 1074 1075 ret = FORM.accepts( 1076 self, 1077 request_vars, 1078 session, 1079 formname, 1080 keepvalues, 1081 onvalidation, 1082 hideerror=hideerror, 1083 ) 1084 1085 if not ret and self.record and self.errors: 1086 ### if there are errors in update mode 1087 # and some errors refers to an already uploaded file 1088 # delete error if 1089 # - user not trying to upload a new file 1090 # - there is existing file and user is not trying to delete it 1091 # this is because removing the file may not pass validation 1092 for key in self.errors.keys(): 1093 if key in self.table \ 1094 and self.table[key].type == 'upload' \ 1095 and request_vars.get(key, None) in (None, '') \ 1096 and self.record[key] \ 1097 and not key + UploadWidget.ID_DELETE_SUFFIX in request_vars: 1098 del self.errors[key] 1099 if not self.errors: 1100 ret = True 1101 1102 self.deleted = \ 1103 request_vars.get(self.FIELDNAME_REQUEST_DELETE, False) 1104 1105 self.custom.end = TAG[''](self.hidden_fields(), self.custom.end) 1106 1107 auch = record_id and self.errors and self.deleted 1108 1109 # auch is true when user tries to delete a record 1110 # that does not pass validation, yet it should be deleted 1111 1112 if not ret and not auch: 1113 for fieldname in self.fields: 1114 field = self.table[fieldname] 1115 ### this is a workaround! widgets should always have default not None! 1116 if not field.widget and field.type.startswith('list:') and \ 1117 not OptionsWidget.has_options(field): 1118 field.widget = self.widgets.list.widget 1119 if hasattr(field, 'widget') and field.widget and fieldname in request_vars: 1120 if fieldname in self.vars: 1121 value = self.vars[fieldname] 1122 elif self.record: 1123 value = self.record[fieldname] 1124 else: 1125 value = self.table[fieldname].default 1126 if field.type.startswith('list:') and \ 1127 isinstance(value, str): 1128 value = [value] 1129 row_id = '%s_%s%s' % (self.table, fieldname, SQLFORM.ID_ROW_SUFFIX) 1130 widget = field.widget(field, value) 1131 self.field_parent[row_id].components = [ widget ] 1132 if not field.type.startswith('list:'): 1133 self.field_parent[row_id]._traverse(False, hideerror) 1134 self.custom.widget[ fieldname ] = widget 1135 self.accepted = ret 1136 return ret 1137 1138 if record_id and str(record_id) != str(self.record_id): 1139 raise SyntaxError, 'user is tampering with form\'s record_id: ' \ 1140 '%s != %s' % (record_id, self.record_id) 1141 1142 if record_id and dbio and not keyed: 1143 self.vars.id = self.record.id 1144 1145 if self.deleted and self.custom.deletable: 1146 if dbio: 1147 if keyed: 1148 qry = reduce(lambda x, y: x & y, 1149 [self.table[k] == record_id[k] \ 1150 for k in self.table._primarykey]) 1151 else: 1152 qry = self.table._id == self.record.id 1153 self.table._db(qry).delete() 1154 self.errors.clear() 1155 for component in self.elements('input, select, textarea'): 1156 component['_disabled'] = True 1157 self.accepted = True 1158 return True 1159 1160 for fieldname in self.fields: 1161 if not fieldname in self.table.fields: 1162 continue 1163 1164 if not self.ignore_rw and not self.table[fieldname].writable: 1165 ### this happens because FORM has no knowledge of writable 1166 ### and thinks that a missing boolean field is a None 1167 if self.table[fieldname].type == 'boolean' and \ 1168 self.vars.get(fieldname, True) is None: 1169 del self.vars[fieldname] 1170 continue 1171 1172 field = self.table[fieldname] 1173 if field.type == 'id': 1174 continue 1175 if field.type == 'boolean': 1176 if self.vars.get(fieldname, False): 1177 self.vars[fieldname] = fields[fieldname] = True 1178 else: 1179 self.vars[fieldname] = fields[fieldname] = False 1180 elif field.type == 'password' and self.record\ 1181 and request_vars.get(fieldname, None) == \ 1182 PasswordWidget.DEFAULT_PASSWORD_DISPLAY: 1183 continue # do not update if password was not changed 1184 elif field.type == 'upload': 1185 f = self.vars[fieldname] 1186 fd = '%s__delete' % fieldname 1187 if f == '' or f is None: 1188 if self.vars.get(fd, False) or not self.record: 1189 fields[fieldname] = '' 1190 else: 1191 fields[fieldname] = self.record[fieldname] 1192 self.vars[fieldname] = fields[fieldname] 1193 continue 1194 elif hasattr(f, 'file'): 1195 (source_file, original_filename) = (f.file, f.filename) 1196 elif isinstance(f, (str, unicode)): 1197 ### do not know why this happens, it should not 1198 (source_file, original_filename) = \ 1199 (cStringIO.StringIO(f), 'file.txt') 1200 newfilename = field.store(source_file, original_filename, field.uploadfolder) 1201 # this line is for backward compatibility only 1202 self.vars['%s_newfilename' % fieldname] = newfilename 1203 fields[fieldname] = newfilename 1204 if isinstance(field.uploadfield, str): 1205 fields[field.uploadfield] = source_file.read() 1206 # proposed by Hamdy (accept?) do we need fields at this point? 1207 self.vars[fieldname] = fields[fieldname] 1208 continue 1209 elif fieldname in self.vars: 1210 fields[fieldname] = self.vars[fieldname] 1211 elif field.default is None and field.type != 'blob': 1212 self.errors[fieldname] = 'no data' 1213 self.accepted = False 1214 return False 1215 value = fields.get(fieldname,None) 1216 if field.type == 'list:string': 1217 if not isinstance(value, (tuple, list)): 1218 fields[fieldname] = value and [value] or [] 1219 elif isinstance(field.type,str) and field.type.startswith('list:'): 1220 if not isinstance(value, list): 1221 fields[fieldname] = [safe_int(x) for x in (value and [value] or [])] 1222 elif field.type == 'integer': 1223 if not value is None: 1224 fields[fieldname] = safe_int(value) 1225 elif field.type.startswith('reference'): 1226 if not value is None and isinstance(self.table, Table) and not keyed: 1227 fields[fieldname] = safe_int(value) 1228 elif field.type == 'double': 1229 if not value is None: 1230 fields[fieldname] = safe_float(value) 1231 1232 for fieldname in self.vars: 1233 if fieldname != 'id' and fieldname in self.table.fields\ 1234 and not fieldname in fields and not fieldname\ 1235 in request_vars: 1236 fields[fieldname] = self.vars[fieldname] 1237 1238 if dbio: 1239 if 'delete_this_record' in fields: 1240 # this should never happen but seems to happen to some 1241 del fields['delete_this_record'] 1242 for field in self.table: 1243 if not field.name in fields and field.writable==False \ 1244 and field.update is None: 1245 if record_id and self.record: 1246 fields[field.name] = self.record[field.name] 1247 elif not self.table[field.name].default is None: 1248 fields[field.name] = self.table[field.name].default 1249 if keyed: 1250 if reduce(lambda x, y: x and y, record_id.values()): # if record_id 1251 if fields: 1252 qry = reduce(lambda x, y: x & y, 1253 [self.table[k] == self.record[k] for k in self.table._primarykey]) 1254 self.table._db(qry).update(**fields) 1255 else: 1256 pk = self.table.insert(**fields) 1257 if pk: 1258 self.vars.update(pk) 1259 else: 1260 ret = False 1261 else: 1262 if record_id: 1263 self.vars.id = self.record.id 1264 if fields: 1265 self.table._db(self.table._id == self.record.id).update(**fields) 1266 else: 1267 self.vars.id = self.table.insert(**fields) 1268 self.accepted = ret 1269 return ret1270 1271 @staticmethod1273 """ 1274 generates a SQLFORM for the given fields. 1275 1276 Internally will build a non-database based data model 1277 to hold the fields. 1278 """ 1279 # Define a table name, this way it can be logical to our CSS. 1280 # And if you switch from using SQLFORM to SQLFORM.factory 1281 # your same css definitions will still apply. 1282 1283 table_name = attributes.get('table_name', 'no_table') 1284 1285 # So it won't interfear with SQLDB.define_table 1286 if 'table_name' in attributes: 1287 del attributes['table_name'] 1288 1289 return SQLFORM(DAL(None).define_table(table_name, *fields), 1290 **attributes)1291 1292 @staticmethod1294 key = keywords.strip() 1295 if key and not ' ' in key: 1296 SEARCHABLE_TYPES = ('string','text','list:string') 1297 parts = [field.contains(key) for field in fields if field.type in SEARCHABLE_TYPES] 1298 else: 1299 parts = None 1300 if parts: 1301 return reduce(lambda a,b: a|b,parts) 1302 else: 1303 return smart_query(fields,key)1304 1305 @staticmethod 1375 1376 1377 @staticmethod1378 - def grid(query, 1379 fields=None, 1380 field_id=None, 1381 left=None, 1382 headers={}, 1383 orderby=None, 1384 searchable=True, 1385 sortable=True, 1386 paginate=20, 1387 deletable=True, 1388 editable=True, 1389 details=True, 1390 selectable=None, 1391 create=True, 1392 csv=True, 1393 links=None, 1394 links_in_grid=True, 1395 upload = '<default>', 1396 args=[], 1397 user_signature = True, 1398 maxtextlengths={}, 1399 maxtextlength=20, 1400 onvalidation=None, 1401 oncreate=None, 1402 onupdate=None, 1403 ondelete=None, 1404 sorter_icons=(XML('↑'),XML('↓')), 1405 ui = 'web2py', 1406 showbuttontext=True, 1407 _class="web2py_grid", 1408 formname='web2py_grid', 1409 search_widget='default', 1410 ignore_rw = False, 1411 formstyle = 'table3cols', 1412 ):1413 1414 # jQuery UI ThemeRoller classes (empty if ui is disabled) 1415 if ui == 'jquery-ui': 1416 ui = dict(widget='ui-widget', 1417 header='ui-widget-header', 1418 content='ui-widget-content', 1419 default='ui-state-default', 1420 cornerall='ui-corner-all', 1421 cornertop='ui-corner-top', 1422 cornerbottom='ui-corner-bottom', 1423 button='ui-button-text-icon-primary', 1424 buttontext='ui-button-text', 1425 buttonadd='ui-icon ui-icon-plusthick', 1426 buttonback='ui-icon ui-icon-arrowreturnthick-1-w', 1427 buttonexport='ui-icon ui-icon-transferthick-e-w', 1428 buttondelete='ui-icon ui-icon-trash', 1429 buttonedit='ui-icon ui-icon-pencil', 1430 buttontable='ui-icon ui-icon-triangle-1-e', 1431 buttonview='ui-icon ui-icon-zoomin', 1432 ) 1433 elif ui == 'web2py': 1434 ui = dict(widget='', 1435 header='', 1436 content='', 1437 default='', 1438 cornerall='', 1439 cornertop='', 1440 cornerbottom='', 1441 button='button', 1442 buttontext='buttontext button', 1443 buttonadd='icon plus', 1444 buttonback='icon leftarrow', 1445 buttonexport='icon downarrow', 1446 buttondelete='icon trash', 1447 buttonedit='icon pen', 1448 buttontable='icon rightarrow', 1449 buttonview='icon magnifier', 1450 ) 1451 elif not isinstance(ui,dict): 1452 raise RuntimeError,'SQLFORM.grid ui argument must be a dictionary' 1453 1454 from gluon import current, redirect 1455 db = query._db 1456 T = current.T 1457 request = current.request 1458 session = current.session 1459 response = current.response 1460 wenabled = (not user_signature or (session.auth and session.auth.user)) 1461 create = wenabled and create 1462 editable = wenabled and editable 1463 deletable = wenabled and deletable 1464 if search_widget=='default': 1465 search_widget = SQLFORM.search_menu 1466 def url(**b): 1467 b['args'] = args+b.get('args',[]) 1468 b['user_signature'] = user_signature 1469 return URL(**b)1470 1471 def gridbutton(buttonclass='buttonadd',buttontext='Add', 1472 buttonurl=url(args=[]),callback=None,delete=None,trap=True): 1473 if showbuttontext: 1474 if callback: 1475 return A(SPAN(_class=ui.get(buttonclass,'')), 1476 SPAN(T(buttontext),_title=buttontext, 1477 _class=ui.get('buttontext','')), 1478 callback=callback,delete=delete, 1479 _class=trap_class(ui.get('button',''),trap)) 1480 else: 1481 return A(SPAN(_class=ui.get(buttonclass,'')), 1482 SPAN(T(buttontext),_title=buttontext, 1483 _class=ui.get('buttontext','')), 1484 _href=buttonurl, 1485 _class=trap_class(ui.get('button',''),trap)) 1486 else: 1487 if callback: 1488 return A(SPAN(_class=ui.get(buttonclass,'')), 1489 callback=callback,delete=delete, 1490 _title=buttontext, 1491 _class=trap_class(ui.get('buttontext',''),trap)) 1492 else: 1493 return A(SPAN(_class=ui.get(buttonclass,'')), 1494 _href=buttonurl,_title=buttontext, 1495 _class=trap_class(ui.get('buttontext',''),trap))1851 - def smartgrid(table, constraints=None, linked_tables=None, 1852 links=None, links_in_grid=True, 1853 args=None, user_signature=True, 1854 **kwargs):1855 """ 1856 @auth.requires_login() 1857 def index(): 1858 db.define_table('person',Field('name'),format='%(name)s') 1859 db.define_table('dog', 1860 Field('name'),Field('owner',db.person),format='%(name)s') 1861 db.define_table('comment',Field('body'),Field('dog',db.dog)) 1862 if db(db.person).isempty(): 1863 from gluon.contrib.populate import populate 1864 populate(db.person,300) 1865 populate(db.dog,300) 1866 populate(db.comment,1000) 1867 db.commit() 1868 form=SQLFORM.smartgrid(db[request.args(0) or 'person']) #*** 1869 return dict(form=form) 1870 1871 *** builds a complete interface to navigate all tables links 1872 to the request.args(0) 1873 table: pagination, search, view, edit, delete, 1874 children, parent, etc. 1875 1876 constraints is a dict {'table',query} that limits which 1877 records can be accessible 1878 links is a dict like 1879 {'tablename':[lambda row: A(....), ...]} 1880 that will add buttons when table tablename is displayed 1881 linked_tables is a optional list of tablenames of tables 1882 to be linked 1883 """ 1884 from gluon import current, A, URL, DIV, H3, redirect 1885 request, T = current.request, current.T 1886 if args is None: args = [] 1887 db = table._db 1888 breadcrumbs = [] 1889 if request.args(len(args)) != table._tablename: 1890 request.args[:]=args+[table._tablename] 1891 if links is None: links = {} 1892 if constraints is None: constraints = {} 1893 try: 1894 nargs = len(args)+1 1895 previous_tablename,previous_fieldname,previous_id = \ 1896 table._tablename,None,None 1897 while len(request.args)>nargs: 1898 key = request.args(nargs) 1899 if '.' in key: 1900 id = request.args(nargs+1) 1901 tablename,fieldname = key.split('.',1) 1902 table = db[tablename] 1903 field = table[fieldname] 1904 field.default = id 1905 referee = field.type[10:] 1906 if referee!=previous_tablename: 1907 raise HTTP(400) 1908 cond = constraints.get(referee,None) 1909 if cond: 1910 record = db(db[referee].id==id)(cond).select().first() 1911 else: 1912 record = db[referee](id) 1913 if previous_id: 1914 if record[previous_fieldname] != int(previous_id): 1915 raise HTTP(400) 1916 previous_tablename,previous_fieldname,previous_id = \ 1917 tablename,fieldname,id 1918 try: 1919 name = db[referee]._format % record 1920 except TypeError: 1921 name = id 1922 breadcrumbs += [A(T(db[referee]._plural), 1923 _class=trap_class(), 1924 _href=URL(args=request.args[:nargs])), 1925 ' ', 1926 A(name,_class=trap_class(), 1927 _href=URL(args=request.args[:nargs]+[ 1928 'view',referee,id],user_signature=True)), 1929 ' > '] 1930 nargs+=2 1931 else: 1932 break 1933 if nargs>len(args)+1: 1934 query = (field == id) 1935 if linked_tables is None or referee in linked_tables: 1936 field.represent = lambda id,r=None,referee=referee,rep=field.represent: A(callable(rep) and rep(id) or id,_class=trap_class(),_href=URL(args=request.args[:nargs]+['view',referee,id], user_signature=user_signature)) 1937 except (KeyError,ValueError,TypeError): 1938 redirect(URL(args=table._tablename)) 1939 if nargs==len(args)+1: 1940 query = table.id>0 1941 1942 # filter out data info for displayed table 1943 if table._tablename in constraints: 1944 query = query & constraints[table._tablename] 1945 if isinstance(links,dict): 1946 links = links.get(table._tablename,[]) 1947 for key in 'columns,orderby,searchable,sortable,paginate,deletable,editable,details,selectable,create'.split(','): 1948 if isinstance(kwargs.get(key,None),dict): 1949 if table._tablename in kwargs[key]: 1950 kwargs[key] = kwargs[key][table._tablename] 1951 else: 1952 del kwargs[key] 1953 for tablename,fieldname in table._referenced_by: 1954 if linked_tables is None or tablename in linked_tables: 1955 args0 = tablename+'.'+fieldname 1956 links.append( 1957 lambda row,t=T(db[tablename]._plural),nargs=nargs,args0=args0:\ 1958 A(SPAN(t),_class=trap_class(),_href=URL( 1959 args=request.args[:nargs]+[args0,row.id]))) 1960 grid=SQLFORM.grid(query,args=request.args[:nargs],links=links, 1961 links_in_grid=links_in_grid, 1962 user_signature=user_signature,**kwargs) 1963 if isinstance(grid,DIV): 1964 breadcrumbs.append(A(T(table._plural),_class=trap_class(), 1965 _href=URL(args=request.args[:nargs]))) 1966 grid.insert(0,DIV(H3(*breadcrumbs),_class='web2py_breadcrumbs')) 1967 return grid1968 19691971 1972 """ 1973 given a Rows object, as returned by a db().select(), generates 1974 an html table with the rows. 1975 1976 optional arguments: 1977 1978 :param linkto: URL (or lambda to generate a URL) to edit individual records 1979 :param upload: URL to download uploaded files 1980 :param orderby: Add an orderby link to column headers. 1981 :param headers: dictionary of headers to headers redefinions 1982 headers can also be a string to gerenare the headers from data 1983 for now only headers="fieldname:capitalize", 1984 headers="labels" and headers=None are supported 1985 :param truncate: length at which to truncate text in table cells. 1986 Defaults to 16 characters. 1987 :param columns: a list or dict contaning the names of the columns to be shown 1988 Defaults to all 1989 1990 Optional names attributes for passed to the <table> tag 1991 1992 The keys of headers and columns must be of the form "tablename.fieldname" 1993 1994 Simple linkto example:: 1995 1996 rows = db.select(db.sometable.ALL) 1997 table = SQLTABLE(rows, linkto='someurl') 1998 1999 This will link rows[id] to .../sometable/value_of_id 2000 2001 2002 More advanced linkto example:: 2003 2004 def mylink(field, type, ref): 2005 return URL(args=[field]) 2006 2007 rows = db.select(db.sometable.ALL) 2008 table = SQLTABLE(rows, linkto=mylink) 2009 2010 This will link rows[id] to 2011 current_app/current_controlle/current_function/value_of_id 2012 2013 New Implements: 24 June 2011: 2014 ----------------------------- 2015 2016 :param selectid: The id you want to select 2017 :param renderstyle: Boolean render the style with the table 2018 2019 :param extracolums = [{'label':A('Extra',_href='#'), 2020 'class': '', #class name of the header 2021 'width':'', #width in pixels or % 2022 'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id), 2023 'selected': False #agregate class selected to this column 2024 }] 2025 2026 2027 :param headers = {'table.id':{'label':'Id', 2028 'class':'', #class name of the header 2029 'width':'', #width in pixels or % 2030 'truncate': 16, #truncate the content to... 2031 'selected': False #agregate class selected to this column 2032 }, 2033 'table.myfield':{'label':'My field', 2034 'class':'', #class name of the header 2035 'width':'', #width in pixels or % 2036 'truncate': 16, #truncate the content to... 2037 'selected': False #agregate class selected to this column 2038 }, 2039 } 2040 2041 table = SQLTABLE(rows, headers=headers, extracolums=extracolums) 2042 2043 2044 """ 20452253 2254 form_factory = SQLFORM.factory # for backward compatibility, deprecated 22552046 - def __init__( 2047 self, 2048 sqlrows, 2049 linkto=None, 2050 upload=None, 2051 orderby=None, 2052 headers={}, 2053 truncate=16, 2054 columns=None, 2055 th_link='', 2056 extracolumns=None, 2057 selectid=None, 2058 renderstyle=False, 2059 **attributes 2060 ):2061 2062 TABLE.__init__(self, **attributes) 2063 2064 self.components = [] 2065 self.attributes = attributes 2066 self.sqlrows = sqlrows 2067 (components, row) = (self.components, []) 2068 if not sqlrows: 2069 return 2070 if not columns: 2071 columns = sqlrows.colnames 2072 if headers=='fieldname:capitalize': 2073 headers = {} 2074 for c in columns: 2075 headers[c] = c.split('.')[-1].replace('_',' ').title() 2076 elif headers=='labels': 2077 headers = {} 2078 for c in columns: 2079 (t,f) = c.split('.') 2080 field = sqlrows.db[t][f] 2081 headers[c] = field.label 2082 if headers is None: 2083 headers = {} 2084 else: 2085 for c in columns:#new implement dict 2086 if isinstance(headers.get(c, c), dict): 2087 coldict = headers.get(c, c) 2088 attrcol = dict() 2089 if coldict['width']!="": 2090 attrcol.update(_width=coldict['width']) 2091 if coldict['class']!="": 2092 attrcol.update(_class=coldict['class']) 2093 row.append(TH(coldict['label'],**attrcol)) 2094 elif orderby: 2095 row.append(TH(A(headers.get(c, c), 2096 _href=th_link+'?orderby=' + c))) 2097 else: 2098 row.append(TH(headers.get(c, c))) 2099 2100 if extracolumns:#new implement dict 2101 for c in extracolumns: 2102 attrcol = dict() 2103 if c['width']!="": 2104 attrcol.update(_width=c['width']) 2105 if c['class']!="": 2106 attrcol.update(_class=c['class']) 2107 row.append(TH(c['label'],**attrcol)) 2108 2109 components.append(THEAD(TR(*row))) 2110 2111 2112 tbody = [] 2113 for (rc, record) in enumerate(sqlrows): 2114 row = [] 2115 if rc % 2 == 0: 2116 _class = 'even' 2117 else: 2118 _class = 'odd' 2119 2120 if not selectid is None: #new implement 2121 if record.id==selectid: 2122 _class += ' rowselected' 2123 2124 for colname in columns: 2125 if not table_field.match(colname): 2126 if "_extra" in record and colname in record._extra: 2127 r = record._extra[colname] 2128 row.append(TD(r)) 2129 continue 2130 else: 2131 raise KeyError("Column %s not found (SQLTABLE)" % colname) 2132 (tablename, fieldname) = colname.split('.') 2133 try: 2134 field = sqlrows.db[tablename][fieldname] 2135 except KeyError: 2136 field = None 2137 if tablename in record \ 2138 and isinstance(record,Row) \ 2139 and isinstance(record[tablename],Row): 2140 r = record[tablename][fieldname] 2141 elif fieldname in record: 2142 r = record[fieldname] 2143 else: 2144 raise SyntaxError, 'something wrong in Rows object' 2145 r_old = r 2146 if not field: 2147 pass 2148 elif linkto and field.type == 'id': 2149 try: 2150 href = linkto(r, 'table', tablename) 2151 except TypeError: 2152 href = '%s/%s/%s' % (linkto, tablename, r_old) 2153 r = A(r, _href=href) 2154 elif field.type.startswith('reference'): 2155 if linkto: 2156 ref = field.type[10:] 2157 try: 2158 href = linkto(r, 'reference', ref) 2159 except TypeError: 2160 href = '%s/%s/%s' % (linkto, ref, r_old) 2161 if ref.find('.') >= 0: 2162 tref,fref = ref.split('.') 2163 if hasattr(sqlrows.db[tref],'_primarykey'): 2164 href = '%s/%s?%s' % (linkto, tref, urllib.urlencode({fref:r})) 2165 r = A(represent(field,r,record), _href=str(href)) 2166 elif field.represent: 2167 r = represent(field,r,record) 2168 elif linkto and hasattr(field._table,'_primarykey') and fieldname in field._table._primarykey: 2169 # have to test this with multi-key tables 2170 key = urllib.urlencode(dict( [ \ 2171 ((tablename in record \ 2172 and isinstance(record, Row) \ 2173 and isinstance(record[tablename], Row)) and 2174 (k, record[tablename][k])) or (k, record[k]) \ 2175 for k in field._table._primarykey ] )) 2176 r = A(r, _href='%s/%s?%s' % (linkto, tablename, key)) 2177 elif field.type.startswith('list:'): 2178 r = represent(field,r or [],record) 2179 elif field.represent: 2180 r = represent(field,r,record) 2181 elif field.type == 'blob' and r: 2182 r = 'DATA' 2183 elif field.type == 'upload': 2184 if upload and r: 2185 r = A('file', _href='%s/%s' % (upload, r)) 2186 elif r: 2187 r = 'file' 2188 else: 2189 r = '' 2190 elif field.type in ['string','text']: 2191 r = str(field.formatter(r)) 2192 ur = unicode(r, 'utf8') 2193 if headers!={}: #new implement dict 2194 if isinstance(headers[colname],dict): 2195 if isinstance(headers[colname]['truncate'], int) \ 2196 and len(ur)>headers[colname]['truncate']: 2197 r = ur[:headers[colname]['truncate'] - 3] 2198 r = r.encode('utf8') + '...' 2199 elif not truncate is None and len(ur) > truncate: 2200 r = ur[:truncate - 3].encode('utf8') + '...' 2201 2202 attrcol = dict()#new implement dict 2203 if headers!={}: 2204 if isinstance(headers[colname],dict): 2205 colclass=headers[colname]['class'] 2206 if headers[colname]['selected']: 2207 colclass= str(headers[colname]['class'] + " colselected").strip() 2208 if colclass!="": 2209 attrcol.update(_class=colclass) 2210 2211 row.append(TD(r,**attrcol)) 2212 2213 if extracolumns:#new implement dict 2214 for c in extracolumns: 2215 attrcol = dict() 2216 colclass=c['class'] 2217 if c['selected']: 2218 colclass= str(c['class'] + " colselected").strip() 2219 if colclass!="": 2220 attrcol.update(_class=colclass) 2221 contentfunc = c['content'] 2222 row.append(TD(contentfunc(record, rc),**attrcol)) 2223 2224 tbody.append(TR(_class=_class, *row)) 2225 2226 if renderstyle: 2227 components.append(STYLE(self.style())) 2228 2229 components.append(TBODY(*tbody))2230 22312233 2234 css = ''' 2235 table tbody tr.odd { 2236 background-color: #DFD; 2237 } 2238 table tbody tr.even { 2239 background-color: #EFE; 2240 } 2241 table tbody tr.rowselected { 2242 background-color: #FDD; 2243 } 2244 table tbody tr td.colselected { 2245 background-color: #FDD; 2246 } 2247 table tbody tr:hover { 2248 background: #DDF; 2249 } 2250 ''' 2251 2252 return css
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0beta1 on Wed Dec 14 14:46:07 2011 | http://epydoc.sourceforge.net |