Changeset 13935 for main


Ignore:
Timestamp:
14 Jun 2016, 01:38:12 (9 years ago)
Author:
uli
Message:

Merge changes from uli-scores-upload back into trunk.

Location:
main/waeup.kofa/trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/trunk

  • main/waeup.kofa/trunk/src/waeup/kofa

  • main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py

    r13908 r13935  
    1818import grok
    1919from urllib import urlencode
     20from zope.component import getMultiAdapter, queryAdapter, getUtility
    2021from zope.i18n import translate
    21 from zope.component import getMultiAdapter, queryAdapter, getUtility
    2222from zope.interface import Interface
    2323from zope.location.interfaces import ISite
    2424from zope.traversing.browser import absoluteURL
     25from waeup.kofa.browser.interfaces import (
     26    IFacultiesContainer, IFaculty, IDepartment, ICourse, ICertificate,
     27    ICertificateCourse, IBreadcrumbContainer, IUniversity, IUsersContainer)
     28from waeup.kofa.browser.layout import KofaPage, default_primary_nav_template
    2529from waeup.kofa.browser.pages import (
    2630    UniversityPage, FacultiesContainerPage, DatacenterPage, FacultyPage,
    2731    DepartmentPage, CoursePage, CertificatePage, CertificateCoursePage,
    2832    UsersContainerPage, UserManageFormPage, DatacenterImportStep1)
    29 from waeup.kofa.students.browser import EditScoresPage
    30 from waeup.kofa.browser.interfaces import (
    31     IFacultiesContainer, IFaculty, IDepartment, ICourse, ICertificate,
    32     ICertificateCourse, IBreadcrumbContainer, IUniversity, IUsersContainer)
     33from waeup.kofa.interfaces import MessageFactory as _
    3334from waeup.kofa.interfaces import (
    3435    IKofaUtils, IKofaObject,
    3536    IDataCenter, IUserAccount)
    36 from waeup.kofa.browser.layout import KofaPage, default_primary_nav_template
    3737from waeup.kofa.utils.helpers import get_user_account
    3838
    39 from waeup.kofa.interfaces import MessageFactory as _
    4039
    4140grok.templatedir('templates')
    4241grok.context(IKofaObject) # Make IKofaObject the default context
    4342
     43
    4444class ManageSidebar(grok.ViewletManager):
    4545    grok.name('left_manage')
    4646
     47
    4748class BreadCrumbManager(grok.ViewletManager):
    4849    grok.name('breadcrumbs')
    4950
     51
    5052class LanguageManager(grok.ViewletManager):
    5153    grok.name('languages')
    5254
     55
    5356class ActionBar(grok.ViewletManager):
    5457    grok.name('actionbar')
    5558
     59
    5660class AdministrationTasks(grok.ViewletManager):
    5761    grok.name('admintasks')
    5862
     63
    5964class WidgetsSlot(grok.ViewletManager):
    6065    grok.name('widgets')
    6166
     67
    6268class SourcesLeft(grok.ViewletManager):
    6369    grok.name('sources_left')
     70
    6471
    6572class SourcesRight(grok.ViewletManager):
     
    555562    grok.order(4)
    556563
    557 class DownloadCSVFileActionButton(ManageActionButton):
    558     """ 'Download csv file' button for courses.
    559     """
    560     grok.context(ICourse)
    561     grok.view(EditScoresPage)
    562     grok.name('downloadcsv')
    563     grok.require('waeup.editScores')
    564     icon = 'actionicon_down.png'
    565     text = _('Download editable tickets')
    566     target = 'download_scores'
    567     grok.order(1)
    568 
    569 class DownloadTicketOverviewActionButton(ManageActionButton):
    570     """ 'Download ticket overview' button for courses.
    571     """
    572     grok.context(ICourse)
    573     grok.view(EditScoresPage)
    574     grok.name('coursetickets')
    575     grok.require('waeup.editScores')
    576     icon = 'actionicon_pdf.png'
    577     text = _('Download pdf file')
    578     target = 'coursetickets.pdf'
    579     grok.order(2)
    580564
    581565class ManageCertificateActionButton(ManageActionButton):
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py

    r13908 r13935  
    1818"""UI components for students and related components.
    1919"""
    20 import sys
     20import csv
    2121import grok
    2222import pytz
     23import sys
     24from cStringIO import StringIO
     25from datetime import datetime
     26from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
    2327from urllib import urlencode
    24 from datetime import datetime
    25 from zope.event import notify
    26 from zope.i18n import translate
    2728from zope.catalog.interfaces import ICatalog
    2829from zope.component import queryUtility, getUtility, createObject
     30from zope.event import notify
     31from zope.formlib.textwidgets import BytesDisplayWidget
     32from zope.i18n import translate
    2933from zope.schema.interfaces import ConstraintNotSatisfied, RequiredMissing
    30 from zope.formlib.textwidgets import BytesDisplayWidget
    3134from zope.security import checkPermission
    32 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
    33 from waeup.kofa.accesscodes import (
    34     invalidate_accesscode, get_access_code)
     35from waeup.kofa.accesscodes import invalidate_accesscode, get_access_code
    3536from waeup.kofa.accesscodes.workflow import USED
     37from waeup.kofa.browser.breadcrumbs import Breadcrumb
     38from waeup.kofa.browser.interfaces import ICaptchaManager
    3639from waeup.kofa.browser.layout import (
    3740    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,
    3841    NullValidator, jsaction, action, UtilityView)
    39 from waeup.kofa.browser.breadcrumbs import Breadcrumb
    4042from waeup.kofa.browser.pages import (
    4143    ContactAdminFormPage, ExportCSVView, doll_up, exports_not_allowed,
    4244    LocalRoleAssignmentUtilityView)
    43 from waeup.kofa.browser.interfaces import ICaptchaManager
    4445from waeup.kofa.hostels.hostel import NOT_OCCUPIED
    4546from waeup.kofa.interfaces import (
    4647    IKofaObject, IUserAccount, IExtFileStore, IPasswordValidator, IContactForm,
    47     IKofaUtils, IUniversity, IObjectHistory, academic_sessions, ICSVExporter,
    48     academic_sessions_vocab, IJobManager, IDataCenter, DOCLINK)
     48    IKofaUtils, IObjectHistory, academic_sessions, ICSVExporter,
     49    academic_sessions_vocab, IDataCenter, DOCLINK)
    4950from waeup.kofa.interfaces import MessageFactory as _
    50 from waeup.kofa.widgets.datewidget import (
    51     FriendlyDateWidget, FriendlyDateDisplayWidget,
    52     FriendlyDatetimeDisplayWidget)
    5351from waeup.kofa.mandates.mandate import PasswordMandate
    5452from waeup.kofa.university.interfaces import (
    5553    IDepartment, ICertificate, ICourse)
     54from waeup.kofa.university.certificate import (
     55    VirtualCertificateExportJobContainer)
     56from waeup.kofa.university.department import (
     57    VirtualDepartmentExportJobContainer)
    5658from waeup.kofa.university.faculty import VirtualFacultyExportJobContainer
    57 from waeup.kofa.university.department import VirtualDepartmentExportJobContainer
    5859from waeup.kofa.university.facultiescontainer import (
    59     VirtualFacultiesExportJobContainer, FacultiesContainer)
    60 from waeup.kofa.university.certificate import (
    61     VirtualCertificateExportJobContainer,)
     60    VirtualFacultiesExportJobContainer)
    6261from waeup.kofa.university.course import (
    6362    VirtualCourseExportJobContainer,)
    6463from waeup.kofa.university.vocabularies import course_levels
    6564from waeup.kofa.utils.batching import VirtualExportJobContainer
    66 from waeup.kofa.utils.helpers import get_current_principal, to_timezone, now
     65from waeup.kofa.utils.helpers import get_current_principal, now
     66from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
    6767from waeup.kofa.students.interfaces import (
    68     IStudentsContainer, IStudent,
    69     IUGStudentClearance,IPGStudentClearance,
     68    IStudentsContainer, IStudent, IUGStudentClearance, IPGStudentClearance,
    7069    IStudentPersonal, IStudentPersonalEdit, IStudentBase, IStudentStudyCourse,
    7170    IStudentStudyCourseTransfer, IStudentStudyCourseTranscript,
    72     IStudentAccommodation, IStudentStudyLevel,
    73     ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
    74     IStudentOnlinePayment, IStudentPreviousPayment, IStudentBalancePayment,
    75     IBedTicket, IStudentsUtils, IStudentRequestPW, IStudentTranscript
     71    IStudentAccommodation, IStudentStudyLevel, ICourseTicket, ICourseTicketAdd,
     72    IStudentPaymentsContainer, IStudentOnlinePayment, IStudentPreviousPayment,
     73    IStudentBalancePayment, IBedTicket, IStudentsUtils, IStudentRequestPW,
     74    IStudentTranscript
    7675    )
    7776from waeup.kofa.students.catalog import search, StudentQueryResultItem
    78 from waeup.kofa.students.studylevel import StudentStudyLevel, CourseTicket
    7977from waeup.kofa.students.vocabularies import StudyLevelSource
    80 from waeup.kofa.students.workflow import (CREATED, ADMITTED, PAID,
    81     CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED,
    82     GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS)
    83 
    84 
    85 grok.context(IKofaObject) # Make IKofaObject the default context
     78from waeup.kofa.students.workflow import (
     79    ADMITTED, PAID, CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED,
     80    VALIDATED, GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS
     81    )
     82
     83
     84grok.context(IKofaObject)  # Make IKofaObject the default context
     85
    8686
    8787# Save function used for save methods in pages
     
    9090    # Turn list of lists into single list
    9191    if changed_fields:
    92         changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
     92        changed_fields = reduce(lambda x, y: x+y, changed_fields.values())
    9393    # Inform catalog if certificate has changed
    9494    # (applyData does this only for the context)
     
    11991199    def label(self):
    12001200        # Here we know that the cookie has been set
    1201         lang = self.request.cookies.get('kofa.language')
    12021201        return _('${a}: Transcript Data', mapping = {
    12031202            'a':self.context.student.display_fullname})
     
    13971396    def render(self):
    13981397        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
    1399         Term = translate(_('Term'), 'waeup.kofa', target_language=portal_language)
    14001398        Code = translate(_('Code'), 'waeup.kofa', target_language=portal_language)
    14011399        Title = translate(_('Title'), 'waeup.kofa', target_language=portal_language)
     
    31043102        return list(coursetickets)
    31053103
     3104    def _extract_uploadfile(self, uploadfile):
     3105        """Get a mapping of student-ids to scores.
     3106
     3107        The mapping is constructed by reading contents from `uploadfile`.
     3108
     3109        We expect uploadfile to be a regular CSV file with columns
     3110        ``student_id`` and ``score`` (other cols are ignored).
     3111        """
     3112        result = dict()
     3113        data = StringIO(uploadfile.read())  # ensure we have something seekable
     3114        reader = csv.DictReader(data)
     3115        for row in reader:
     3116            if not 'student_id' in row or not 'score' in row:
     3117                continue
     3118            result[row['student_id']] = row['score']
     3119        return result
     3120
    31063121    def update(self,  *args, **kw):
    31073122        form = self.request.form
    3108         ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
     3123        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
    31093124        self.current_academic_session = grok.getSite()[
    31103125            'configuration'].current_academic_session
     
    31243139            self.redirect(self.url(self.context))
    31253140            return
    3126         if 'UPDATE' in form:
    3127             error = ''
    3128             if not editable_tickets:
    3129                 return
    3130             scores = form['scores']
    3131             sids = form['sids']
    3132             if isinstance(scores, basestring):
    3133                 scores = [scores]
    3134             if isinstance(sids, basestring):
    3135                 sids = [sids]
    3136             formvals = dict([(sids[i], scores[i]) for i in range(len(sids))])
    3137             for ticket in editable_tickets:
    3138                 score = ticket.score
    3139                 sid = ticket.student.student_id
    3140                 if formvals[sid] == '':
    3141                     score = None
    3142                 else:
    3143                     try:
    3144                         score = int(formvals[sid])
    3145                     except ValueError:
    3146                         error += '%s, ' % ticket.student.display_fullname
    3147                 if ticket.score != score:
    3148                     ticket.score = score
    3149                     ticket.student.__parent__.logger.info(
    3150                         '%s - %s %s/%s score updated (%s)' %
    3151                         (ob_class, ticket.student.student_id,
    3152                          ticket.level, ticket.code, score))
    3153                     #notify(grok.ObjectModifiedEvent(ticket))
    3154             if error:
    3155                 self.flash(_('Error: Score(s) of %s have not be updated. '
    3156                   'Only integers are allowed.' % error.strip(', ')),
    3157                   type="danger")
    3158         return
     3141        if not 'UPDATE' in form:
     3142            return
     3143        error = ''
     3144        if not editable_tickets:
     3145            return
     3146        formvals = dict(zip(form['sids'], form['scores']))
     3147        if form['uploadfile']:
     3148            try:
     3149                formvals = self._extract_uploadfile(form['uploadfile'])
     3150            except:
     3151                self.flash(
     3152                    _('Uploaded file contains illegal data. Ignored'),
     3153                    type="danger")
     3154        for ticket in editable_tickets:
     3155            score = ticket.score
     3156            sid = ticket.student.student_id
     3157            if sid not in formvals:
     3158                continue
     3159            if formvals[sid] == '':
     3160                score = None
     3161            else:
     3162                try:
     3163                    score = int(formvals[sid])
     3164                except ValueError:
     3165                    error += '%s, ' % ticket.student.display_fullname
     3166            if ticket.score != score:
     3167                ticket.score = score
     3168                ticket.student.__parent__.logger.info(
     3169                    '%s - %s %s/%s score updated (%s)' % (
     3170                        ob_class, ticket.student.student_id,
     3171                        ticket.level, ticket.code, score)
     3172                    )
     3173        if error:
     3174            self.flash(
     3175                _('Error: Score(s) of following students have not been '
     3176                    'updated (only integers are allowed): %s.' % error.strip(', ')),
     3177                type="danger")
     3178        return
     3179
    31593180
    31603181class DownloadScoresView(UtilityView, grok.View):
     
    31663187
    31673188    def update(self):
    3168         ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
    31693189        self.current_academic_session = grok.getSite()[
    31703190            'configuration'].current_academic_session
     
    33503370            date_format = '%d/%m/%Y'
    33513371            try:
    3352                 dummy = datetime.strptime(payments_start, date_format)
    3353                 dummy = datetime.strptime(payments_end, date_format)
     3372                datetime.strptime(payments_start, date_format)
     3373                datetime.strptime(payments_end, date_format)
    33543374            except ValueError:
    33553375                self.flash(_('Payment dates do not match format d/m/Y.'),
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/editscorespage.pt

    r13896 r13935  
    1 <form  i18n:domain="waeup.kofa"  method="POST">
     1<form  i18n:domain="waeup.kofa"  method="POST" enctype="multipart/form-data">
    22  <br />
    33
     
    1515    </thead>
    1616    <tbody>
    17         <tr tal:repeat="ticket view/tickets">
     17      <tr tal:repeat="ticket view/tickets">
    1818      <td tal:content="ticket/student/matric_number">MATRIC_NUMBER</td>
    1919      <td tal:content="ticket/student/student_id">STUDENT ID</td>
     
    2424
    2525          <td tal:condition="ticket/editable_by_lecturer" style="width: 65px;">
    26           <input type="text" name="scores" class="form-control"
     26          <input type="text" name="scores:list" class="form-control"
    2727                     tal:attributes="value ticket/score" />
    28           <input type="hidden" name="sids"
     28          <input type="hidden" name="sids:list"
    2929                 tal:attributes="value ticket/student/student_id" />
    3030      </td>
     
    3232          tal:content="ticket/score">SCORE</td>
    3333        </tr>
     34
     35      <tr>
     36        <td i18n:translate="">
     37          Set scores from CSV file:
     38        </td>
     39        <td>
     40          <div class="input-group half">
     41            <div class="input-group-btn">
     42              <div class="btn btn-default btn-file">
     43                Select&hellip;
     44                <input type="file" name="uploadfile:file" />
     45              </div>
     46            </div>
     47            <input type="text" class="form-control" readonly />
     48          </div>
     49        </td>
     50      </tr>
     51
    3452    </tbody>
    3553  </table>
  • main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py

    r13908 r13935  
     1# -*- coding: utf-8 -*-
    12## $Id$
    23##
     
    4546from waeup.kofa.university.faculty import Faculty
    4647from waeup.kofa.university.department import Department
    47 from waeup.kofa.interfaces import IUserAccount, IJobManager
     48from waeup.kofa.interfaces import IUserAccount, IJobManager, VALIDATED, CREATED
    4849from waeup.kofa.authentication import LocalRoleSetEvent
    4950from waeup.kofa.hostels.hostel import Hostel, Bed, NOT_OCCUPIED
     
    5556SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
    5657SAMPLE_IMAGE_BMP = os.path.join(os.path.dirname(__file__), 'test_image.bmp')
     58URL_LECTURER_LANDING = 'http://localhost/app/my_courses'
    5759
    5860
     
    18071809        self.assertTrue(self.student_id in self.browser.contents)
    18081810
    1809     def test_handle_courses_by_lecturer(self):
    1810         self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
    1811         self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
    1812         self.app['users']['mrslecturer'].title = u'Mercedes Benz'
    1813         # Add course ticket
    1814         studylevel = createObject(u'waeup.StudentStudyLevel')
    1815         studylevel.level = 100
    1816         studylevel.level_session = 2004
    1817         self.student['studycourse'].addStudentStudyLevel(
    1818             self.certificate, studylevel)
    1819         # Assign local Lecturer role for a certificate.
    1820         course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
    1821         prmlocal = IPrincipalRoleManager(course)
    1822         prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
    1823         notify(LocalRoleSetEvent(
    1824             course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
    1825         # Login as lecturer.
    1826         self.browser.open(self.login_path)
    1827         self.browser.getControl(name="form.login").value = 'mrslecturer'
    1828         self.browser.getControl(
    1829             name="form.password").value = 'mrslecturersecret'
    1830         self.browser.getControl("Login").click()
    1831         self.assertMatches('...You logged in...', self.browser.contents)
    1832         # Lecturer is on landing page
    1833         self.assertTrue('(<span>COURSE1</span>)' in self.browser.contents)
    1834         # Lecturer can click the 'My Roles' link.
    1835         self.browser.getLink("My Roles").click()
    1836         self.assertMatches(
    1837             '...<div>Academics Officer (view only)</div>...',
    1838             self.browser.contents)
    1839         self.assertMatches(
    1840             '...<a href="http://localhost/app/'
    1841             'faculties/fac1/dep1/courses/COURSE1">...',
    1842             self.browser.contents)
    1843         self.assertFalse('(<span>COURSE1</span>)' in self.browser.contents)
    1844         # Lecturer can click the 'My Courses' link ...
    1845         self.browser.getLink("My Courses").click()
    1846         # ... and is is back on landing page
    1847         self.assertTrue('(<span>COURSE1</span>)' in self.browser.contents)
    1848         # The lecturer can go to her course ...
    1849         self.browser.getLink("COURSE1").click()
    1850         # Lecturers can neither filter students ...
    1851         self.assertRaises(
    1852             Unauthorized, self.browser.open,
    1853             "http://localhost/app/faculties/fac1/dep1/courses/COURSE1/students")
    1854         # ... nor access the student ...
    1855         self.assertRaises(
    1856             Unauthorized, self.browser.open, self.student_path)
    1857         # ... nor the respective course ticket since
    1858         # editing course tickets by lecturers is not feasible.
    1859         self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
    1860         course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
    1861         self.assertRaises(
    1862             Unauthorized, self.browser.open, course_ticket_path)
    1863         # Course results can be batch edited via the edit_courses view.
    1864         self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
    1865         self.browser.open(
    1866             "http://localhost/app/faculties/fac1/dep1/courses/COURSE1")
    1867         self.browser.getLink("Update scores").click()
    1868         self.assertTrue('Score editing disabled' in self.browser.contents)
    1869         self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
    1870         self.browser.getLink("Update scores").click()
    1871         self.assertTrue(
    1872             'Current academic session not set' in self.browser.contents)
    1873         self.app['configuration'].current_academic_session = 2004
    1874         self.browser.getLink("Update scores").click()
    1875         self.assertFalse(
    1876             '<input type="text" name="scores" class="span1" />'
    1877             in self.browser.contents)
    1878         IWorkflowState(self.student).setState('courses validated')
    1879         # Student must be in state 'courses validated'.
    1880         self.browser.open(
    1881             'http://localhost/app/faculties/fac1/'
    1882             'dep1/courses/COURSE1/edit_scores')
    1883         self.assertTrue(
    1884             'input type="text" name="scores"' in self.browser.contents)
    1885         self.browser.getControl(name="scores", index=0).value = '55'
    1886         self.browser.getControl("Update scores").click()
    1887         # New score has been set.
    1888         self.assertEqual(
    1889             self.student['studycourse']['100']['COURSE1'].score, 55)
    1890         # Score editing has been logged.
    1891         logfile = os.path.join(
    1892             self.app['datacenter'].storage, 'logs', 'students.log')
    1893         logcontent = open(logfile).read()
    1894         self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
    1895             'K1000000 100/COURSE1 score updated (55)' in logcontent)
    1896         # Non-integer scores won't be accepted.
    1897         self.browser.open(
    1898             'http://localhost/app/faculties/fac1/'
    1899             'dep1/courses/COURSE1/edit_scores')
    1900         self.assertTrue('value="55" />' in self.browser.contents)
    1901         self.browser.getControl(name="scores", index=0).value = 'abc'
    1902         self.browser.getControl("Update scores").click()
    1903         self.assertTrue('Error: Score(s) of Anna Tester have not be updated'
    1904             in self.browser.contents)
    1905         # Scores can be removed.
    1906         self.browser.open(
    1907             'http://localhost/app/faculties/fac1/'
    1908             'dep1/courses/COURSE1/edit_scores')
    1909         self.browser.getControl(name="scores", index=0).value = ''
    1910         self.browser.getControl("Update scores").click()
    1911         self.assertEqual(
    1912             self.student['studycourse']['100']['COURSE1'].score, None)
    1913         logcontent = open(logfile).read()
    1914         self.assertTrue('mrslecturer - students.browser.EditScoresPage - '
    1915             'K1000000 100/COURSE1 score updated (None)' in logcontent)
    1916         # A course ticket slip can be downloaded
    1917         self.browser.open(
    1918             'http://localhost/app/faculties/fac1/'
    1919             'dep1/courses/COURSE1/coursetickets.pdf')
    1920         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    1921         self.assertEqual(self.browser.headers['Content-Type'],
    1922                          'application/pdf')
    1923         path = os.path.join(samples_dir(), 'coursetickets.pdf')
    1924         open(path, 'wb').write(self.browser.contents)
    1925         print "Sample PDF coursetickets.pdf written to %s" % path
    1926 
    19271811    def test_change_current_mode(self):
    19281812        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     
    38983782            )
    38993783
    3900     def test_course_download_lecturer(self):
    3901         # We add study level 100 to the student's studycourse
    3902         studylevel = StudentStudyLevel()
    3903         studylevel.level = 100
    3904         studylevel.level_session = 2004
    3905         IWorkflowState(self.student).setState('courses validated')
    3906         self.student['studycourse'].addStudentStudyLevel(
    3907             self.certificate,studylevel)
    3908         course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
    3909         # Create lecturer
    3910         self.app['users'].addUser('mrlecturer', 'mrlecturersecret')
    3911         self.app['users']['mrlecturer'].email = 'mrlecturer@foo.ng'
    3912         self.app['users']['mrlecturer'].title = 'Carlo Intelligent'
    3913         prmglobal = IPrincipalRoleManager(self.course)
    3914         prmglobal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrlecturer')
    3915         # Login as lecturer
    3916         self.browser.open(self.login_path)
    3917         self.browser.getControl(name="form.login").value = 'mrlecturer'
    3918         self.browser.getControl(name="form.password").value = 'mrlecturersecret'
    3919         self.browser.getControl("Login").click()
    3920         self.assertMatches('...You logged in...', self.browser.contents)
    3921         self.browser.open(course1_path)
    3922         self.assertFalse('Export' in self.browser.contents)
    3923         self.browser.getLink("Update scores").click()
    3924         self.assertTrue('Current academic session not set' in self.browser.contents)
    3925         self.app['configuration'].current_academic_session = 2004
    3926         self.browser.getLink("Update scores").click()
    3927         self.browser.getLink("Download editable tickets").click()
    3928         self.assertEqual(self.browser.headers['Status'], '200 Ok')
    3929         self.assertEqual(self.browser.headers['Content-Type'],
    3930                          'text/csv; charset=UTF-8')
    3931         self.assertEqual(self.browser.contents, 'matric_number,student_id,'
    3932             'display_fullname,level,code,level_session,score\r\n234,'
    3933             'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
    3934 
    39353784    def test_export_departmet_officers(self):
    39363785        # Create department officer
     
    40043853        self.browser.getControl("Discard").click()
    40053854        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
     3855
     3856
     3857UPLOAD_CSV_TEMPLATE = (
     3858    'matric_number,student_id,display_fullname,level,code,level_session,'
     3859    'score\r\n'
     3860    '234,K1000000,Anna Tester,100,COURSE1,2004,%s\r\n')
     3861
     3862class LecturerUITests(StudentsFullSetup):
     3863    # Tests for UI actions when acting as lecturer.
     3864
     3865    def login_as_lecturer(self):
     3866        self.app['users'].addUser('mrslecturer', 'mrslecturersecret')
     3867        self.app['users']['mrslecturer'].email = 'mrslecturer@foo.ng'
     3868        self.app['users']['mrslecturer'].title = u'Mercedes Benz'
     3869        # Add course ticket
     3870        studylevel = createObject(u'waeup.StudentStudyLevel')
     3871        studylevel.level = 100
     3872        studylevel.level_session = 2004
     3873        self.student['studycourse'].addStudentStudyLevel(
     3874            self.certificate, studylevel)
     3875        # Assign local Lecturer role for a certificate.
     3876        course = self.app['faculties']['fac1']['dep1'].courses['COURSE1']
     3877        prmlocal = IPrincipalRoleManager(course)
     3878        prmlocal.assignRoleToPrincipal('waeup.local.Lecturer', 'mrslecturer')
     3879        notify(LocalRoleSetEvent(
     3880            course, 'waeup.local.Lecturer', 'mrslecturer', granted=True))
     3881        # Login as lecturer.
     3882        self.browser.open(self.login_path)
     3883        self.browser.getControl(name="form.login").value = 'mrslecturer'
     3884        self.browser.getControl(
     3885            name="form.password").value = 'mrslecturersecret'
     3886        self.browser.getControl("Login").click()
     3887        # Store reused urls/paths
     3888        self.course_url = (
     3889            'http://localhost/app/faculties/fac1/dep1/courses/COURSE1')
     3890        self.edit_scores_url = '%s/edit_scores' % self.course_url
     3891        # Set standard parameters
     3892        self.app['configuration'].current_academic_session = 2004
     3893        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
     3894        IWorkflowState(self.student).setState(VALIDATED)
     3895
     3896    @property
     3897    def stud_log_path(self):
     3898        return os.path.join(
     3899            self.app['datacenter'].storage, 'logs', 'students.log')
     3900
     3901    def test_lecturer_lands_on_landing_page(self):
     3902        # lecturers can login and will be led to landing page.
     3903        self.login_as_lecturer()
     3904        self.assertMatches('...You logged in...', self.browser.contents)
     3905        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
     3906
     3907    def test_my_roles_link_works(self):
     3908        # lecturers can see their roles
     3909        self.login_as_lecturer()
     3910        self.browser.getLink("My Roles").click()
     3911        self.assertTrue(
     3912            "<div>Academics Officer (view only)</div>"
     3913            in self.browser.contents)
     3914        self.assertTrue(
     3915            '<a href="%s">' % self.course_url in self.browser.contents)
     3916
     3917    def test_my_roles_page_contains_backlink(self):
     3918        # we can get back from 'My Roles' view to landing page
     3919        self.login_as_lecturer()
     3920        self.browser.getLink("My Roles").click()
     3921        self.browser.getLink("My Courses").click()
     3922        self.assertEqual(self.browser.url, URL_LECTURER_LANDING)
     3923
     3924    def test_lecturers_can_reach_their_courses(self):
     3925        # lecturers get links to their courses on the landing page
     3926        self.login_as_lecturer()
     3927        self.browser.getLink("COURSE1").click()
     3928        self.assertEqual(self.browser.url, self.course_url)
     3929
     3930    def test_lecturers_student_access_is_restricted(self):
     3931        # lecturers are not able to change other student data
     3932        self.login_as_lecturer()
     3933        # Lecturers can neither filter students ...
     3934        self.assertRaises(
     3935            Unauthorized, self.browser.open, '%s/students' % self.course_url)
     3936        # ... nor access the student ...
     3937        self.assertRaises(
     3938            Unauthorized, self.browser.open, self.student_path)
     3939        # ... nor the respective course ticket since editing course
     3940        # tickets by lecturers is not feasible.
     3941        self.assertTrue('COURSE1' in self.student['studycourse']['100'].keys())
     3942        course_ticket_path = self.student_path + '/studycourse/100/COURSE1'
     3943        self.assertRaises(
     3944            Unauthorized, self.browser.open, course_ticket_path)
     3945
     3946    def test_score_editing_requires_department_permit(self):
     3947        # we get a warning if we try to update score while we are not allowed
     3948        self.login_as_lecturer()
     3949        self.app['faculties']['fac1']['dep1'].score_editing_disabled = True
     3950        self.browser.open(self.course_url)
     3951        self.browser.getLink("Update scores").click()
     3952        self.assertTrue('Score editing disabled' in self.browser.contents)
     3953        self.app['faculties']['fac1']['dep1'].score_editing_disabled = False
     3954        self.browser.open(self.course_url)
     3955        self.browser.getLink("Update scores").click()
     3956        self.assertFalse('Score editing disabled' in self.browser.contents)
     3957
     3958    def test_score_editing_requires_validated_students(self):
     3959        # we can edit only scores of students whose courses have been
     3960        # validated.
     3961        self.login_as_lecturer()
     3962        # set invalid student state
     3963        IWorkflowState(self.student).setState(CREATED)
     3964        self.browser.open(self.edit_scores_url)
     3965        self.assertRaises(
     3966            LookupError, self.browser.getControl, name="scores")
     3967        # set valid student state
     3968        IWorkflowState(self.student).setState(VALIDATED)
     3969        self.browser.open(self.edit_scores_url)
     3970        self.assertTrue(
     3971            self.browser.getControl(name="scores:list") is not None)
     3972
     3973    def test_score_editing_offers_only_current_scores(self):
     3974        # only scores from current academic session can be edited
     3975        self.login_as_lecturer()
     3976        IWorkflowState(self.student).setState('courses validated')
     3977        # with no academic session set
     3978        self.app['configuration'].current_academic_session = None
     3979        self.browser.open(self.edit_scores_url)
     3980        self.assertRaises(
     3981            LookupError, self.browser.getControl, name="scores")
     3982        # with wrong academic session set
     3983        self.app['configuration'].current_academic_session = 1999
     3984        self.browser.open(self.edit_scores_url)
     3985        self.assertRaises(
     3986            LookupError, self.browser.getControl, name="scores")
     3987        # with right academic session set
     3988        self.app['configuration'].current_academic_session = 2004
     3989        self.browser.reload()
     3990        self.assertTrue(
     3991            self.browser.getControl(name="scores:list") is not None)
     3992
     3993    def test_score_editing_can_change_scores(self):
     3994        # we can really change scores via edit_scores view
     3995        self.login_as_lecturer()
     3996        self.assertEqual(
     3997            self.student['studycourse']['100']['COURSE1'].score, None)
     3998        self.browser.open(self.edit_scores_url)
     3999        self.browser.getControl(name="scores:list", index=0).value = '55'
     4000        self.browser.getControl("Update scores").click()
     4001        # the new value is stored in data
     4002        self.assertEqual(
     4003            self.student['studycourse']['100']['COURSE1'].score, 55)
     4004        # the new value is displayed on page/prefilled in form
     4005        self.assertEqual(
     4006            self.browser.getControl(name="scores:list", index=0).value, '55')
     4007        # The change has been logged
     4008        with open(self.stud_log_path, 'r') as fd:
     4009            self.assertTrue(
     4010                'mrslecturer - students.browser.EditScoresPage - '
     4011                'K1000000 100/COURSE1 score updated (55)' in fd.read())
     4012
     4013    def test_scores_editing_scores_must_be_integers(self):
     4014        # Non-integer scores won't be accepted.
     4015        self.login_as_lecturer()
     4016        self.browser.open(self.edit_scores_url)
     4017        self.browser.getControl(name="scores:list", index=0).value = 'abc'
     4018        self.browser.getControl("Update scores").click()
     4019        self.assertTrue(
     4020            'Error: Score(s) of following students have not been updated '
     4021            '(only integers are allowed): Anna Tester.'
     4022            in self.browser.contents)
     4023
     4024    def test_scores_editing_allows_score_removal(self):
     4025        # we can remove scores, once they were set
     4026        self.login_as_lecturer()
     4027        # without a prior value, we cannot remove
     4028        self.student['studycourse']['100']['COURSE1'].score = None
     4029        self.browser.open(self.edit_scores_url)
     4030        self.browser.getControl(name="scores:list", index=0).value = ''
     4031        self.browser.getControl("Update scores").click()
     4032        logcontent = open(self.stud_log_path, 'r').read()
     4033        self.assertFalse('COURSE1 score updated (None)' in logcontent)
     4034        # now retry with some value set
     4035        self.student['studycourse']['100']['COURSE1'].score = 55
     4036        self.browser.getControl(name="scores:list", index=0).value = ''
     4037        self.browser.getControl("Update scores").click()
     4038        logcontent = open(self.stud_log_path, 'r').read()
     4039        self.assertTrue('COURSE1 score updated (None)' in logcontent)
     4040
     4041    def test_lecturers_can_download_course_tickets(self):
     4042        # A course ticket slip can be downloaded
     4043        self.login_as_lecturer()
     4044        pdf_url = '%s/coursetickets.pdf' % self.course_url
     4045        self.browser.open(pdf_url)
     4046        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     4047        self.assertEqual(
     4048            self.browser.headers['Content-Type'], 'application/pdf')
     4049        path = os.path.join(samples_dir(), 'coursetickets.pdf')
     4050        open(path, 'wb').write(self.browser.contents)
     4051        print "Sample PDF coursetickets.pdf written to %s" % path
     4052
     4053    def test_lecturers_can_download_scores_as_csv(self):
     4054        # Lecturers can download course scores as CSV.
     4055        self.login_as_lecturer()
     4056        self.browser.open(self.edit_scores_url)
     4057        self.browser.getLink("Download editable tickets").click()
     4058        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     4059        self.assertEqual(self.browser.headers['Content-Type'],
     4060                         'text/csv; charset=UTF-8')
     4061        self.assertEqual(self.browser.contents, 'matric_number,student_id,'
     4062            'display_fullname,level,code,level_session,score\r\n234,'
     4063            'K1000000,Anna Tester,100,COURSE1,2004,\r\n')
     4064
     4065    def test_scores_csv_upload_available(self):
     4066        # lecturers can upload a CSV file to set values.
     4067        self.login_as_lecturer()
     4068        # set value to change from
     4069        self.student['studycourse']['100']['COURSE1'].score = 55
     4070        self.browser.open(self.edit_scores_url)
     4071        upload_ctrl = self.browser.getControl(name='uploadfile:file')
     4072        upload_file = StringIO(UPLOAD_CSV_TEMPLATE % '65')
     4073        upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv')
     4074        self.browser.getControl("Update scores").click()
     4075        # value changed
     4076        self.assertEqual(
     4077            self.student['studycourse']['100']['COURSE1'].score, 65)
     4078
     4079    def test_scores_csv_upload_ignored(self):
     4080        # for many type of file contents we simply ignore uploaded data
     4081        self.login_as_lecturer()
     4082        self.student['studycourse']['100']['COURSE1'].score = 55
     4083        self.browser.open(self.edit_scores_url)
     4084        for content, mimetype, name in (
     4085                # empty file
     4086                ('', 'text/foo', 'my.foo'),
     4087                # plain ASCII text, w/o comma
     4088                ('abcdef' * 200, 'text/plain', 'my.txt'),
     4089                # plain UTF-8 text, with umlauts
     4090                ('umlauts: äöü', 'text/plain', 'my.txt'),
     4091                # csv file with only a header row
     4092                ('student_id,score', 'text/csv', 'my.csv'),
     4093                # csv with student_id column missing
     4094                ('foo,score\r\nbar,66\r\n', 'text/csv', 'my.csv'),
     4095                # csv with score column missing
     4096                ('student_id,foo\r\nK1000000,bar\r\n', 'text/csv', 'my.csv'),
     4097                # csv with non number as score value
     4098                (UPLOAD_CSV_TEMPLATE % 'not-a-number', 'text/csv', 'my.csv'),
     4099                ):
     4100            upload_ctrl = self.browser.getControl(name='uploadfile:file')
     4101            upload_ctrl.add_file(StringIO(content), mimetype, name)
     4102            self.browser.getControl("Update scores").click()
     4103            self.assertEqual(
     4104                self.student['studycourse']['100']['COURSE1'].score, 55)
     4105            self.assertFalse(
     4106                'Uploaded file contains illegal data' in self.browser.contents)
     4107
     4108    def test_scores_csv_upload_warn_illegal_chars(self):
     4109        # for some types of files we issue a warning if upload data
     4110        # contains illegal chars (and ignore the data)
     4111        self.login_as_lecturer()
     4112        self.student['studycourse']['100']['COURSE1'].score = 55
     4113        self.browser.open(self.edit_scores_url)
     4114        for content, mimetype, name in (
     4115                # plain ASCII text, commas, control chars
     4116                ('abv,qwe\n\r\r\t\b\n' * 20, 'text/plain', 'my.txt'),
     4117                # image data (like a JPEG image)
     4118                (open(SAMPLE_IMAGE, 'rb').read(), 'image/jpg', 'my.jpg'),
     4119                ):
     4120            upload_ctrl = self.browser.getControl(name='uploadfile:file')
     4121            upload_ctrl.add_file(StringIO(content), mimetype, name)
     4122            self.browser.getControl("Update scores").click()
     4123            self.assertEqual(
     4124                self.student['studycourse']['100']['COURSE1'].score, 55)
     4125            self.assertTrue(
     4126                'Uploaded file contains illegal data' in self.browser.contents)
  • main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py

    r13610 r13935  
    1818import grok
    1919from zope.component import getUtility
     20from zope.i18n import translate
    2021from zope.interface import Interface
    21 from zope.i18n import translate
    22 from waeup.kofa.interfaces import IExtFileStore, IKofaObject
    23 from waeup.kofa.interfaces import MessageFactory as _
     22from waeup.kofa.browser.layout import default_primary_nav_template
    2423from waeup.kofa.browser.viewlets import (
    2524    PrimaryNavTab, ManageActionButton, AddActionButton)
    26 from waeup.kofa.browser.layout import default_primary_nav_template
    27 from waeup.kofa.students.workflow import (
    28     ADMITTED, PAID, REQUESTED, RETURNING, CLEARED, REGISTERED,
    29     VALIDATED, GRADUATED, TRANSCRIPT)
     25from waeup.kofa.interfaces import MessageFactory as _
     26from waeup.kofa.interfaces import IExtFileStore, IKofaObject
    3027from waeup.kofa.students.browser import (
    31     StudentsContainerPage,
    32     StudentsContainerManagePage, StudentBaseDisplayFormPage,
    33     StudentClearanceDisplayFormPage, StudentPersonalDisplayFormPage,
    34     StudyCourseDisplayFormPage, StudyLevelDisplayFormPage,
    35     CourseTicketDisplayFormPage, OnlinePaymentDisplayFormPage,
    36     AccommodationManageFormPage, BedTicketDisplayFormPage,
     28    StudentsContainerPage, StudentsContainerManagePage,
     29    StudentBaseDisplayFormPage, StudentClearanceDisplayFormPage,
     30    StudentPersonalDisplayFormPage, StudyCourseDisplayFormPage,
     31    StudyLevelDisplayFormPage, CourseTicketDisplayFormPage,
     32    OnlinePaymentDisplayFormPage, BedTicketDisplayFormPage,
    3733    StudentClearanceEditFormPage, StudentPersonalEditFormPage,
    38     PaymentsManageFormPage, StudyCourseTranscriptPage)
     34    PaymentsManageFormPage, StudyCourseTranscriptPage, EditScoresPage)
    3935from waeup.kofa.students.interfaces import (
    40     IStudentsContainer, IStudent, IStudentStudyCourse, IStudentAccommodation,
    41     IStudentStudyLevel, ICourseTicket, IStudentOnlinePayment, IBedTicket,
     36    IStudentsContainer, IStudent, IStudentStudyCourse, IStudentStudyLevel,
     37    ICourseTicket, IStudentOnlinePayment, IBedTicket,
    4238    IStudentPaymentsContainer, IStudentsUtils
    4339    )
    44 
    45 grok.context(IKofaObject) # Make IKofaObject the default context
     40from waeup.kofa.students.workflow import (
     41    ADMITTED, PAID, REQUESTED, CLEARED, REGISTERED, VALIDATED, GRADUATED,
     42    TRANSCRIPT)
     43from waeup.kofa.university.interfaces import ICourse
     44
     45
     46grok.context(IKofaObject)  # Make IKofaObject the default context
    4647grok.templatedir('browser_templates')
    4748
     
    4950class StudentManageSidebar(grok.ViewletManager):
    5051    grok.name('left_studentmanage')
     52
    5153
    5254class StudentManageLink(grok.Viewlet):
     
    6971        # Here we know that the cookie has been set
    7072        lang = self.request.cookies.get('kofa.language')
    71         text = translate(self.text, 'waeup.kofa',
    72             target_language=lang)
     73        text = translate(
     74            self.text, 'waeup.kofa', target_language=lang)
    7375        if not self.link:
    7476            return ''
    7577        return u'<li><a href="%s">%s</a></li>' % (
    76                 url, text)
     78            url, text)
     79
    7780
    7881class StudentManageApplicationLink(StudentManageLink):
     
    8689        if slip:
    8790            lang = self.request.cookies.get('kofa.language')
    88             text = translate(self.text, 'waeup.kofa',
    89                 target_language=lang)
    90             url = self.view.url(self.context.student,self.link)
     91            text = translate(
     92                self.text, 'waeup.kofa', target_language=lang)
     93            url = self.view.url(self.context.student, self.link)
    9194            return u'<li><a href="%s">%s</a></li>' % (
    92                     url, text)
     95                url, text)
    9396        return ''
     97
    9498
    9599class StudentManageBaseLink(StudentManageLink):
     
    97101    link = 'index'
    98102    text = _(u'Base Data')
     103
    99104
    100105class StudentManageClearanceLink(StudentManageLink):
     
    104109    text = _(u'Clearance Data')
    105110
     111
    106112class StudentManagePersonalLink(StudentManageLink):
    107113    grok.order(4)
     
    110116    text = _(u'Personal Data')
    111117
     118
    112119class StudentManageStudyCourseLink(StudentManageLink):
    113120    grok.order(5)
    114121    link = 'studycourse'
    115122    text = _(u'Study Course')
     123
    116124
    117125class StudentManagePaymentsLink(StudentManageLink):
     
    120128    link = 'payments'
    121129    text = _(u'Payments')
     130
    122131
    123132class StudentManageAccommodationLink(StudentManageLink):
     
    128137    text = _(u'Accommodation')
    129138
     139
    130140class StudentManageHistoryLink(StudentManageLink):
    131141    grok.order(8)
     
    141151    text = _('Manage students section')
    142152
     153
    143154class StudentsContainerAddActionButton(AddActionButton):
    144155    grok.order(1)
     
    148159    text = _('Add student')
    149160    target = 'addstudent'
     161
    150162
    151163class ContactActionButton(ManageActionButton):
     
    158170    target = 'contactstudent'
    159171
     172
    160173class StudentBaseManageActionButton(ManageActionButton):
    161174    grok.order(1)
     
    165178    text = _('Manage')
    166179    target = 'manage_base'
     180
    167181
    168182class StudentTrigTransActionButton(ManageActionButton):
     
    175189    target = 'trigtrans'
    176190
     191
    177192class StudentLoginAsActionButton(ManageActionButton):
    178193    grok.order(3)
     
    184199    target = 'loginasstep1'
    185200
     201
    186202class AdmissionSlipActionButton(ManageActionButton):
    187203    grok.order(4)
     
    193209    target = 'admission_slip.pdf'
    194210
     211
    195212class StudentTransferButton(ManageActionButton):
    196213    grok.order(6)
     
    202219    icon = 'actionicon_redo.png'
    203220
     221
    204222class StudentDeactivateActionButton(ManageActionButton):
    205223    grok.order(7)
     
    222240            "'A history message will be added. Are you sure?'")
    223241
     242
    224243class StudentActivateActionButton(ManageActionButton):
    225244    grok.order(7)
     
    242261            "'A history message will be added. Are you sure?'")
    243262
     263
    244264class StudentClearanceManageActionButton(ManageActionButton):
    245265    grok.order(1)
     
    249269    text = _('Manage')
    250270    target = 'manage_clearance'
     271
    251272
    252273class StudentClearActionButton(ManageActionButton):
     
    261282    @property
    262283    def target_url(self):
    263         cdm = getUtility(IStudentsUtils).clearance_disabled_message(self.context)
     284        cdm = getUtility(
     285            IStudentsUtils).clearance_disabled_message(self.context)
    264286        if cdm:
    265287            return ''
     
    267289            return ''
    268290        return self.view.url(self.view.context, self.target)
     291
    269292
    270293class StudentRejectClearanceActionButton(ManageActionButton):
     
    279302    @property
    280303    def target_url(self):
    281         cdm = getUtility(IStudentsUtils).clearance_disabled_message(self.context)
     304        cdm = getUtility(
     305            IStudentsUtils).clearance_disabled_message(self.context)
    282306        if cdm:
    283307            return ''
     
    285309            return ''
    286310        return self.view.url(self.view.context, self.target)
     311
    287312
    288313class ClearanceSlipActionButton(ManageActionButton):
     
    295320    target = 'clearance_slip.pdf'
    296321
     322
    297323class ClearanceViewActionButton(ManageActionButton):
    298324    grok.order(1)
     
    304330    target = 'view_clearance'
    305331
     332
    306333class PersonalViewActionButton(ManageActionButton):
    307334    grok.order(1)
     
    313340    target = 'view_personal'
    314341
     342
    315343class StudentPersonalManageActionButton(ManageActionButton):
    316344    grok.order(1)
     
    321349    target = 'manage_personal'
    322350
     351
    323352class StudentPersonalEditActionButton(ManageActionButton):
    324353    grok.order(2)
     
    328357    text = _('Edit')
    329358    target = 'edit_personal'
     359
    330360
    331361class StudyCourseManageActionButton(ManageActionButton):
     
    342372            return self.view.url(self.view.context, self.target)
    343373        return False
     374
    344375
    345376class StudyCourseTranscriptActionButton(ManageActionButton):
     
    358389        return False
    359390
     391
    360392class TranscriptSlipActionButton(ManageActionButton):
    361393    grok.order(1)
     
    373405        return False
    374406
     407
    375408class RevertTransferActionButton(ManageActionButton):
    376409    grok.order(1)
     
    388421        return False
    389422
     423
    390424class StudyLevelManageActionButton(ManageActionButton):
    391425    grok.order(1)
     
    402436            return ''
    403437        return self.view.url(self.view.context, self.target)
     438
    404439
    405440class StudentValidateCoursesActionButton(ManageActionButton):
     
    414449    @property
    415450    def target_url(self):
    416         is_current = self.context.__parent__.is_current
    417         if self.context.student.state != REGISTERED or \
    418             str(self.context.__parent__.current_level) != self.context.__name__ or\
    419             not is_current:
    420             return ''
    421         return self.view.url(self.view.context, self.target)
     451        if not self.context.__parent__.is_current:
     452            return ''
     453        if self.context.student.state != REGISTERED:
     454            return ''
     455        if str(self.context.__parent__.current_level) != self.context.__name__:
     456            return ''
     457        return self.view.url(self.view.context, self.target)
     458
    422459
    423460class StudentRejectCoursesActionButton(ManageActionButton):
     
    432469    @property
    433470    def target_url(self):
    434         is_current = self.context.__parent__.is_current
    435         if self.context.student.state not in (VALIDATED, REGISTERED) or \
    436             str(self.context.__parent__.current_level) != self.context.__name__ or\
    437             not is_current:
    438             return ''
    439         return self.view.url(self.view.context, self.target)
     471        if not self.context.__parent__.is_current:
     472            return ''
     473        if self.context.student.state not in (VALIDATED, REGISTERED):
     474            return ''
     475        if str(self.context.__parent__.current_level) != self.context.__name__:
     476            return ''
     477        return self.view.url(self.view.context, self.target)
     478
    440479
    441480class StudentUnregisterCoursesActionButton(ManageActionButton):
     
    450489    @property
    451490    def target_url(self):
    452         is_current = self.context.__parent__.is_current
    453         if self.context.student.state != REGISTERED or \
    454             str(self.context.__parent__.current_level) != self.context.__name__ or\
    455             not is_current:
    456             return ''
    457         return self.view.url(self.view.context, self.target)
     491        if not self.context.__parent__.is_current:
     492            return ''
     493        if self.context.student.state != REGISTERED:
     494            return ''
     495        if str(self.context.__parent__.current_level) != self.context.__name__:
     496            return ''
     497        return self.view.url(self.view.context, self.target)
     498
    458499
    459500class CourseRegistrationSlipActionButton(ManageActionButton):
     
    473514        return self.view.url(self.view.context, self.target)
    474515
     516
    475517class CourseTicketManageActionButton(ManageActionButton):
    476518    grok.order(1)
     
    481523    target = 'manage'
    482524
    483 #class OnlinePaymentManageActionButton(ManageActionButton):
    484 #    grok.order(1)
    485 #    grok.context(IStudentPaymentsContainer)
    486 #    grok.view(PaymentsDisplayFormPage)
    487 #    grok.require('waeup.manageStudent')
    488 #    text = 'Manage payments'
    489 #    target = 'manage'
    490525
    491526class PaymentReceiptActionButton(ManageActionButton):
    492     grok.order(9) # This button should always be the last one.
     527    grok.order(9)  # This button should always be the last one.
    493528    grok.context(IStudentOnlinePayment)
    494529    grok.view(OnlinePaymentDisplayFormPage)
     
    503538        #    return ''
    504539        return self.view.url(self.view.context, self.target)
     540
    505541
    506542class ApprovePaymentActionButton(ManageActionButton):
     
    519555        return self.view.url(self.view.context, self.target)
    520556
     557
    521558class BedTicketSlipActionButton(ManageActionButton):
    522559    grok.order(1)
     
    528565    target = 'bed_allocation_slip.pdf'
    529566
     567
    530568class RelocateStudentActionButton(ManageActionButton):
    531569    grok.order(2)
     
    537575    target = 'relocate'
    538576
     577
    539578class StudentBaseActionButton(ManageActionButton):
    540579    grok.order(1)
     
    544583    text = _('Edit')
    545584    target = 'edit_base'
     585
    546586
    547587class StudentPasswordActionButton(ManageActionButton):
     
    554594    target = 'change_password'
    555595
     596
    556597class StudentPassportActionButton(ManageActionButton):
    557598    grok.order(3)
     
    565606    @property
    566607    def target_url(self):
    567         PORTRAIT_CHANGE_STATES = getUtility(IStudentsUtils).PORTRAIT_CHANGE_STATES
     608        PORTRAIT_CHANGE_STATES = getUtility(
     609            IStudentsUtils).PORTRAIT_CHANGE_STATES
    568610        if self.context.state not in PORTRAIT_CHANGE_STATES:
    569611            return ''
    570612        return self.view.url(self.view.context, self.target)
     613
    571614
    572615class StudentClearanceStartActionButton(ManageActionButton):
     
    585628        return self.view.url(self.view.context, self.target)
    586629
     630
    587631class StudentClearanceEditActionButton(ManageActionButton):
    588632    grok.order(1)
     
    598642            return ''
    599643        return self.view.url(self.view.context, self.target)
     644
    600645
    601646class StartSessionActionButton(ManageActionButton):
     
    613658            return self.view.url(self.view.context, self.target)
    614659        return False
     660
    615661
    616662class AddStudyLevelActionButton(AddActionButton):
     
    632678            return ''
    633679        return self.view.url(self.view.context, self.target)
     680
    634681
    635682class StudyLevelEditActionButton(ManageActionButton):
     
    651698        return ''
    652699
     700
    653701class AddPaymentActionButton(AddActionButton):
    654702    grok.order(1)
     
    658706    text = _('Add current session payment ticket')
    659707    target = 'addop'
     708
    660709
    661710class AddPreviousPaymentActionButton(AddActionButton):
     
    675724        return self.view.url(self.view.context, self.target)
    676725
     726
    677727class AddBalancePaymentActionButton(AddActionButton):
    678728    grok.order(3)
     
    690740        return self.view.url(self.view.context, self.target)
    691741
     742
    692743class RequestTranscriptActionButton(ManageActionButton):
    693744    grok.order(8)
     
    704755            return ''
    705756        return self.view.url(self.view.context, self.target)
     757
    706758
    707759class ProcessTranscriptRequestActionButton(ManageActionButton):
     
    720772        return self.view.url(self.view.context, self.target)
    721773
     774
    722775class StudentsTab(PrimaryNavTab):
    723776    """Students tab in primary navigation.
    724777    """
    725 
    726778    grok.context(IKofaObject)
    727779    grok.order(4)
     
    736788        return self.view.application_url('students')
    737789
     790
    738791class PrimaryStudentNavManager(grok.ViewletManager):
    739792    """Viewlet manager for the primary navigation tab.
    740793    """
    741794    grok.name('primary_nav_student')
     795
    742796
    743797class PrimaryStudentNavTab(grok.Viewlet):
     
    763817            return 'active'
    764818        return ''
     819
    765820
    766821class MyStudentDataTab(PrimaryStudentNavTab):
     
    789844        targets = []
    790845        if app_slip:
    791             targets = [{'url':student_url + '/application_slip',
    792                         'title':_('Application Slip')},]
     846            targets = [{'url': student_url + '/application_slip',
     847                        'title': _('Application Slip')}, ]
    793848        targets += [
    794             {'url':student_url, 'title':'Base Data'},
    795             {'url':student_url + '/view_clearance',
    796              'title':_('Clearance Data')},
    797             {'url':student_url + '/view_personal', 'title':_('Personal Data')},
    798             {'url':student_url + '/studycourse', 'title':_('Study Course')},
    799             {'url':student_url + '/payments', 'title':_('Payments')},
    800             {'url':student_url + '/accommodation',
    801              'title':_('Accommodation Data')},
    802             {'url':student_url + '/history', 'title':_('History')},
     849            {'url': student_url, 'title': 'Base Data'},
     850            {'url': student_url + '/view_clearance',
     851             'title': _('Clearance Data')},
     852            {'url': student_url + '/view_personal',
     853             'title': _('Personal Data')},
     854            {'url': student_url + '/studycourse', 'title': _('Study Course')},
     855            {'url': student_url + '/payments', 'title': _('Payments')},
     856            {'url': student_url + '/accommodation',
     857             'title': _('Accommodation Data')},
     858            {'url': student_url + '/history', 'title': _('History')},
    803859            ]
    804860        return targets
     861
     862
     863class DownloadCSVFileActionButton(ManageActionButton):
     864    """ 'Download csv file' button for courses.
     865    """
     866    grok.context(ICourse)
     867    grok.view(EditScoresPage)
     868    grok.name('downloadcsv')
     869    grok.require('waeup.editScores')
     870    icon = 'actionicon_down.png'
     871    text = _('Download editable tickets')
     872    target = 'download_scores'
     873    grok.order(1)
     874
     875
     876class DownloadTicketOverviewActionButton(ManageActionButton):
     877    """ 'Download ticket overview' button for courses.
     878    """
     879    grok.context(ICourse)
     880    grok.view(EditScoresPage)
     881    grok.name('coursetickets')
     882    grok.require('waeup.editScores')
     883    icon = 'actionicon_pdf.png'
     884    text = _('Download pdf file')
     885    target = 'coursetickets.pdf'
     886    grok.order(2)
Note: See TracChangeset for help on using the changeset viewer.