Ignore:
Timestamp:
24 May 2019, 09:11:40 (5 years ago)
Author:
Henrik Bettermann
Message:

Implement course result validation workflow for lecturers.

Location:
main/waeup.kofa/trunk/src/waeup/kofa
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/trunk/src/waeup/kofa/app.py

    r14671 r15422  
    8787        XXX: Tests for this method were disabled.
    8888        """
    89         for name in ['faculties', 'departments', 'certificates', 'certcourses',
     89        for name in ['faculties', 'departments', 'certificates',
     90                     'certcourses', 'courses',
    9091                     'site-pluggable-auth']:
    9192            getUtility(IKofaPluggable, name=name).update(
  • main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py

    r15287 r15422  
    4444from zope.password.interfaces import IPasswordManager
    4545from waeup.kofa.utils.helpers import html2dict
     46from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
    4647from waeup.kofa.browser.layout import (
    4748    KofaPage, KofaFormPage, KofaEditFormPage, KofaAddFormPage,
     
    24842485    pnav = 1
    24852486    form_fields = grok.AutoFields(ICourse)
     2487    form_fields[
     2488        'results_validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    24862489
    24872490    @property
     
    25052508
    25062509    form_fields = grok.AutoFields(ICourse).omit('code')
     2510    form_fields['results_validated_by'].for_display = True
     2511    form_fields['results_validation_date'].for_display = True
     2512    #form_fields['results_validation_session'].for_display = True
     2513    form_fields[
     2514        'results_validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    25072515
    25082516    @action(_('Save'), style='primary')
  • main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py

    r15163 r15422  
    3131    DepartmentPage, CoursePage, CertificatePage, CertificateCoursePage,
    3232    UsersContainerPage, UserManageFormPage, DatacenterImportStep1)
     33from waeup.kofa.students.browser import EditScoresPage
    3334from waeup.kofa.interfaces import MessageFactory as _
    3435from waeup.kofa.interfaces import (
     
    615616        return _('Update session ${a} scores', mapping = {'a':st})
    616617
     618class DownloadTicketOverviewActionButton(ManageActionButton):
     619    """ 'Download ticket overview' button for courses.
     620    """
     621    grok.context(ICourse)
     622    grok.view(CoursePage)
     623    grok.name('coursetickets')
     624    grok.require('waeup.showStudents')
     625    icon = 'actionicon_pdf.png'
     626    text = _('Download course tickets overview')
     627    target = 'coursetickets.pdf'
     628    grok.order(5)
     629
     630class DownloadCSVFileActionButton(ManageActionButton):
     631    """ 'Download csv file' button for courses.
     632    """
     633    grok.context(ICourse)
     634    grok.view(EditScoresPage)
     635    grok.name('downloadcsv')
     636    grok.require('waeup.editScores')
     637    icon = 'actionicon_down.png'
     638    text = _('Download csv file (editable scores only)')
     639    target = 'download_scores'
     640    grok.order(7)
    617641
    618642class ManageCertificateActionButton(ManageActionButton):
  • main/waeup.kofa/trunk/src/waeup/kofa/permissions.py

    r15277 r15422  
    433433    grok.title(u'Lecturer')
    434434    grok.permissions('waeup.editScores',
     435                     'waeup.showStudents',
    435436                     'waeup.viewAcademics',
    436437                     'waeup.exportData')
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py

    r15419 r15422  
    3333from zope.schema.interfaces import ConstraintNotSatisfied, RequiredMissing
    3434from zope.security import checkPermission
     35from zope.securitypolicy.interfaces import IPrincipalRoleManager
    3536from waeup.kofa.accesscodes import invalidate_accesscode, get_access_code
    3637from waeup.kofa.accesscodes.workflow import USED
     
    33793380        return
    33803381
    3381 
    33823382class EditScoresPage(KofaPage):
    33833383    """Page that allows to edit batches of scores.
     
    34643464        return True
    34653465
     3466    def _validate_results(self, form):
     3467        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
     3468        user = get_current_principal()
     3469        if user is None:
     3470            usertitle = 'system'
     3471        else:
     3472            usertitle = getattr(user, 'public_name', None)
     3473            if not usertitle:
     3474                usertitle = user.title
     3475        self.context.results_validated_by = usertitle
     3476        self.context.results_validation_date = datetime.utcnow()
     3477        self.context.results_validation_session = self.current_academic_session
     3478        return
     3479
     3480    def _results_editable(self, results_validation_session,
     3481                         current_academic_session):
     3482        user = get_current_principal()
     3483        prm = IPrincipalRoleManager(self.context)
     3484        roles = [x[0] for x in prm.getRolesForPrincipal(user.id)]
     3485        if 'waeup.local.LocalStudentsManager' in roles:
     3486            return True
     3487        if results_validation_session \
     3488            and results_validation_session >= current_academic_session:
     3489            return False
     3490        return True
     3491
    34663492    def update(self,  *args, **kw):
    34673493        form = self.request.form
     
    34763502            self.redirect(self.url(self.context))
    34773503            return
     3504        vs = self.context.results_validation_session
     3505        if not self._results_editable(vs, self.current_academic_session):
     3506            self.flash(
     3507                _('Course results have already been '
     3508                  'validated and can no longer be changed.'),
     3509                type="danger")
     3510            self.redirect(self.url(self.context))
     3511            return
    34783512        self.session_title = academic_sessions_vocab.getTerm(
    34793513            self.current_academic_session).title
     
    34853519        self.editable_tickets = [
    34863520            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
    3487         if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
     3521        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form\
     3522            and not 'VALIDATE_RESULTS' in form:
     3523            return
     3524        if 'VALIDATE_RESULTS' in form:
     3525            if vs and vs >= self.current_academic_session:
     3526                self.flash(
     3527                    _('Course results have already been validated.'),
     3528                    type="danger")
     3529                return
     3530            self._validate_results(form)
     3531            self.flash(_('You successfully validated the course results.'))
     3532            self.redirect(self.url(self.context))
    34883533            return
    34893534        if not self.editable_tickets:
     
    34943539        return
    34953540
    3496 
    34973541class DownloadScoresView(UtilityView, grok.View):
    34983542    """View that exports scores.
     
    35013545    grok.require('waeup.editScores')
    35023546    grok.name('download_scores')
     3547
     3548    def _results_editable(self, results_validation_session,
     3549                         current_academic_session):
     3550        user = get_current_principal()
     3551        prm = IPrincipalRoleManager(self.context)
     3552        roles = [x[0] for x in prm.getRolesForPrincipal(user.id)]
     3553        if 'waeup.local.LocalStudentsManager' in roles:
     3554            return True
     3555        if results_validation_session \
     3556            and results_validation_session >= current_academic_session:
     3557            return False
     3558        return True
    35033559
    35043560    def update(self):
     
    35113567        if not self.current_academic_session:
    35123568            self.flash(_('Current academic session not set.'), type="warning")
     3569            self.redirect(self.url(self.context))
     3570            return
     3571        vs = self.context.results_validation_session
     3572        if not self._results_editable(vs, self.current_academic_session):
     3573            self.flash(
     3574                _('Course results have already been '
     3575                  'validated and can no longer be changed.'),
     3576                type="danger")
    35133577            self.redirect(self.url(self.context))
    35143578            return
     
    35373601    grok.context(ICourse)
    35383602    grok.name('coursetickets.pdf')
    3539     grok.require('waeup.editScores')
     3603    grok.require('waeup.showStudents')
    35403604
    35413605    @property
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/editscorespage.pt

    r14287 r15422  
    108108         value="Update scores from table" class="btn btn-primary"
    109109         />
     110
     111  <input type="submit" name="VALIDATE_RESULTS" i18n:translate=""
     112         value="Validate course results" class="btn btn-primary"
     113         />
    110114</form>
  • main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py

    r15421 r15422  
    44584458        self.assertTrue('COURSE1 score updated (None)' in logcontent)
    44594459
     4460    def test_lecturer_can_validate_courses(self):
     4461        # the form is locked after validation
     4462        self.login_as_lecturer()
     4463        self.student['studycourse']['100']['COURSE1'].score = None
     4464        self.browser.open(self.edit_scores_url)
     4465        self.browser.getControl(name="scores:list", index=0).value = ''
     4466        self.browser.getControl("Update scores").click()
     4467        self.browser.getControl("Validate").click()
     4468        self.assertTrue(
     4469            'You successfully validated the course results'
     4470            in self.browser.contents)
     4471        self.assertEqual(self.course.results_validation_session, 2004)
     4472        self.assertEqual(self.course.results_validated_by, 'Mercedes Benz')
     4473        self.assertEqual(self.browser.url, self.course_url)
     4474        # Lecturer can't open edit_scores again
     4475        self.browser.getLink("Update session 2004/2005 scores").click()
     4476        self.assertEqual(self.browser.url, self.course_url)
     4477        self.assertTrue(
     4478            'Course results have already been validated'
     4479            ' and can no longer be changed.'
     4480            in self.browser.contents)
     4481        # Also DownloadScoresView is blocked
     4482        self.browser.open(self.browser.url + '/download_scores')
     4483        self.assertEqual(self.browser.url, self.course_url)
     4484        self.assertTrue(
     4485            'Course results have already been validated'
     4486            ' and can no longer be changed.'
     4487            in self.browser.contents)
     4488        # Students Manager can open page ...
     4489        prmlocal = IPrincipalRoleManager(self.course)
     4490        prmlocal.assignRoleToPrincipal(
     4491            'waeup.local.LocalStudentsManager', 'mrslecturer')
     4492        self.browser.getLink("Update session 2004/2005 scores").click()
     4493        self.assertEqual(self.browser.url, self.edit_scores_url)
     4494        self.browser.getLink("Download csv file").click()
     4495        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     4496        self.assertEqual(self.browser.headers['Content-Type'],
     4497                         'text/csv; charset=UTF-8')
     4498        # ... but can't validate courses a second time
     4499        self.browser.open(self.edit_scores_url)
     4500        self.browser.getControl("Validate").click()
     4501        self.assertTrue(
     4502            'Course results have already been validated.'
     4503            in self.browser.contents)
     4504
    44604505    def test_lecturers_can_download_course_tickets(self):
    44614506        # A course ticket slip can be downloaded
  • main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py

    r15175 r15422  
    3232    OnlinePaymentDisplayFormPage, BedTicketDisplayFormPage,
    3333    StudentClearanceEditFormPage, StudentPersonalEditFormPage,
    34     PaymentsManageFormPage, StudyCourseTranscriptPage, EditScoresPage)
     34    PaymentsManageFormPage, StudyCourseTranscriptPage)
    3535from waeup.kofa.students.interfaces import (
    3636    IStudentsContainer, IStudent, IStudentStudyCourse, IStudentStudyLevel,
     
    4141    ADMITTED, PAID, REQUESTED, CLEARED, REGISTERED, VALIDATED, GRADUATED,
    4242    TRANSREQ, TRANSVAL, TRANSREL)
    43 from waeup.kofa.university.interfaces import ICourse
    4443
    4544
     
    922921            ]
    923922        return targets
    924 
    925 
    926 class DownloadCSVFileActionButton(ManageActionButton):
    927     """ 'Download csv file' button for courses.
    928     """
    929     grok.context(ICourse)
    930     grok.view(EditScoresPage)
    931     grok.name('downloadcsv')
    932     grok.require('waeup.editScores')
    933     icon = 'actionicon_down.png'
    934     text = _('Download csv file (editable scores only)')
    935     target = 'download_scores'
    936     grok.order(1)
    937 
    938 
    939 class DownloadTicketOverviewActionButton(ManageActionButton):
    940     """ 'Download ticket overview' button for courses.
    941     """
    942     grok.context(ICourse)
    943     grok.view(EditScoresPage)
    944     grok.name('coursetickets')
    945     grok.require('waeup.editScores')
    946     icon = 'actionicon_pdf.png'
    947     text = _('Download pdf file')
    948     target = 'coursetickets.pdf'
    949     grok.order(2)
  • main/waeup.kofa/trunk/src/waeup/kofa/university/course.py

    r10685 r15422  
    5656        self.semester = semester
    5757        self.former_course = former_course
     58        self.results_validated_by = None
     59        self.results_validation_date = None
     60        self.results_validation_session = None
    5861
    5962    def traverse(self, name):
  • main/waeup.kofa/trunk/src/waeup/kofa/university/export.py

    r14511 r15422  
    116116
    117117    fields = ('code', 'faculty_code', 'department_code', 'title', 'credits',
    118               'passmark', 'semester', 'users_with_local_roles', 'former_course')
     118              'passmark', 'semester', 'users_with_local_roles', 'former_course',
     119              'results_validated_by', 'results_validation_date',
     120              'results_validation_session')
    119121
    120122    title = _(u'Courses')
  • main/waeup.kofa/trunk/src/waeup/kofa/university/interfaces.py

    r14638 r15422  
    2121from zope import schema
    2222from zope.interface import Attribute, invariant, Invalid
    23 from waeup.kofa.interfaces import IKofaObject, IKofaContainer, validate_id
     23from waeup.kofa.interfaces import (
     24    IKofaObject, IKofaContainer, validate_id, academic_sessions_vocab)
    2425from waeup.kofa.interfaces import MessageFactory as _
    2526from waeup.kofa.university.vocabularies import (
     
    189190        )
    190191
     192    results_validated_by = schema.TextLine(
     193        title = _(u'Results validated by'),
     194        default = None,
     195        required = False,
     196        )
     197
     198    results_validation_date = schema.Datetime(
     199        title = _(u'Results validation date'),
     200        required = False,
     201        readonly = False,
     202        )
     203
     204    results_validation_session = schema.Choice(
     205        title = _(u'Results validation session'),
     206        source = academic_sessions_vocab,
     207        required = False,
     208        readonly = False,
     209        )
    191210
    192211class ICertificate(IKofaObject):
  • main/waeup.kofa/trunk/src/waeup/kofa/university/tests/test_export.py

    r14511 r15422  
    281281            result,
    282282            'code,faculty_code,department_code,title,credits,'
    283             'passmark,semester,users_with_local_roles,former_course\r\n'
    284             'C1,F1,D1,Cheese Basics,0,40,1,[],0\r\n'
     283            'passmark,semester,users_with_local_roles,former_course,'
     284            'results_validated_by,results_validation_date,'
     285            'results_validation_session\r\n'
     286            'C1,F1,D1,Cheese Basics,0,40,1,[],0,,,\r\n'
    285287            )
    286288        return
     
    293295            result,
    294296            'code,faculty_code,department_code,title,credits,passmark,'
    295             'semester,users_with_local_roles,former_course\r\n'
    296             'C1,F1,D1,Cheese Basics,0,40,1,[],0\r\n'
    297             'C2,F1,D1,Advanced Cheese Making,0,40,1,[],0\r\n'
     297            'semester,users_with_local_roles,former_course,'
     298            'results_validated_by,results_validation_date,'
     299            'results_validation_session\r\n'
     300            'C1,F1,D1,Cheese Basics,0,40,1,[],0,,,\r\n'
     301            'C2,F1,D1,Advanced Cheese Making,0,40,1,[],0,,,\r\n'
    298302            )
    299303        return
     
    307311            result,
    308312            'code,faculty_code,department_code,title,credits,passmark,'
    309             'semester,users_with_local_roles,former_course\r\n'
    310             'C1,F1,D1,Cheese Basics,0,40,1,[],0\r\n'
    311             'C2,F1,D1,Advanced Cheese Making,0,40,1,[],0\r\n'
    312             'C3,F1,D2,Selling Cheese,0,40,1,[],0\r\n'
     313            'semester,users_with_local_roles,former_course,'
     314            'results_validated_by,results_validation_date,'
     315            'results_validation_session\r\n'
     316            'C1,F1,D1,Cheese Basics,0,40,1,[],0,,,\r\n'
     317            'C2,F1,D1,Advanced Cheese Making,0,40,1,[],0,,,\r\n'
     318            'C3,F1,D2,Selling Cheese,0,40,1,[],0,,,\r\n'
    313319            )
    314320        return
     
    321327            result,
    322328            'code,faculty_code,department_code,title,credits,passmark,'
    323             'semester,users_with_local_roles,former_course\r\n'
    324             'C1,F1,D1,Cheese Basics,0,40,1,[],0\r\n'
    325             'C2,F1,D1,Advanced Cheese Making,0,40,1,[],0\r\n'
    326             'C3,F1,D2,Selling Cheese,0,40,1,[],0\r\n'
     329            'semester,users_with_local_roles,former_course,'
     330            'results_validated_by,results_validation_date,'
     331            'results_validation_session\r\n'
     332            'C1,F1,D1,Cheese Basics,0,40,1,[],0,,,\r\n'
     333            'C2,F1,D1,Advanced Cheese Making,0,40,1,[],0,,,\r\n'
     334            'C3,F1,D2,Selling Cheese,0,40,1,[],0,,,\r\n'
    327335            )
    328336        return
Note: See TracChangeset for help on using the changeset viewer.