Ignore:
Timestamp:
14 Jun 2016, 15:55:07 (9 years ago)
Author:
Henrik Bettermann
Message:

Adjust customizations to changes in base package.

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  
    1717##
    1818import grok
     19import csv
     20from cStringIO import StringIO
    1921from zope.i18n import translate
    2022from zope.component import getUtility, queryUtility
     
    539541    grok.template('editscorespage')
    540542
     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
    541561    def update(self,  *args, **kw):
    542562        form = self.request.form
    543         ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
     563        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
    544564        self.current_academic_session = grok.getSite()[
    545565            'configuration'].current_academic_session
     
    559579            self.redirect(self.url(self.context))
    560580            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")
    564598                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")
    609635        return
    610636
  • 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">
    211  <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&hellip;
     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">&times;</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 />
    351
    452  <table class="kofa-data-table dataTable">
    553    <thead>
    6         <tr>
     54    <tr>
    755      <th i18n:translate="">Matric No.</th>
    856      <th i18n:translate="">Fullname</th>
     
    1260      <th i18n:translate="">Score</th>
    1361      <th i18n:translate="">CA</th>
    14         </tr>
     62    </tr>
    1563    </thead>
    1664    <tbody>
    17         <tr tal:repeat="ticket view/tickets">
     65    <tr tal:repeat="ticket view/tickets">
    1866      <td tal:content="ticket/student/matric_number">MATRIC_NUMBER</td>
    19           <td tal:content="ticket/student/display_fullname">FULLNAME</td>
     67      <td tal:content="ticket/student/display_fullname">FULLNAME</td>
    2068      <td tal:content="ticket/student/translated_state">STATE</td>
    2169      <td tal:content="ticket/student/certcode">CERTCODE</td>
    2270      <td tal:content="ticket/level">LEVEL</td>
    23           <td tal:condition="ticket/editable_by_lecturer" style="width: 65px;">
    24           <input type="text" name="scores" class="form-control"
    25                      tal:attributes="value ticket/score" />
    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"
    2775                 tal:attributes="value ticket/student/student_id" />
    2876      </td>
    29           <td tal:condition="ticket/editable_by_lecturer" style="width: 65px;">
    30           <input type="text" name="cas" class="form-control"
    31                      tal:attributes="value ticket/ca" />
     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" />
    3280      </td>
    3381      <td tal:condition="not: ticket/editable_by_lecturer"
     
    3583      <td tal:condition="not: ticket/editable_by_lecturer"
    3684          tal:content="ticket/ca">CA</td>
    37         </tr>
     85    </tr>
    3886    </tbody>
    3987  </table>
    4088
    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"
    4391         />
    4492</form>
  • main/waeup.aaue/trunk/src/waeup/aaue/students/browser_templates/studyleveleditpage.pt

    r13834 r13937  
    2727       <td>
    2828        <input type="checkbox" name="val_id"
    29                   tal:attributes="value value/__name__"
     29        tal:attributes="value value/__name__"
    3030        tal:condition="value/removable_by_student" />
    3131      </td>
  • main/waeup.aaue/trunk/src/waeup/aaue/students/tests/test_browser.py

    r13909 r13937  
    2121import pytz
    2222import grok
     23from StringIO import StringIO
    2324from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
    2425from zope.securitypolicy.interfaces import IPrincipalRoleManager
     
    3132from zope.catalog.interfaces import ICatalog
    3233from waeup.kofa.app import University
     34from waeup.kofa.interfaces import VALIDATED
    3335from waeup.kofa.students.tests.test_browser import StudentsFullSetup
    3436from waeup.kofa.students.accommodation import BedTicket
     
    7274        return
    7375
     76UPLOAD_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
    7481class OfficerUITests(StudentsFullSetup):
    7582    # Tests for Student class views and pages
    7683
    7784    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
    78117
    79118    def test_gpa_calculation(self):
     
    281320        return
    282321
     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
    283338class StudentUITests(StudentsFullSetup):
    284339    """Tests for customized student class views and pages
     
    688743            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
    689744        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()
    695750        # New score and ca has been set.
    696751        self.assertEqual(
     
    710765            "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/edit_scores")
    711766        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'
    713768        self.browser.getControl("Update scores").click()
    714769        self.assertTrue('Error: Score(s) and CA(s) of Anna Tester have not be updated.'
     
    717772        self.browser.open(
    718773            "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 = ''
    720775        self.browser.getControl("Update scores").click()
    721776        self.assertEqual(
Note: See TracChangeset for help on using the changeset viewer.