Changeset 4858
- Timestamp:
- 19 Jan 2010, 17:39:32 (15 years ago)
- Location:
- waeup/trunk/src/waeup
- Files:
-
- 7 edited
- 13 copied
Legend:
- Unmodified
- Added
- Removed
-
waeup/trunk/src/waeup/browser.txt
r4789 r4858 797 797 >>> browser.getControl(name='SUBMIT').click() 798 798 799 The file was indeed uploaded :799 The file was indeed uploaded, with the current userid inserted: 800 800 801 801 >>> os.listdir(uploadpath) 802 ['myfaculties .csv']802 ['myfaculties_zope.mgr.csv'] 803 803 804 804 We create and upload also a CSV file containing departments: … … 880 880 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... 881 881 ...The following files are available for import:... 882 ...mycertcourses .csv...Certificate Course Importer...883 ...mycertificates .csv...Certificate Importer...884 ...mycourses .csv...Course Importer...885 ...mydepartments .csv...Department Importer...886 ...myfaculties .csv...Faculty Importer...882 ...mycertcourses_zope.mgr.csv...Certificate Course Importer... 883 ...mycertificates_zope.mgr.csv...Certificate Importer... 884 ...mycourses_zope.mgr.csv...Course Importer... 885 ...mydepartments_zope.mgr.csv...Department Importer... 886 ...myfaculties_zope.mgr.csv...Faculty Importer... 887 887 ... 888 888 … … 898 898 >>> print browser.contents 899 899 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... 900 ...Successfully imported: myfaculties .csv...900 ...Successfully imported: myfaculties_zope.mgr.csv... 901 901 ... 902 902 … … 919 919 >>> print browser.contents 920 920 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... 921 ...Successfully imported: mydepartments .csv...921 ...Successfully imported: mydepartments_zope.mgr.csv... 922 922 ... 923 923 … … 930 930 >>> print browser.contents 931 931 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... 932 ...Successfully imported: mycourses .csv...932 ...Successfully imported: mycourses_zope.mgr.csv... 933 933 ... 934 934 … … 941 941 >>> print browser.contents 942 942 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... 943 ...Successfully imported: mycertificates .csv...943 ...Successfully imported: mycertificates_zope.mgr.csv... 944 944 ... 945 945 … … 952 952 >>> print browser.contents 953 953 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... 954 ...Successfully imported: mycertcourses .csv...954 ...Successfully imported: mycertcourses_zope.mgr.csv... 955 955 ... 956 956 -
waeup/trunk/src/waeup/browser/pages.py
r4789 r4858 3 3 """ 4 4 import copy 5 import csv 5 6 import grok 6 7 import os 8 import re 7 9 import sys 8 10 from hurry import yui … … 28 30 from zope.component.interfaces import Invalid 29 31 from zope.exceptions import DuplicationError 32 from zope.session.interfaces import ISession 33 34 from waeup.interfaces import IBatchProcessor 35 from zope.component import getAllUtilitiesRegisteredFor 36 30 37 31 38 grok.context(IWAeUPObject) … … 245 252 grok.context(IDataCenter) 246 253 grok.name('index') 254 grok.require('waeup.manageUniversity') 247 255 title = u'Data Center' 248 256 pnav = 0 … … 251 259 grok.context(IDataCenter) 252 260 grok.name('upload') 261 grok.require('waeup.manageUniversity') 253 262 title = u'Data Center Upload' 254 263 pnav = 0 … … 262 271 try: 263 272 filename = uploadfile.filename 264 target = os.path.join(self.context.storage, filename) 273 target = os.path.join(self.context.storage, 274 self.getNormalizedFileName(filename)) 265 275 open(target, 'wb').write(uploadfile.read()) 266 276 except IOError: … … 270 280 self.redirect(self.url(self.context)) 271 281 282 def getNormalizedFileName(self, filename): 283 """Build sane filename. 284 285 An uploaded file foo.csv will be stored as foo_USERNAME.csv 286 where username is the principal id of the currently logged in 287 user. 288 289 Spaces in filename are replaced by underscore. 290 """ 291 username = self.request.principal.id 292 filename = filename.replace(' ', '_') 293 # Only accept typical filname chars... 294 filtered_username = ''.join(re.findall('[a-zA-Z0-9_\.\-]', username)) 295 base, ext = os.path.splitext(filename) 296 return '%s_%s%s' % (base, filtered_username, ext.lower()) 272 297 273 298 class DataCenterImportCSVPage(WAeUPPage): … … 310 335 return 311 336 337 class DatacenterImportStep1(WAeUPPage): 338 """Manual import step 1: choose file 339 """ 340 grok.context(IDataCenter) 341 grok.name('import1') 342 grok.template('datacenterimport1page') 343 grok.require('waeup.manageUniversity') 344 title = u'Process CSV file' 345 pnav = 0 346 347 def getFiles(self): 348 files = self.context.getFiles(sort='date') 349 for file in files: 350 name = file.name 351 if not name.endswith('.csv') and not name.endswith('.pending'): 352 continue 353 yield file 354 355 def update(self, filename=None, select=None, cancel=None): 356 if cancel is not None: 357 self.flash('Import aborted') 358 self.redirect(self.url(self.context)) 359 return 360 if select is not None: 361 # A filename was selected 362 session = ISession(self.request)['waeup.sirp'] 363 session['import_filename'] = select 364 self.redirect(self.url(self.context, '@@import2')) 365 366 class DatacenterImportStep2(WAeUPPage): 367 """Manual import step 2: choose importer 368 """ 369 grok.context(IDataCenter) 370 grok.name('import2') 371 grok.template('datacenterimport2page') 372 grok.require('waeup.manageUniversity') 373 title = u'Process CSV file' 374 pnav = 0 375 376 filename = None 377 mode = 'create' 378 importer = None 379 380 def getPreviewHeader(self): 381 """Get the header fields of attached CSV file. 382 """ 383 reader = csv.reader(open(self.fullpath, 'rb')) 384 return reader.next() 385 386 def getPreviewBody(self): 387 """Get the first 5 rows of attached CSV file. 388 """ 389 result = [] 390 num = 0 391 for row in self.reader: 392 if num > 4: 393 break 394 num += 1 395 row = row.items() 396 # Sort fields in headerfield order 397 row = sorted(row, key=lambda k: self.reader.fieldnames.index(k[0])) 398 row = [x[1] for x in row] 399 result.append(row) 400 result.append(len(result[0]) * ['...']) 401 return result 402 403 def getImporters(self): 404 importers = getAllUtilitiesRegisteredFor(IBatchProcessor) 405 importers = [ 406 dict(title=x.name, name=x.util_name) for x in importers] 407 return importers 408 409 410 def update(self, mode=None, importer=None, 411 back1=None, cancel=None, proceed=None): 412 session = ISession(self.request)['waeup.sirp'] 413 self.filename = session.get('import_filename', None) 414 415 if self.filename is None or back1 is not None: 416 self.redirect(self.url(self.context, '@@import1')) 417 return 418 if cancel is not None: 419 self.flash('Import aborted') 420 self.redirect(self.url(self.context)) 421 return 422 self.mode = mode or session.get('import_mode', self.mode) 423 self.importer = importer or session.get('import_importer', None) 424 session['import_mode'] = self.mode 425 session['import_importer'] = self.importer 426 if proceed is not None: 427 self.redirect(self.url(self.context, '@@import3')) 428 return 429 self.fullpath = os.path.join(self.context.storage, self.filename) 430 self.reader = csv.DictReader(open(self.fullpath, 'rb')) 431 432 class DatacenterImportStep3(WAeUPPage): 433 """Manual import step 3: modify header 434 """ 435 grok.context(IDataCenter) 436 grok.name('import3') 437 grok.template('datacenterimport3page') 438 grok.require('waeup.manageUniversity') 439 title = u'Process CSV file' 440 pnav = 0 441 442 filename = None 443 mode = None 444 importername = None 445 446 @property 447 def nextstep(self): 448 return self.url(self.context, '@@import4') 449 450 def getPreviewHeader(self): 451 """Get the header fields of attached CSV file. 452 """ 453 reader = csv.reader(open(self.fullpath, 'rb')) 454 return reader.next() 455 456 def getPreviewBody(self): 457 """Get the first 5 rows of attached CSV file. 458 """ 459 result = [] 460 num = 0 461 for row in self.reader: 462 if num > 4: 463 break 464 num += 1 465 row = row.items() 466 # Sort fields in headerfield order 467 row = sorted(row, key=lambda k: self.reader.fieldnames.index(k[0])) 468 row = [x[1] for x in row] 469 result.append(row) 470 result.append(len(result[0]) * ['...']) 471 return result 472 473 def getPossibleHeaders(self): 474 """Get the possible headers. 475 476 The headers are described as dicts {value:internal_name, 477 title:displayed_name} 478 """ 479 result = [dict(title='<IGNORE COL>', value='--IGNORE--')] 480 headers = self.importer.getHeaders() 481 result.extend([dict(title=x, value=x) for x in headers]) 482 return result 483 484 def getWarnings(self): 485 import sys 486 result = [] 487 try: 488 self.importer.checkHeaders(self.headerfields, mode=self.mode) 489 except: 490 fatal = '%s' % sys.exc_info()[1] 491 result.append(fatal) 492 return result 493 494 @property 495 def nextstep(self): 496 return self.url(self.context, '@@import4') 497 498 def update(self, headerfield=None, back2=None, cancel=None, proceed=None): 499 session = ISession(self.request)['waeup.sirp'] 500 self.filename = session.get('import_filename', None) 501 self.mode = session.get('import_mode', None) 502 self.importername = session.get('import_importer', None) 503 504 if None in (self.filename, self.mode, self.importername): 505 self.redirect(self.url(self.context, '@@import2')) 506 return 507 if back2 is not None: 508 self.redirect(self.url(self.context ,'@@import2')) 509 return 510 if cancel is not None: 511 self.flash('Import aborted.') 512 self.redirect(self.url(self.context)) 513 return 514 515 self.fullpath = os.path.join(self.context.storage, self.filename) 516 self.headerfields = headerfield or self.getPreviewHeader() 517 session['import_headerfields'] = self.headerfields 518 519 if proceed is not None: 520 self.redirect(self.url(self.context, '@@import4')) 521 return 522 523 self.importer = getUtility(IBatchProcessor, name=self.importername) 524 self.reader = csv.DictReader(open(self.fullpath, 'rb')) 525 526 class DatacenterImportStep4(WAeUPPage): 527 """Manual import step 4: do actual import 528 """ 529 grok.context(IDataCenter) 530 grok.name('import4') 531 grok.template('datacenterimport4page') 532 grok.require('waeup.manageUniversity') 533 title = u'Process CSV file' 534 pnav = 0 535 536 filename = None 537 mode = None 538 importername = None 539 headerfields = None 540 warnnum = None 541 542 def update(self, back=None, finish=None, showlog=None): 543 if finish is not None: 544 self.redirect(self.url(self.context)) 545 return 546 if back is not None: 547 self.redirect(self.url(self.context, '@@import3')) 548 return 549 session = ISession(self.request)['waeup.sirp'] 550 self.filename = session.get('import_filename', None) 551 self.mode = session.get('import_mode', None) 552 self.importername = session.get('import_importer', None) 553 self.headerfields = session.get('import_headerfields', None) 554 555 if None in (self.filename, self.mode, self.importername, 556 self.headerfields): 557 self.redirect(self.url(self.context, '@@import3')) 558 return 559 560 if showlog is not None: 561 logfilename = "%s.%s.msg" % (self.filename, self.mode) 562 session['logname'] = logfilename 563 self.redirect(self.url(self.context, '@@show')) 564 return 565 566 self.fullpath = os.path.join(self.context.storage, self.filename) 567 self.importer = getUtility(IBatchProcessor, name=self.importername) 568 (linenum, warnings) = self.importer.doImport( 569 self.fullpath, self.headerfields, self.mode, 570 self.request.principal.id) 571 self.warn_num = len(warnings) 572 if self.warn_num: 573 self.flash('Processing of %d rows failed!' % self.warn_num) 574 self.flash('Successfully processed %s rows' % ( 575 linenum - (self.warn_num))) 576 577 class DatacenterLogsOverview(WAeUPPage): 578 grok.context(IDataCenter) 579 grok.name('logs') 580 grok.template('datacenterlogspage') 581 grok.require('waeup.manageUniversity') 582 title = u'Data Center Logs' 583 pnav = 0 584 585 def update(self, show=None, remove=None, logname=None, back=None): 586 session = ISession(self.request)['waeup.sirp'] 587 if back is not None: 588 self.redirect(self.url(self.context)) 589 return 590 if logname is not None: 591 session['logname'] = logname 592 if remove is not None: 593 fullpath = os.path.join(self.context.storage, logname) 594 try: 595 os.unlink(fullpath) 596 self.flash('File %s deleted.' % logname) 597 except: 598 self.flash("Could not delete %s: " % logname) 599 self.flash("Problem: %s" % sys.exc_info()[1]) 600 601 if show is not None: 602 self.redirect(self.url(self.context, '@@show')) 603 return 604 self.files = self.context.getLogFiles() 605 606 class DatacenterLogsFileview(WAeUPPage): 607 grok.context(IDataCenter) 608 grok.name('show') 609 grok.template('datacenterlogsshowfilepage') 610 grok.require('waeup.manageUniversity') 611 title = u'Show file' 612 pnav = 0 613 614 def update(self, show=None, remove=None, back=None): 615 session = ISession(self.request)['waeup.sirp'] 616 logname = session.get('logname', None) 617 if back is not None or logname is None: 618 self.redirect(self.url(self.context, '@@logs')) 619 return 620 self.filename = logname 621 self.files = self.context.getLogFiles() 622 fullpath = os.path.join(self.context.storage, logname) 623 self.filecontents = open(fullpath, 'rb').read() 624 312 625 class DatacenterSettings(WAeUPPage): 313 626 grok.context(IDataCenter) … … 317 630 title = u'Data Center Settings' 318 631 pnav = 0 319 #grok.template('master')320 632 321 633 def update(self, newpath=None, move=False, overwrite=False, -
waeup/trunk/src/waeup/browser/static/purple.css
r4789 r4858 14 14 15 15 /***** Uli Stuff *****/ 16 17 /* Make YUI button icons appear vertically centered... */ 18 .yui-skin-sam .yui-button a img { 19 vertical-align: middle; 20 margin-right: 3px; 21 margin-top: -1px; 22 margin-left: -2px; 23 } 16 24 17 25 /* Fix logo image height: as image is taller than h1 text, header area -
waeup/trunk/src/waeup/browser/viewlets.py
r4789 r4858 369 369 target = 'addcertificatecourse' 370 370 371 371 # 372 # Actions with a 'browse' icon... 373 # 374 class BrowseActionButton(ActionButton): 375 grok.baseclass() 376 grok.context(IWAeUPObject) 377 grok.template('actionbutton') 378 grok.viewletmanager(ActionBar) 379 grok.require('waeup.manageUniversity') 380 icon = 'actionicon_manage.png' # File must exist in static/ 381 target = '@@show' # link to this viewname. 382 text = 'Show batch logs' # Text to display on the button 383 384 class BrowseDatacenterLogs(BrowseActionButton): 385 grok.context(IDataCenter) 386 grok.view(DatacenterPage) 387 grok.order(4) 388 icon = 'documentinfo_templet.png' 389 target = '@@logs' 390 text = 'Show batch logs' 391 392 class BatchOpButton(ActionButton): 393 grok.context(IDataCenter) 394 grok.view(DatacenterPage) 395 grok.order(6) 396 icon = 'actionbox_templet.png' 397 target = '@@import1' 398 text = 'Batch processing' 399 372 400 # 373 401 # Primary navigation tabs (in upper left navigation bar)... -
waeup/trunk/src/waeup/datacenter.py
r4789 r4858 63 63 return result 64 64 65 def getFiles(self ):65 def getFiles(self, sort='name'): 66 66 """Get a list of files stored in `storage`. 67 67 … … 76 76 continue 77 77 result.append(DataCenterFile(fullpath)) 78 return result 79 78 if sort == 'date': 79 # sort results in newest-first order... 80 result = sorted(result, key=lambda x: x.getTimeStamp(), 81 reverse=True) 82 return result 83 84 def getLogFiles(self): 85 """Get a list of .msg files. 86 """ 87 result = [] 88 files = self.getFiles() 89 for file in files: 90 if not file.name.endswith('.msg'): 91 continue 92 result.append( 93 LogFile(os.path.join(self.storage, file.name))) 94 return result 95 80 96 def setStoragePath(self, path, move=False, overwrite=False): 81 97 """Set the path where to store files. … … 179 195 return 180 196 197 181 198 class DataCenterFile(object): 182 199 """A description of a file stored in data center. … … 189 206 self.size = self.getSize() 190 207 self.uploaddate = self.getDate() 208 self.lines = self.getLinesNumber() 191 209 192 210 def getDate(self): … … 196 214 return date.strftime('%c') 197 215 216 def getTimeStamp(self): 217 """Get a (machine readable) timestamp. 218 """ 219 return os.path.getctime(self.context) 220 198 221 def getSize(self): 199 222 """Get a human readable file size. … … 209 232 return size 210 233 234 def getLinesNumber(self): 235 """Get number of lines. 236 """ 237 num = 0 238 for line in open(self.context, 'rb'): 239 num += 1 240 return num 241 242 class LogFile(DataCenterFile): 243 """A description of a log file. 244 """ 245 def __init__(self, context): 246 super(LogFile, self).__init__(context) 247 self._markers = dict() 248 self._parsed = False 249 self.userid = self.getUserId() 250 self.mode = self.getMode() 251 self.stats = self.getStats() 252 self.source = self.getSourcePath() 253 254 def _parseFile(self, maxline=10): 255 """Find markers in a file. 256 """ 257 if self._parsed: 258 return 259 for line in open(self.context, 'rb'): 260 line = line.strip() 261 if not ':' in line: 262 continue 263 name, text = line.split(':', 1) 264 self._markers[name.lower()] = text 265 self._parsed = True 266 return 267 268 def _getMarker(self, marker): 269 marker = marker.lower() 270 if not self._parsed: 271 self._parseFile() 272 if marker in self._markers.keys(): 273 return self._markers[marker] 274 275 def getUserId(self): 276 return self._getMarker('user') or '<UNKNOWN>' 277 278 def getMode(self): 279 return self._getMarker('mode') or '<NOT SET>' 280 281 def getStats(self): 282 return self._getMarker('processed') or '<Info not avail.>' 283 284 def getSourcePath(self): 285 return self._getMarker('source') or None 286 211 287 class Import(object): 212 288 """Helper class to aggregate imports and their data. -
waeup/trunk/src/waeup/interfaces.py
r4789 r4858 9 9 from waeup.permissions import RoleSource 10 10 11 class FatalCSVError(Exception): 12 """Some row could not be processed. 13 """ 14 pass 15 11 16 def SimpleWAeUPVocabulary(*terms): 12 17 """A well-buildt vocabulary provides terms with a value, token and … … 21 26 up a catalog. 22 27 """ 23 catalog = None24 28 def getValues(self): 25 if self.catalog is None: 26 self.catalog = getUtility(ICatalog, name='courses_catalog') 27 return list(self.catalog.searchResults(code=('', 'z*'))) 29 catalog = getUtility(ICatalog, name='courses_catalog') 30 return list(catalog.searchResults(code=('', 'z*'))) 31 32 def getToken(self, value): 33 return value.code 28 34 29 35 def getTitle(self, value): … … 201 207 review_state = schema.Choice( 202 208 title = u'review state', 209 default = 'unchecked', 203 210 values = ['unchecked', 'checked'] 204 211 ) … … 348 355 `clear_old_data` is set to True. 349 356 """ 350 357 358 class IBatchProcessor(Interface): 359 """A batch processor that handles mass-operations. 360 """ 361 name = schema.TextLine( 362 title = u'Importer name' 363 ) 364 365 mode = schema.Choice( 366 title = u'Import mode', 367 values = ['create', 'update', 'remove'] 368 ) 369 370 def doImport(path): 371 """Read data from ``path`` and update connected object. 372 """ 373 374 class ISchemaTypeConverter(Interface): 375 """A converter for zope.schema.types. 376 """ 377 def convert(string): 378 """Convert string to certain schema type. 379 """ 380 381 351 382 class IUserAccount(IWAeUPObject): 352 383 """A user account. … … 403 434 """A data center file. 404 435 """ 436 437 name = schema.TextLine( 438 title = u'Filename') 439 440 size = schema.TextLine( 441 title = u'Human readable file size') 442 443 uploaddate = schema.TextLine( 444 title = u'Human readable upload datetime') 445 446 lines = schema.Int( 447 title = u'Number of lines in file') 448 405 449 def getDate(): 406 450 """Get creation timestamp from file in human readable form. … … 410 454 """Get human readable size of file. 411 455 """ 456 457 def getLinesNumber(): 458 """Get number of lines of file. 459 """ -
waeup/trunk/src/waeup/utils/api.txt
r4789 r4858 15 15 16 16 helpers.txt 17 batching.txt 17 18 importexport.txt 18 19 csvimport.txt 20 converters.txt
Note: See TracChangeset for help on using the changeset viewer.