Changeset 13937
- Timestamp:
- 14 Jun 2016, 15:55:07 (9 years ago)
- Location:
- main/waeup.aaue/trunk/src/waeup/aaue/students
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.aaue/trunk/src/waeup/aaue/students/browser.py
r13907 r13937 17 17 ## 18 18 import grok 19 import csv 20 from cStringIO import StringIO 19 21 from zope.i18n import translate 20 22 from zope.component import getUtility, queryUtility … … 539 541 grok.template('editscorespage') 540 542 543 544 def _extract_uploadfile(self, uploadfile): 545 """Get a mapping of student-ids to scores. 546 547 The mapping is constructed by reading contents from `uploadfile`. 548 549 We expect uploadfile to be a regular CSV file with columns 550 ``student_id``, ``score`` and ``ca`` (other cols are ignored). 551 """ 552 result = dict() 553 data = StringIO(uploadfile.read()) # ensure we have something seekable 554 reader = csv.DictReader(data) 555 for row in reader: 556 if not ('student_id' in row and 'score' in row and 'ca' in row): 557 continue 558 result[row['student_id']] = (row['score'], row['ca']) 559 return result 560 541 561 def update(self, *args, **kw): 542 562 form = self.request.form 543 ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')563 ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '') 544 564 self.current_academic_session = grok.getSite()[ 545 565 'configuration'].current_academic_session … … 559 579 self.redirect(self.url(self.context)) 560 580 return 561 if 'UPDATE' in form: 562 error = '' 563 if not editable_tickets: 581 if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form: 582 return 583 584 if not editable_tickets: 585 return 586 if 'UPDATE_FILE' in form: 587 if form['uploadfile']: 588 try: 589 formvals = self._extract_uploadfile(form['uploadfile']) 590 except: 591 self.flash( 592 _('Uploaded file contains illegal data. Ignored'), 593 type="danger") 594 return 595 else: 596 self.flash( 597 _('No file provided.'), type="danger") 564 598 return 565 scores = form['scores'] 566 cas = form['cas'] 567 sids = form['sids'] 568 if isinstance(scores, basestring): 569 scores = [scores] 570 if isinstance(cas, basestring): 571 cas = [cas] 572 if isinstance(sids, basestring): 573 sids = [sids] 574 formvals = dict([(sids[i], (scores[i], cas[i])) 575 for i in range(len(sids))]) 576 for ticket in editable_tickets: 577 ticket_error = False 578 score = ticket.score 579 ca = ticket.ca 580 sid = ticket.student.student_id 581 if formvals[sid][0] == '': 582 score = None 583 if formvals[sid][1] == '': 584 ca = None 585 try: 586 if formvals[sid][0]: 587 score = int(formvals[sid][0]) 588 if formvals[sid][1]: 589 ca = int(formvals[sid][1]) 590 except ValueError: 591 error += '%s, ' % ticket.student.display_fullname 592 ticket_error = True 593 if not ticket_error and ticket.score != score: 594 ticket.score = score 595 ticket.student.__parent__.logger.info( 596 '%s - %s %s/%s score updated (%s)' % 597 (ob_class, ticket.student.student_id, 598 ticket.level, ticket.code, score)) 599 if not ticket_error and ticket.ca != ca: 600 ticket.ca = ca 601 ticket.student.__parent__.logger.info( 602 '%s - %s %s/%s ca updated (%s)' % 603 (ob_class, ticket.student.student_id, 604 ticket.level, ticket.code, ca)) 605 if error: 606 self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. ' 607 'Only integers are allowed.' % error.strip(', ')), 608 type="danger") 599 else: 600 formvals = dict(zip(form['sids'], zip(form['scores'], form['cas']))) 601 error = '' 602 for ticket in editable_tickets: 603 ticket_error = False 604 score = ticket.score 605 ca = ticket.ca 606 sid = ticket.student.student_id 607 if formvals[sid][0] == '': 608 score = None 609 if formvals[sid][1] == '': 610 ca = None 611 try: 612 if formvals[sid][0]: 613 score = int(formvals[sid][0]) 614 if formvals[sid][1]: 615 ca = int(formvals[sid][1]) 616 except ValueError: 617 error += '%s, ' % ticket.student.display_fullname 618 ticket_error = True 619 if not ticket_error and ticket.score != score: 620 ticket.score = score 621 ticket.student.__parent__.logger.info( 622 '%s - %s %s/%s score updated (%s)' % 623 (ob_class, ticket.student.student_id, 624 ticket.level, ticket.code, score)) 625 if not ticket_error and ticket.ca != ca: 626 ticket.ca = ca 627 ticket.student.__parent__.logger.info( 628 '%s - %s %s/%s ca updated (%s)' % 629 (ob_class, ticket.student.student_id, 630 ticket.level, ticket.code, ca)) 631 if error: 632 self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. ' 633 'Only integers are allowed.' % error.strip(', ')), 634 type="danger") 609 635 return 610 636 -
main/waeup.aaue/trunk/src/waeup/aaue/students/browser_templates/editscorespage.pt
r13897 r13937 1 <form i18n:domain="waeup.kofa" method="POST"> 1 <p i18n:domain="waeup.kofa" i18n:translate="edit_scores_info"> 2 This page helps you to update your students' course results. 3 You can either update scores and CAs by importing a csv file 4 (press 'Help' for further information) or by 5 changing score or CA values in the table below. Only scores and CAs 6 of students in state 'courses validated' and current academic session 7 can be modified. 8 </p> 9 10 <form i18n:domain="waeup.kofa" method="POST" enctype="multipart/form-data"> 2 11 <br /> 12 <div class="input-group"> 13 <div class="input-group-btn"> 14 <div class="btn btn-default btn-file" i18n:translate=""> 15 Select file… 16 <input type="file" name="uploadfile:file" /> 17 </div> 18 </div> 19 <input type="text" class="form-control" readonly> 20 <div class="input-group-btn"> 21 <input type="submit" name="UPDATE_FILE" i18n:translate="" 22 value="Update editable scores from csv file" 23 class="btn btn-primary" /> 24 <button class="btn btn-warning" data-toggle="modal" 25 data-target="#myModal" i18n:translate=""> 26 Help 27 </button> 28 </div> 29 </div> 30 31 <!-- Modal --> 32 <div class="modal fade" id="myModal" tabindex="-1" role="dialog" 33 aria-labelledby="myModalLabel" aria-hidden="true"> 34 <div class="modal-dialog"> 35 <div class="modal-content"> 36 <div class="modal-header"> 37 <button type="button" class="close" data-dismiss="modal" 38 aria-hidden="true">×</button> 39 <h4 class="modal-title" id="myModalLabel" i18n:translate=""> 40 Instructions for File Upload 41 </h4> 42 </div> 43 <div class="modal-body" i18n:translate="lecturer_help"> 44 ... 45 </div> 46 </div><!-- /.modal-content --> 47 </div><!-- /.modal-dialog --> 48 </div><!-- /.modal --> 49 50 <br /><br /> 3 51 4 52 <table class="kofa-data-table dataTable"> 5 53 <thead> 6 54 <tr> 7 55 <th i18n:translate="">Matric No.</th> 8 56 <th i18n:translate="">Fullname</th> … … 12 60 <th i18n:translate="">Score</th> 13 61 <th i18n:translate="">CA</th> 14 62 </tr> 15 63 </thead> 16 64 <tbody> 17 65 <tr tal:repeat="ticket view/tickets"> 18 66 <td tal:content="ticket/student/matric_number">MATRIC_NUMBER</td> 19 67 <td tal:content="ticket/student/display_fullname">FULLNAME</td> 20 68 <td tal:content="ticket/student/translated_state">STATE</td> 21 69 <td tal:content="ticket/student/certcode">CERTCODE</td> 22 70 <td tal:content="ticket/level">LEVEL</td> 23 24 <input type="text" name="scores " class="form-control"25 26 <input type="hidden" name="sids "71 <td tal:condition="ticket/editable_by_lecturer" style="width: 65px;"> 72 <input type="text" name="scores:list" class="form-control" 73 tal:attributes="value ticket/score" /> 74 <input type="hidden" name="sids:list" 27 75 tal:attributes="value ticket/student/student_id" /> 28 76 </td> 29 30 <input type="text" name="cas " class="form-control"31 77 <td tal:condition="ticket/editable_by_lecturer" style="width: 65px;"> 78 <input type="text" name="cas:list" class="form-control" 79 tal:attributes="value ticket/ca" /> 32 80 </td> 33 81 <td tal:condition="not: ticket/editable_by_lecturer" … … 35 83 <td tal:condition="not: ticket/editable_by_lecturer" 36 84 tal:content="ticket/ca">CA</td> 37 85 </tr> 38 86 </tbody> 39 87 </table> 40 88 41 <input type="submit" name="UPDATE "42 value="Update scores " class="btn btn-primary"89 <input type="submit" name="UPDATE_TABLE" i18n:translate="" 90 value="Update scores from table" class="btn btn-primary" 43 91 /> 44 92 </form> -
main/waeup.aaue/trunk/src/waeup/aaue/students/browser_templates/studyleveleditpage.pt
r13834 r13937 27 27 <td> 28 28 <input type="checkbox" name="val_id" 29 29 tal:attributes="value value/__name__" 30 30 tal:condition="value/removable_by_student" /> 31 31 </td> -
main/waeup.aaue/trunk/src/waeup/aaue/students/tests/test_browser.py
r13909 r13937 21 21 import pytz 22 22 import grok 23 from StringIO import StringIO 23 24 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState 24 25 from zope.securitypolicy.interfaces import IPrincipalRoleManager … … 31 32 from zope.catalog.interfaces import ICatalog 32 33 from waeup.kofa.app import University 34 from waeup.kofa.interfaces import VALIDATED 33 35 from waeup.kofa.students.tests.test_browser import StudentsFullSetup 34 36 from waeup.kofa.students.accommodation import BedTicket … … 72 74 return 73 75 76 UPLOAD_CSV_TEMPLATE = ( 77 'matric_number,student_id,display_fullname,level,code,level_session,' 78 'score,ca\r\n' 79 '234,E1000000,Anna Tester,100,COURSE1,2004,%s,%s\r\n') 80 74 81 class OfficerUITests(StudentsFullSetup): 75 82 # Tests for Student class views and pages 76 83 77 84 layer = FunctionalLayer 85 86 def login_as_lecturer(self): 87 self.app['users'].addUser('mrslecturer', 'mrslecturersecret') 88 self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng' 89 self.app['users']['mrslecturer'].title = u'Mercedes Benz' 90 # Add course ticket 91 studylevel = createObject(u'waeup.StudentStudyLevel') 92 studylevel.level = 100 93 studylevel.level_session = 2004 94 self.student['studycourse'].addStudentStudyLevel( 95 self.certificate, studylevel) 96 # Assign local Lecturer role for a certificate. 97 course = self.app['faculties']['fac1']['dep1'].courses['COURSE1'] 98 prmlocal = IPrincipalRoleManager(course) 99 prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer') 100 notify(LocalRoleSetEvent( 101 course, 'waeup.local.Lecturer', 'mrslecturer', granted=True)) 102 # Login as lecturer. 103 self.browser.open(self.login_path) 104 self.browser.getControl(name="form.login").value = 'mrslecturer' 105 self.browser.getControl( 106 name="form.password").value = 'mrslecturersecret' 107 self.browser.getControl("Login").click() 108 # Store reused urls/paths 109 self.course_url = ( 110 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1') 111 self.edit_scores_url = '%s/edit_scores' % self.course_url 112 # Set standard parameters 113 self.app['configuration'].current_academic_session = 2004 114 self.app['faculties']['fac1']['dep1'].score_editing_disabled = False 115 IWorkflowState(self.student).setState(VALIDATED) 116 78 117 79 118 def test_gpa_calculation(self): … … 281 320 return 282 321 322 def test_scores_csv_upload_available(self): 323 # lecturers can upload a CSV file to set values. 324 self.login_as_lecturer() 325 # set value to change from 326 self.student['studycourse']['100']['COURSE1'].score = 55 327 self.browser.open(self.edit_scores_url) 328 upload_ctrl = self.browser.getControl(name='uploadfile:file') 329 upload_file = StringIO(UPLOAD_CSV_TEMPLATE % ('65','77')) 330 upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv') 331 self.browser.getControl("Update editable scores from").click() 332 # value changed 333 self.assertEqual( 334 self.student['studycourse']['100']['COURSE1'].score, 65) 335 self.assertEqual( 336 self.student['studycourse']['100']['COURSE1'].ca, 77) 337 283 338 class StudentUITests(StudentsFullSetup): 284 339 """Tests for customized student class views and pages … … 688 743 "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores") 689 744 self.assertTrue( 690 'input type="text" name="scores "'691 in self.browser.contents) 692 self.browser.getControl(name="scores ", index=0).value = '55'693 self.browser.getControl(name="cas ", index=0).value = '66'694 self.browser.getControl("Update scores ").click()745 'input type="text" name="scores:list"' 746 in self.browser.contents) 747 self.browser.getControl(name="scores:list", index=0).value = '55' 748 self.browser.getControl(name="cas:list", index=0).value = '66' 749 self.browser.getControl("Update scores from").click() 695 750 # New score and ca has been set. 696 751 self.assertEqual( … … 710 765 "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores") 711 766 self.assertTrue('value="55" />' in self.browser.contents) 712 self.browser.getControl(name="scores ", index=0).value = 'abc'767 self.browser.getControl(name="scores:list", index=0).value = 'abc' 713 768 self.browser.getControl("Update scores").click() 714 769 self.assertTrue('Error: Score(s) and CA(s) of Anna Tester have not be updated.' … … 717 772 self.browser.open( 718 773 "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores") 719 self.browser.getControl(name="scores ", index=0).value = ''774 self.browser.getControl(name="scores:list", index=0).value = '' 720 775 self.browser.getControl("Update scores").click() 721 776 self.assertEqual(
Note: See TracChangeset for help on using the changeset viewer.