Changeset 15163


Ignore:
Timestamp:
23 Sep 2018, 05:05:04 (6 years ago)
Author:
Henrik Bettermann
Message:

Merge with /main/waeup.kofa/branches/henrik-transcript-workflow:15127-15162

Location:
main/waeup.kofa/trunk
Files:
1 deleted
34 edited
2 copied

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/trunk

  • main/waeup.kofa/trunk/CHANGES.txt

    r15122 r15163  
    441.6.1.dev0 (unreleased)
    55=======================
     6
     7* Implement new transcript processing workflow.
    68
    79* Always display applicant payments if they exist.
  • main/waeup.kofa/trunk/docs/source/userdocs/datacenter/export.rst

    r15051 r15163  
    282282  .. autoattribute:: waeup.kofa.students.export.SessionPaymentsOverviewExporter.fields
    283283  .. autoattribute:: waeup.kofa.students.export.SessionPaymentsOverviewExporter.title
    284   .. autoattribute:: waeup.kofa.students.export.SessionPaymentsOverviewExporter.curr_year
    285284  .. automethod:: waeup.kofa.students.export.SessionPaymentsOverviewExporter.mangle_value()
    286285
  • main/waeup.kofa/trunk/docs/source/userdocs/datacenter/import_stages.rst

    r12998 r15163  
    88   :ref:`Data Center Doctests <datacenter_txt>`
    99
    10 The term 'data import' actually understates the range of funcnctions
     10The term 'data import' actually understates the range of functions
    1111importers really have. As already stated, many importers do not only
    1212restore data once backed up by exporters or, in other words, take
  • main/waeup.kofa/trunk/docs/source/userdocs/security.rst

    r14948 r15163  
    241241   :noindex:
    242242
     243.. autoclass:: waeup.kofa.students.permissions.TranscriptOfficer()
     244   :noindex:
     245
     246.. autoclass:: waeup.kofa.students.permissions.TranscriptSignee()
     247   :noindex:
     248
    243249.. _local_roles:
    244250
     
    306312.. autoclass:: waeup.kofa.permissions.CourseAdviser100()
    307313   :noindex:
     314
     315.. autoclass:: waeup.kofa.permissions.LocalTranscriptOfficer()
     316   :noindex:
     317
     318.. autoclass:: waeup.kofa.permissions.LocalTranscriptSignee()
     319   :noindex:
  • main/waeup.kofa/trunk/docs/source/userdocs/students/workflow.rst

    r13610 r15163  
    2828
    2929              a
    30    created ------- admitted
    31               b       |
    32      +----------------+
    33      |                         e
    34      +------------------------------------------------------+
    35      |                  c                            d      |
    36    clearance started ------- clearance requested  ------- cleared
    37                                                             |
    38                                 f                           |
    39      +--------+---------------------------------------------+
    40      |        |
    41      | h    school fee paid -------- returning -------------+
    42      |        |             \   l        |                  |
    43      +--------+ g            \ _ _ _ m   | k                |
    44               |                      \   |                  |
    45            courses registered ------ courses validated      |
    46                           |      i       |                  |
    47                           |              | n                |
    48                           | j            |                  |
    49                           |          graduated              |
    50                           |              |                  |
    51                           |              | o                |
    52                           |              |                  |
    53                           |          transcript requested   |
    54      a: admit             |                                 |
    55     -a: reset1            +---------------------------------+
    56      b: start_clearance
    57     -b: reset2
    58      c: request_clearance
    59     -c: reset3 (= reject request)
    60      d: clear
    61     -d: N/A
     30    created ------- admitted
     31               b       |
     32      +----------------+
     33      |                         e
     34      +------------------------------------------------------+
     35      |                  c                            d      |
     36    clearance started ------- clearance requested  ------- cleared
     37                                                             |
     38                                 f                           |
     39      +--------+---------------------------------------------+
     40      |        |
     41      | h    school fee paid -------- returning -------------+
     42      |        |             \   l        |                  |
     43      +--------+ g            \ _ _ _ m   | k                |
     44               |                      \   |                  |
     45            courses registered ------ courses validated      |
     46                           |      i       |                  |
     47                           |              | n                |
     48                           | j            |                  |
     49                           |      +-- graduated              |
     50                           |      |       |                  |
     51                           |      |       | o                |
     52                           |      |       |                  |
     53                           |      |   transcript requested   |
     54     a: admit              |      |       |                  |
     55    -a: reset1             |    r |       | p                |
     56     b: start_clearance    |      |       |                  |
     57    -b: reset2             |      |   transcript validated   |
     58     c: request_clearance  |      |       |                  |
     59    -c: reset3             |      |       | q                |
     60        (= reject request) |      |       |                  |
     61     d: clear              |      +-- transcript released    |
     62    -d: N/A                +---------------------------------+
    6263     e: N/A
    6364    -e: reset4 (= annul clearance)
     
    8182    -n: N/A
    8283     o: request_transcript
    83     -o: process_transcript
    84 
     84    -o: reset10 (= reject request)
     85     p: validate_transcript
     86    -p: N/A
     87     q: release_transcript
     88    -q: N/A
     89     r: N/A
     90    -r: reset11
    8591
    8692Student registration starts with the first login of the student into
     
    131137list.
    132138
     139After graduation, former students (alumni) can apply for an official
     140transcript. The transcript processing workflow (**opqr**) is a
     141subset of the registration workflow which starts in state
     142``graduated``. Many people are involved in transcript processing.
     143Therefore, Kofa provides two additional roles, the Transcript
     144Officer and Transcript Signee roles. Both are available globally and
     145locally (at faculty level). First of all, the graduated student has
     146to request a transcript **(o)** by filling a request form. The
     147Transcript Officer will see the new request and can either reject
     148the request **(-o)** or validate the request **(p)**. Before
     149validation course results can be edited or added. After validation
     150the entire studycourse including its content is locked. Nobody will
     151be able to modify the course lists, neither through the UI nor by
     152import. Transcripts can now be electronically signed by Transcript
     153Signees. Once all signees have done their job, the Transcript
     154Officer can release the transcript **(q)**. A pdf file is created
     155and stored in the file system. The transcript pdf slip contains all
     156signatures. The dynamic transcript generation is disabled. Nothing
     157can be changed now, unless the entire transcript process is reset
     158**(-r)** by a Students Manager. Then comments and signatures will be
     159removed and the pdf file deleted. The student can start afresh.
     160
    133161
    134162.. _registration_workflow_batch_processing:
  • main/waeup.kofa/trunk/src/waeup/kofa

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

    r15116 r15163  
    6060    ISessionConfiguration, ISessionConfigurationAdd, IJobManager,
    6161    IPasswordValidator, IContactForm, IKofaUtils, ICSVExporter,
    62     academic_sessions_vocab, DOCLINK, registration_states_vocab)
     62    academic_sessions_vocab, DOCLINK, registration_states_vocab,
     63    TRANSREQ, TRANSVAL)
     64from waeup.kofa.university.faculty import Faculty
    6365from waeup.kofa.university.department import Department
    6466from waeup.kofa.university.course import Course
     
    375377                    self.redirect(self.application_url() + rel_link)
    376378                    return
    377                 # Redirect lecturer after login
     379                # Redirect some officers after login
    378380                local_roles = get_user_account(self.request).getLocalRoles()
    379381                if local_roles.has_key('waeup.local.Lecturer'):
    380382                    self.redirect(self.application_url() + '/my_courses')
     383                    return
     384                if local_roles.has_key('waeup.local.TranscriptOfficer'):
     385                    self.redirect(self.application_url() + '/my_transcript_requests')
     386                    return
     387                if local_roles.has_key('waeup.local.TranscriptSignee'):
     388                    self.redirect(self.application_url() + '/my_transcript_signing_requests')
    381389                    return
    382390                if not self.camefrom:
     
    10231031        return courses
    10241032
     1033class TranscriptOfficerLandingPage(KofaPage):
     1034    """Display students, who requested a transcript, when
     1035    transcript officer logs in.
     1036    """
     1037    grok.name('my_transcript_requests')
     1038    grok.require('waeup.Authenticated')
     1039    grok.context(IUniversity)
     1040    grok.template('mytranscriptrequests')
     1041    label =_('My ToDoList')
     1042
     1043    @property
     1044    def getStudents(self):
     1045        students = ([],[])
     1046        cat = queryUtility(ICatalog, name='students_catalog')
     1047        local_roles = get_user_account(self.request).getLocalRoles()
     1048        for item in local_roles.get('waeup.local.TranscriptOfficer', []):
     1049            if isinstance(item, Faculty):
     1050                faccode = item.code
     1051                results1 = cat.searchResults(
     1052                    state=(TRANSREQ, TRANSREQ), faccode=(faccode, faccode))
     1053                for result in results1:
     1054                    students[0].append(StudentQueryResultItem(result, self))
     1055                results2 = cat.searchResults(
     1056                    state=(TRANSVAL, TRANSVAL), faccode=(faccode, faccode))
     1057                for result in results2:
     1058                    students[1].append(StudentQueryResultItem(result, self))
     1059        return students
     1060
     1061class TranscriptSigneeLandingPage(KofaPage):
     1062    """Display students with validated transcript requests, when
     1063    transcript signee logs in.
     1064    """
     1065    grok.name('my_transcript_signing_requests')
     1066    grok.require('waeup.Authenticated')
     1067    grok.context(IUniversity)
     1068    grok.template('mytranscriptrequests')
     1069    label =_('My ToDoList')
     1070
     1071    @property
     1072    def getStudents(self):
     1073        students = ([],[])
     1074        cat = queryUtility(ICatalog, name='students_catalog')
     1075        local_roles = get_user_account(self.request).getLocalRoles()
     1076        for item in local_roles.get('waeup.local.TranscriptSignee', []):
     1077            if isinstance(item, Faculty):
     1078                faccode = item.code
     1079                results = cat.searchResults(
     1080                    state=(TRANSVAL, TRANSVAL), faccode=(faccode, faccode))
     1081                for result in results:
     1082                    students[1].append(StudentQueryResultItem(result, self))
     1083        return students
     1084
    10251085#
    10261086# Search pages...
  • main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py

    r15060 r15163  
    439439            'nationality,officer_comment,perm_address,personal_updated,'
    440440            'phone,reg_number,sex,student_id,suspended,suspended_comment,'
    441             'transcript_comment,'
    442441            'password,state,history,certcode,is_postgrad,current_level,'
    443442            'current_session\r\n')
  • main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py

    r14286 r15163  
    365365        return ''
    366366
     367class TranscriptOfficerStudents(ManageLink):
     368    """A link to transcript requests.
     369    """
     370    grok.order(6)
     371    grok.require('waeup.Authenticated')
     372
     373    link = u'my_transcript_requests'
     374    text = _(u'My ToDoList')
     375
     376    def render(self):
     377        user_account = get_user_account(self.request)
     378        if user_account:
     379            local_roles = user_account.getLocalRoles()
     380            if local_roles.has_key('waeup.local.TranscriptOfficer'):
     381                url = self.view.url(grok.getSite(), self.link)
     382                text = translate(self.text, context=self.request)
     383                return u'<li><a href="%s">%s</a></li>' % (url, text)
     384        return ''
     385
     386class TranscriptSigneeStudents(ManageLink):
     387    """A link to transcript signing requests.
     388    """
     389    grok.order(6)
     390    grok.require('waeup.Authenticated')
     391
     392    link = u'my_transcript_signing_requests'
     393    text = _(u'My ToDoList')
     394
     395    def render(self):
     396        user_account = get_user_account(self.request)
     397        if user_account:
     398            local_roles = user_account.getLocalRoles()
     399            if local_roles.has_key('waeup.local.TranscriptSignee'):
     400                url = self.view.url(grok.getSite(), self.link)
     401                text = translate(self.text, context=self.request)
     402                return u'<li><a href="%s">%s</a></li>' % (url, text)
     403        return ''
     404
    367405class MyPreferences(ManageLink):
    368406    """A link to personal preferences, placed in upper left box.
  • main/waeup.kofa/trunk/src/waeup/kofa/doctests/permissions.txt

    r14948 r15163  
    3939    >>> from waeup.kofa.permissions import get_waeup_roles
    4040    >>> len(list(get_waeup_roles()))
    41     28
     41    29
    4242
    4343    >>> len(list(get_waeup_roles(also_local=True)))
    44     49
     44    52
    4545
    4646
     
    7878     u'waeup.StudentsOfficer',
    7979     u'waeup.TranscriptOfficer',
     80     u'waeup.TranscriptSignee',
    8081     u'waeup.UsersManager',
    8182     u'waeup.WorkflowManager',
  • main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py

    r14699 r15163  
    5656VALIDATED = 'courses validated'
    5757GRADUATED = 'graduated'
    58 TRANSCRIPT = 'transcript requested'
     58TRANSREQ = 'transcript requested'
     59TRANSVAL = 'transcript validated'
     60TRANSREL = 'transcript released'
    5961
    6062
     
    103105    (_('returning'), RETURNING),
    104106    (_('graduated'), GRADUATED),
    105     (_('transcript requested'), TRANSCRIPT),
     107    (_('transcript requested'), TRANSREQ),
     108    (_('transcript validated'), TRANSVAL),
     109    (_('transcript released'), TRANSREL),
    106110    )
    107111
  • main/waeup.kofa/trunk/src/waeup/kofa/permissions.py

    r14992 r15163  
    155155
    156156
    157 class ViewTranscript(grok.Permission):
    158     grok.name('waeup.viewTranscript')
    159 
    160 
    161157class ManagePortalConfiguration(grok.Permission):
    162158    """The ManagePortalConfiguration permission allows to
     
    392388                     'waeup.viewAcademics',
    393389                     'waeup.exportData')
     390
     391
     392class LocalTranscriptOfficer(grok.Role):
     393    """The LocalTranscriptOfficer role can be assigned at faculty
     394    level. The role allows to view, to validate and to
     395    release student transcripts at faculty level.
     396    Local Transcript Officers get the TranscriptOfficer role
     397    for this subset of students.
     398    """
     399    grok.name('waeup.local.TranscriptOfficer')
     400    grok.title(u'Transcript Officer')
     401    grok.permissions('waeup.viewAcademics',)
     402
     403
     404class LocalTranscriptSignee(grok.Role):
     405    """The LocalTranscriptSignee role can be assigned at faculty
     406    level. The role allows to view and to sign student transcripts
     407    at faculty level. Local Transcript Signees  get the TranscriptSignee role
     408    for this subset of students.
     409    """
     410    grok.name('waeup.local.TranscriptSignee')
     411    grok.title(u'Transcript Signee')
     412    grok.permissions('waeup.viewAcademics',)
    394413
    395414
     
    552571                     'waeup.exportData',
    553572                     'waeup.viewTranscript',
     573                     'waeup.processTranscript',
    554574                     'waeup.viewDocuments', 'waeup.manageDocuments',
    555575                     'waeup.managePortalConfiguration',
     
    603623        'waeup.exportData',
    604624        'waeup.viewTranscript',
     625        'waeup.processTranscript',
    605626        'waeup.viewDocuments', 'waeup.manageDocuments',
    606627        'waeup.managePortalConfiguration', 'waeup.viewApplication',
  • main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py

    r15066 r15163  
    528528        problems.
    529529        """
     530        if obj.student.studycourse_locked:
     531            return 'Studycourse is locked.'
    530532        certificate = getattr(obj, 'certificate', None)
    531533        entry_session = getattr(obj, 'entry_session', None)
     
    619621        return
    620622
     623    def checkUpdateRequirements(self, obj, row, site):
     624        """
     625        """
     626        if obj.student.studycourse_locked:
     627            return 'Studylevel is locked.'
     628        return None
     629
    621630class CourseTicketProcessor(StudentProcessorBase):
    622631    """The Course Ticket Processor imports course tickets, the subobjects
     
    714723            del parent[ticket.code]
    715724        return
     725
     726    def checkUpdateRequirements(self, obj, row, site):
     727        """
     728        """
     729        if obj.student.studycourse_locked:
     730            return 'Studycourse is locked.'
     731        return None
    716732
    717733    def checkConversion(self, row, mode='ignore'):
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py

    r15116 r15163  
    6868    IStudentsContainer, IStudent, IUGStudentClearance, IPGStudentClearance,
    6969    IStudentPersonal, IStudentPersonalEdit, IStudentBase, IStudentStudyCourse,
    70     IStudentStudyCourseTransfer, IStudentStudyCourseTranscript,
     70    IStudentStudyCourseTransfer,
    7171    IStudentAccommodation, IStudentStudyLevel, ICourseTicket, ICourseTicketAdd,
    7272    IStudentPaymentsContainer, IStudentOnlinePayment, IStudentPreviousPayment,
     
    7878from waeup.kofa.students.workflow import (
    7979    ADMITTED, PAID, CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED,
    80     VALIDATED, GRADUATED, TRANSCRIPT, FORBIDDEN_POSTGRAD_TRANS
     80    VALIDATED, GRADUATED, TRANSREQ, TRANSVAL, TRANSREL, FORBIDDEN_POSTGRAD_TRANS
    8181    )
    8282
     
    264264        form = self.request.form
    265265        self.hitlist = []
    266         if form.get('searchtype', None) == 'suspended':
    267             self.searchtype = form['searchtype']
    268             self.searchterm = None
    269         elif form.get('searchtype', None) == 'transcript':
     266        if form.get('searchtype', None) in ('suspended', TRANSREQ, TRANSVAL):
    270267            self.searchtype = form['searchtype']
    271268            self.searchterm = None
     
    309306        form = self.request.form
    310307        self.hitlist = []
    311         if form.get('searchtype', None) == 'suspended':
     308        if form.get('searchtype', None) in ('suspended', TRANSREQ, TRANSVAL):
    312309            self.searchtype = form['searchtype']
    313310            self.searchterm = None
     
    996993
    997994    def update(self):
    998         if not self.context.is_current:
     995        if not self.context.is_current \
     996            or self.context.student.studycourse_locked:
    999997            emit_lock_message(self)
    1000998            return
     
    10961094            self.ac_series = self.request.form.get('ac_series', None)
    10971095            self.ac_number = self.request.form.get('ac_number', None)
    1098         if self.context.transcript_comment is not None:
    1099             self.correspondence = self.context.transcript_comment.replace(
    1100                 '\n', '<br>')
     1096        if getattr(
     1097            self.context['studycourse'], 'transcript_comment', None) is not None:
     1098            self.correspondence = self.context[
     1099                'studycourse'].transcript_comment.replace(
     1100                    '\n', '<br>')
    11011101        else:
    11021102            self.correspondence = ''
     
    11281128        tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc)
    11291129        today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z')
    1130         old_transcript_comment = self.context.transcript_comment
     1130        old_transcript_comment = getattr(
     1131            self.context['studycourse'], 'transcript_comment', None)
    11311132        if old_transcript_comment == None:
    11321133            old_transcript_comment = ''
    1133         self.context.transcript_comment = '''On %s %s wrote:
     1134        self.context['studycourse'].transcript_comment = '''On %s %s wrote:
    11341135
    11351136%s
     
    11461147        return
    11471148
    1148 class StudentTranscriptRequestProcessFormPage(KofaEditFormPage):
    1149     """ Page to process transcript requests
    1150     """
    1151     grok.context(IStudent)
    1152     grok.name('process_transcript_request')
    1153     grok.require('waeup.viewTranscript')
    1154     grok.template('transcriptprocess')
    1155     form_fields = grok.AutoFields(IStudentTranscript)
    1156     label = _('Process transcript request')
    1157     buttonname = _('Save comment and mark as processed')
    1158     pnav = 4
     1149class StudentTranscriptValidateView(UtilityView, grok.View):
     1150    """ View to validate transcript
     1151    """
     1152    grok.context(IStudentStudyCourse)
     1153    grok.name('validate_transcript')
     1154    grok.require('waeup.processTranscript')
    11591155
    11601156    def update(self, SUBMIT=None):
    1161         super(StudentTranscriptRequestProcessFormPage, self).update()
    1162         if self.context.state != TRANSCRIPT:
     1157        if self.context.student.state != TRANSREQ:
    11631158            self.flash(_('Student is in wrong state.'), type="warning")
    11641159            self.redirect(self.url(self.context))
    11651160            return
    1166         if self.context.transcript_comment is not None:
     1161        # Fire transition
     1162        IWorkflowInfo(self.context.student).fireTransition(
     1163            'validate_transcript')
     1164        self.flash(_('Transcript validated.'))
     1165        self.redirect(self.url(self.context) + '/transcript')
     1166        return
     1167
     1168    def render(self):
     1169        return
     1170
     1171class StudentTranscriptSignView(UtilityView, grok.View):
     1172    """ View to sign transcript
     1173    """
     1174    grok.context(IStudentStudyCourse)
     1175    grok.name('sign_transcript')
     1176    grok.require('waeup.signTranscript')
     1177
     1178    def update(self, SUBMIT=None):
     1179        if self.context.student.state != TRANSVAL:
     1180            self.flash(_('Student is in wrong state.'), type="warning")
     1181            self.redirect(self.url(self.context))
     1182            return
     1183        prev_transcript_signees = getattr(
     1184            self.context, 'transcript_signees', None)
     1185        if prev_transcript_signees \
     1186            and '(%s)' % self.request.principal.id in prev_transcript_signees:
     1187            self.flash(_('You have already signed this transcript.'),
     1188                type="warning")
     1189            self.redirect(self.url(self.context) + '/transcript')
     1190            return
     1191        self.flash(_('Transcript signed.'))
     1192        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
     1193        self.context.student.__parent__.logger.info(
     1194            '%s - %s - Transcript signed'
     1195            % (ob_class, self.context.student.student_id))
     1196        self.context.student.history.addMessage('Transcript signed')
     1197        tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc)
     1198        today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z')
     1199        if prev_transcript_signees == None:
     1200            prev_transcript_signees = ''
     1201        self.context.transcript_signees = (
     1202            u"Electronically signed by %s (%s) on %s\n%s"
     1203            % (self.request.principal.title, self.request.principal.id, today,
     1204            prev_transcript_signees))
     1205        self.redirect(self.url(self.context) + '/transcript')
     1206        return
     1207
     1208    def render(self):
     1209        return
     1210
     1211class StudentTranscriptReleaseFormPage(KofaEditFormPage):
     1212    """ Page to release transcript
     1213    """
     1214    grok.context(IStudentStudyCourse)
     1215    grok.name('release_transcript')
     1216    grok.require('waeup.processTranscript')
     1217    grok.template('transcriptrelease')
     1218    label = _('Release transcript')
     1219    buttonname = _('Save comment and release transcript')
     1220    pnav = 4
     1221
     1222    def update(self, SUBMIT=None):
     1223        super(StudentTranscriptReleaseFormPage, self).update()
     1224        if self.context.student.state != TRANSVAL:
     1225            self.flash(_('Student is in wrong state.'), type="warning")
     1226            self.redirect(self.url(self.context))
     1227            return
     1228        if getattr(self.context, 'transcript_comment', None) is not None:
    11671229            self.correspondence = self.context.transcript_comment.replace(
    11681230                '\n', '<br>')
    11691231        else:
    11701232            self.correspondence = ''
     1233        if getattr(self.context, 'transcript_signees', None) is not None:
     1234            self.signees = self.context.transcript_signees.replace(
     1235                '\n', '<br><br>')
     1236        else:
     1237            self.signees = ''
    11711238        if SUBMIT is None:
    11721239            return
    1173         IWorkflowInfo(self.context).fireTransition('process_transcript')
    1174         self.flash(_('Transcript request processed.'))
     1240        # Fire transition
     1241        IWorkflowInfo(self.context.student).fireTransition('release_transcript')
     1242        self.flash(_('Transcript released and final transcript file saved.'))
    11751243        comment = self.request.form.get('comment', '').replace('\r', '')
    11761244        tz = getattr(queryUtility(IKofaUtils), 'tzinfo', pytz.utc)
    11771245        today = now(tz).strftime('%d/%m/%Y %H:%M:%S %Z')
    1178         old_transcript_comment = self.context.transcript_comment
     1246        old_transcript_comment = getattr(
     1247            self.context, 'transcript_comment', None)
    11791248        if old_transcript_comment == None:
    11801249            old_transcript_comment = ''
     
    11871256        self.context.writeLogMessage(
    11881257            self, 'comment: %s' % comment.replace('\n', '<br>'))
    1189         subject = _('Transcript processed')
    1190         args = {'subject':subject, 'body':comment}
    1191         self.redirect(self.url(self.context) +
    1192             '/contactstudent?%s' % urlencode(args))
     1258        # Produce transcript file
     1259        self.redirect(self.url(self.context) + '/transcript.pdf')
    11931260        return
    11941261
    11951262class StudentTranscriptRequestManageFormPage(KofaEditFormPage):
    1196     """ Page to manage transcript data
    1197     """
    1198     grok.context(IStudent)
     1263    """ Page to manage transcript data. There is no link button
     1264    for this page. It's an emergency page.
     1265    """
     1266    grok.context(IStudentStudyCourse)
    11991267    grok.name('manage_transcript_request')
    1200     grok.require('waeup.manageStudent')
     1268    grok.require('waeup.processTranscript')
    12011269    form_fields = grok.AutoFields(IStudentTranscript)
    12021270    label = _('Manage transcript request')
     
    12111279    """ Page to display the student's transcript.
    12121280    """
    1213     grok.context(IStudentStudyCourseTranscript)
     1281    grok.context(IStudentStudyCourse)
    12141282    grok.name('transcript')
    12151283    grok.require('waeup.viewTranscript')
     
    12181286
    12191287    def update(self):
    1220         if not self.context.student.transcript_enabled:
    1221             self.flash(_('You are not allowed to view the transcript.'),
    1222                        type="warning")
     1288        final_slip = getUtility(IExtFileStore).getFileByContext(
     1289            self.context.student, attr='final_transcript')
     1290        if not self.context.student.transcript_enabled or final_slip:
     1291            self.flash(_('Forbidden!'), type="warning")
    12231292            self.redirect(self.url(self.context))
    12241293            return
     
    12431312    grok.name('transcript.pdf')
    12441313    grok.require('waeup.viewTranscript')
    1245     form_fields = grok.AutoFields(IStudentStudyCourseTranscript)
    12461314    prefix = 'form'
    12471315    omit_fields = (
     
    12511319
    12521320    def update(self):
    1253         if not self.context.student.transcript_enabled:
    1254             self.flash(_('You are not allowed to download the transcript.'),
    1255                        type="warning")
     1321        final_slip = getUtility(IExtFileStore).getFileByContext(
     1322            self.context.student, attr='final_transcript')
     1323        if not self.context.student.transcript_enabled \
     1324            or final_slip:
     1325            self.flash(_('Forbidden!'), type="warning")
    12561326            self.redirect(self.url(self.context))
    12571327            return
     
    12721342
    12731343    def _sigsInFooter(self):
     1344        if getattr(
     1345            self.context.student['studycourse'], 'transcript_signees', None):
     1346            return ()
    12741347        return (_('CERTIFIED TRUE COPY'),)
    12751348
    12761349    def _signatures(self):
    1277         return None
     1350        return ()
     1351
     1352    def _digital_sigs(self):
     1353        if getattr(
     1354            self.context.student['studycourse'], 'transcript_signees', None):
     1355            return self.context.student['studycourse'].transcript_signees
     1356        return ()
     1357
     1358    def _save_file(self):
     1359        if self.context.student.state == TRANSREL:
     1360            return True
     1361        return False
    12781362
    12791363    def render(self):
     
    12971381                         ]
    12981382
    1299         return students_utils.renderPDFTranscript(
     1383        pdfstream = students_utils.renderPDFTranscript(
    13001384            self, 'transcript.pdf',
    13011385            self.context.student, studentview,
     
    13041388            signatures=self._signatures(),
    13051389            sigs_in_footer=self._sigsInFooter(),
     1390            digital_sigs=self._digital_sigs(),
     1391            save_file=self._save_file(),
    13061392            )
     1393        if not pdfstream:
     1394            self.redirect(self.url(self.context.student))
     1395            return
     1396        return pdfstream
    13071397
    13081398class StudentTransferFormPage(KofaAddFormPage):
     
    14821572
    14831573    def update(self, ADD=None, course=None):
    1484         if not self.context.__parent__.is_current:
     1574        if not self.context.__parent__.is_current \
     1575            or self.context.student.studycourse_locked:
    14851576            emit_lock_message(self)
    14861577            return
     
    15541645            emit_lock_message(self)
    15551646            return
    1556         if str(self.context.__parent__.current_level) != self.context.__name__:
     1647        if str(self.context.student.current_level) != self.context.__name__:
    15571648            self.flash(_('This is not the student\'s current level.'),
    15581649                       type="danger")
     
    16501741
    16511742    def update(self):
    1652         if not self.context.__parent__.is_current:
     1743        if not self.context.__parent__.is_current \
     1744            or self.context.student.studycourse_locked:
    16531745            emit_lock_message(self)
    16541746            return
     
    17021794    pnav = 4
    17031795    grok.template('courseticketmanagepage')
     1796
     1797    def update(self):
     1798        if not self.context.__parent__.__parent__.is_current \
     1799            or self.context.student.studycourse_locked:
     1800            emit_lock_message(self)
     1801            return
     1802        super(CourseTicketManageFormPage, self).update()
     1803        return
    17041804
    17051805    @property
     
    26302730
    26312731    def update(self):
    2632         if not self.context.is_current:
     2732        if not self.context.is_current \
     2733            or self.context.student.studycourse_locked:
    26332734            emit_lock_message(self)
    26342735            return
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/containermanagepage.pt

    r13347 r15163  
    1717        with deactivated account (search term omitted)
    1818      </option>
    19       <option i18n:translate="" value="transcript">
    20         who requested transcript (search term omitted)
     19      <option i18n:translate="" value="transcript requested">
     20        in state 'transcript requested' (search term omitted)
     21      </option>
     22      <option i18n:translate="" value="transcript validated">
     23        in state 'transcript validated' (search term omitted)
    2124      </option>
    2225    </select>
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/containerpage.pt

    r11254 r15163  
    1717        with deactivated account (search term omitted)
    1818      </option>
    19       <option i18n:translate="" value="transcript">
    20         who requested transcript (search term omitted)
     19      <option i18n:translate="" value="transcript requested">
     20        in state 'transcript requested' (search term omitted)
     21      </option>
     22      <option i18n:translate="" value="transcript validated">
     23        in state 'transcript validated' (search term omitted)
    2124      </option>
    2225    </select>
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/transcript.pt

    r14382 r15163  
    5858  </tbody>
    5959</table>
     60<br />
     61<div tal:content="python:getattr(context, 'transcript_signees', '')">SIGNEES</div>
    6062</tal:def>
  • main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/transcriptrequest.pt

    r11551 r15163  
    7373
    7474    <div id="tab2" class="tab-pane">
    75       <p tal:content="structure view/correspondence"></p>
     75      <br /><p tal:content="structure view/correspondence"></p>
    7676    </div>
    7777  </div>
  • main/waeup.kofa/trunk/src/waeup/kofa/students/catalog.py

    r10465 r15163  
    2929from waeup.kofa.students.interfaces import IStudent, ICourseTicket
    3030from waeup.kofa.university.vocabularies import course_levels
     31from waeup.kofa.students.workflow import (TRANSREQ, TRANSVAL)
    3132
    3233class StudentsCatalog(grok.Indexes):
     
    8889                hitlist.append(StudentQueryResultItem(student, view=view))
    8990        return hitlist
    90     elif searchtype == 'transcript':
     91    elif searchtype in (TRANSREQ, TRANSVAL):
    9192        cat = queryUtility(ICatalog, name='students_catalog')
    92         results = cat.searchResults(
    93             state=('transcript requested', 'transcript requested'))
     93        results = cat.searchResults(state=(searchtype, searchtype))
    9494    else:
    9595        # Temporary solution to display all students added
  • main/waeup.kofa/trunk/src/waeup/kofa/students/dynamicroles.py

    r11254 r15163  
    4646        'waeup.local.LocalStudentsManager': 'waeup.StudentsManager',
    4747        'waeup.local.LocalWorkflowManager': 'waeup.WorkflowManager',
     48        'waeup.local.TranscriptOfficer': 'waeup.TranscriptOfficer',
     49        'waeup.local.TranscriptSignee': 'waeup.TranscriptSignee',
    4850        }
    4951
  • main/waeup.kofa/trunk/src/waeup/kofa/students/fileviewlets.py

    r14880 r15163  
    153153
    154154
     155class FinalTranscriptImage(StudentImage):
     156    """Renders final transcript.
     157    """
     158    grok.name('final_transcript')
     159    download_name = u'final_transcript'
     160
     161
    155162class BirthCertificateImage(StudentImage):
    156163    """Renders birth certificate scan.
  • main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py

    r14702 r15163  
    219219    transcript_enabled = Attribute('True if transcript processing is enabled')
    220220    clearance_locked = Attribute('True if clearance form is locked')
     221    studycourse_locked = Attribute(
     222        'True if nobody is allowed to change studycourse, studylecel or '
     223        'course ticket data, neither through the UI nor via import')
    221224
    222225    password = Attribute('Encrypted password')
     
    371374
    372375class IStudentTranscript(IKofaObject):
    373     """Representation of student transcript data.
     376    """Representation of student transcript data. Only used for
     377    StudentTranscriptRequestManageFormPage.
    374378    """
    375379
     
    379383        )
    380384
     385    transcript_signees = schema.Text(
     386        title = _(u'Signees'),
     387        required = False,
     388        )
     389
    381390
    382391class IStudent(IStudentBase,IUGStudentClearance,IPGStudentClearance,
    383     IStudentPersonal, IStudentTranscript):
     392    IStudentPersonal):
    384393    """Representation of a student.
    385394    """
     
    528537IStudentStudyCourseTransfer['current_level'].order = IStudentStudyCourse[
    529538    'current_level'].order
    530 
    531 class IStudentStudyCourseTranscript(IKofaObject):
    532     """An interface for student transcripts.
    533     """
    534     entry_mode = schema.Choice(
    535         title = _(u'Entry Mode'),
    536         source = StudyModeSource(),
    537         required = True,
    538         readonly = False,
    539         )
    540 
    541     entry_session = schema.Choice(
    542         title = _(u'Entry Session'),
    543         source = academic_sessions_vocab,
    544         #default = datetime.now().year,
    545         required = True,
    546         readonly = False,
    547         )
    548539
    549540class IStudentVerdictUpdate(IKofaObject):
  • main/waeup.kofa/trunk/src/waeup/kofa/students/permissions.py

    r13762 r15163  
    9292    """
    9393    grok.name('waeup.loginAsStudent')
     94
     95class ViewTranscript(grok.Permission):
     96    """The ViewTranscript role is needed to view transcript pages and slips.
     97    """
     98    grok.name('waeup.viewTranscript')
     99
     100class ProcessTranscript(grok.Permission):
     101    grok.name('waeup.processTranscript')
     102    """The ProcessTranscript role is needed to validate and relase transcripts.
     103    """
     104
     105class SignTranscript(grok.Permission):
     106    grok.name('waeup.signTranscript')
     107    """The ViewTranscript role is needed to sign transcripts.
     108    """
    94109
    95110# Local role
     
    142157
    143158class TranscriptOfficer(grok.Role):
     159    """The Transcript Officer is allowed to view, to validate and to
     160    release student transcripts. The officer is not allowed to
     161    manage student data.
     162    """
    144163    grok.name('waeup.TranscriptOfficer')
    145164    grok.title(u'Transcript Officer')
    146165    grok.permissions('waeup.viewAcademics',
    147166                     'waeup.viewTranscript',
     167                     'waeup.processTranscript',
    148168                     'waeup.viewStudent',
    149169                     'waeup.viewStudentsContainer',
     170                     )
     171
     172class TranscriptSignee(grok.Role):
     173    """The Transcript Signee is allowed to view and to sign student
     174    transcripts.
     175    """
     176    grok.name('waeup.TranscriptSignee')
     177    grok.title(u'Transcript Signee')
     178    grok.permissions('waeup.viewAcademics',
     179                     'waeup.viewTranscript',
     180                     'waeup.signTranscript',
     181                     'waeup.viewStudent',
    150182                     )
    151183
  • main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py

    r14514 r15163  
    2121              'clearance requested', 'cleared', 'school fee paid',
    2222              'courses registered', 'courses validated', 'returning',
    23               'graduated', 'transcript requested', 'Total')
     23              'graduated', 'transcript requested', 'transcript validated',
     24              'transcript released', 'Total')
    2425
    2526    def test_iface(self):
     
    4142            ((u'fac1', u'Total',),
    4243             self.states,
    43              ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
    44               (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
     44             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
     45              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
    4546        self.assertEqual(
    4647            result2,
    4748            ((u'fac1', u'Total'),
    4849             self.states,
    49              ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    50               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
     50             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     51              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
    5152        return
    5253
     
    6263            ((u'fac1', u'fac2', u'Total'),
    6364             self.states,
    64              ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
    65               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    66               (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
     65             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
     66              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     67              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
    6768        self.assertEqual(
    6869            result2,
    6970            ((u'fac1', u'fac2', u'Total'),
    7071             self.states,
    71              ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    72               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    73               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
     72             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     73              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     74              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
    7475        return
    7576
     
    8384            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
    8485             self.states,
    85              ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
    86               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    87               (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
    88         self.assertEqual(
    89             result2,
    90             ((u'fac1/dep1', u'fac2/dep2', u'Total'),
    91              self.states,
    92              ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    93               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    94               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
     86             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
     87              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     88              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
     89        self.assertEqual(
     90            result2,
     91            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
     92             self.states,
     93             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     94              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     95              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
    9596        return
    9697
     
    104105            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
    105106             self.states,
    106              ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
    107               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    108               (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
    109         self.assertEqual(
    110             result2,
    111             ((u'fac1/dep1', u'fac2/dep2', u'Total'),
    112              self.states,
    113              ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    114               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
    115               (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
     107             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
     108              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     109              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
     110        self.assertEqual(
     111            result2,
     112            ((u'fac1/dep1', u'fac2/dep2', u'Total'),
     113             self.states,
     114             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     115              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
     116              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
    116117        return
    117118
  • main/waeup.kofa/trunk/src/waeup/kofa/students/student.py

    r14169 r15163  
    3838    IKofaUtils, registration_states_vocab, IExtFileStore,
    3939    CREATED, ADMITTED, CLEARANCE, PAID, REGISTERED, VALIDATED, RETURNING,
    40     GRADUATED)
     40    GRADUATED, TRANSVAL, TRANSREL)
    4141from waeup.kofa.students.accommodation import StudentAccommodation
    4242from waeup.kofa.students.interfaces import (
     
    239239    def transcript_enabled(self):
    240240        return True
     241
     242    @property
     243    def studycourse_locked(self):
     244        return self.state in (GRADUATED, TRANSREL, TRANSVAL)
    241245
    242246    @property
  • main/waeup.kofa/trunk/src/waeup/kofa/students/studycourse.py

    r14382 r15163  
    2626from waeup.kofa.students.interfaces import (
    2727    IStudentStudyCourse, IStudentNavigation, IStudentStudyLevel,
    28     IStudentStudyCourseTranscript)
     28    IStudentTranscript)
    2929from waeup.kofa.students.studylevel import CourseTicket
    3030from waeup.kofa.students.workflow import CLEARED, RETURNING, PAID
     
    3434    """This is a container for study levels.
    3535    """
    36     grok.implements(IStudentStudyCourse, IStudentNavigation,
    37         IStudentStudyCourseTranscript)
     36    grok.implements(IStudentStudyCourse, IStudentNavigation)
    3837    grok.provides(IStudentStudyCourse)
    3938
  • main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py

    r15066 r15163  
    665665            entry_mode='transfer'), self.app)
    666666        self.assertTrue(err is None)
     667        # In state 'transcript validated' studycourses are locked.
     668        IWorkflowState(self.student).setState('transcript validated')
     669        err = self.processor.checkUpdateRequirements(
     670            self.student['studycourse'],
     671            dict(reg_number='1', current_level=999), self.app)
     672        self.assertEqual(err, 'Studycourse is locked.')
     673        # In state 'graduated' studycourses are also locked.
     674        IWorkflowState(self.student).setState('graduated')
     675        err = self.processor.checkUpdateRequirements(
     676            self.student['studycourse'],
     677            dict(reg_number='1', current_level=999), self.app)
     678        self.assertEqual(err, 'Studycourse is locked.')
     679        # In state 'transcript released' studycourses are also locked ...
     680        IWorkflowState(self.student).setState('transcript released')
     681        err = self.processor.checkUpdateRequirements(
     682            self.student['studycourse'],
     683            dict(reg_number='1', current_level=999), self.app)
     684        self.assertEqual(err, 'Studycourse is locked.')
     685        # ... but not in state 'transcript requested'.
     686        IWorkflowState(self.student).setState('transcript requested')
     687        err = self.processor.checkUpdateRequirements(
     688            self.student['studycourse'],
     689            dict(reg_number='1', current_level=999), self.app)
     690        self.assertTrue(err is None)
    667691
    668692    def test_import(self):
     
    826850        shutil.rmtree(os.path.dirname(fin_file))
    827851
     852    def test_import_update_locked(self):
     853        num, num_warns, fin_file, fail_file = self.processor.doImport(
     854            self.csv_file, STUDYLEVEL_HEADER_FIELDS,'create')
     855        shutil.rmtree(os.path.dirname(fin_file))
     856        # In state 'transcript validated' studylevels can't be edited
     857        student = self.app['students']['X666666']
     858        IWorkflowState(student).setState('transcript validated')
     859        # Two more records could't be imported because course tickets
     860        # of X666666 are locked
     861        num, num_warns, fin_file, fail_file = self.processor.doImport(
     862            self.csv_file, STUDYLEVEL_HEADER_FIELDS,'update')
     863        self.assertEqual(num_warns,5)
     864        content = open(fail_file).read()
     865        self.assertEqual(
     866            content,
     867            'reg_number,level_verdict,level_session,matric_number,level,--ERRORS--\r\n'
     868            '1,<IGNORE>,2008,<IGNORE>,100,Studylevel is locked.\r\n'
     869            '1,<IGNORE>,2008,<IGNORE>,200,Studylevel is locked.\r\n'
     870            '1,A,2008,<IGNORE>,111,level: Invalid value\r\n'
     871            '1,A,2008,<IGNORE>,nonsense,level: Invalid value\r\n'
     872            '1,A,2008,<IGNORE>,<IGNORE>,Cannot update: no such entry\r\n'
     873            )
     874        shutil.rmtree(os.path.dirname(fin_file))
     875
    828876    def test_import_remove(self):
    829877        # We perform the same import twice,
     
    9891037            'INFO - system - K1000000 - Course ticket in 100 removed: COURSE1'
    9901038            in logcontent)
     1039
     1040    def test_import_update_locked(self):
     1041        num, num_warns, fin_file, fail_file = self.processor.doImport(
     1042            self.csv_file, COURSETICKET_HEADER_FIELDS,'create')
     1043        shutil.rmtree(os.path.dirname(fin_file))
     1044        # In state 'transcript validated' course tickets can't edited
     1045        student = self.app['students']['X666666']
     1046        IWorkflowState(student).setState('transcript validated')
     1047        num, num_warns, fin_file, fail_file = self.processor.doImport(
     1048            self.csv_file, COURSETICKET_HEADER_FIELDS,'update')
     1049        fail_file = open(fail_file).read()
     1050        # Two more records could't be imported because course tickets
     1051        # of X666666 are locked
     1052        self.assertEqual(num_warns,7)
     1053        self.assertEqual(fail_file,
     1054            'reg_number,code,mandatory,level,level_session,score,matric_number,--ERRORS--\r\n'
     1055            '1,COURSE1,True,100,<IGNORE>,1,<IGNORE>,Studycourse is locked.\r\n'
     1056            '1,COURSE1,True,200,2008,1,<IGNORE>,Studycourse is locked.\r\n'
     1057            '1,COURSE1,<IGNORE>,nonsense,<IGNORE>,5,<IGNORE>,Cannot update: no such entry\r\n'
     1058            '1,NONSENSE,<IGNORE>,100,<IGNORE>,5,<IGNORE>,code: non-existent\r\n'
     1059            '1,COURSE1,<IGNORE>,200,2004,6,<IGNORE>,level_session: does not match 2008\r\n'
     1060            '1,COURSE1,<IGNORE>,300,2008,6,<IGNORE>,level object: does not exist\r\n'
     1061            '1,COURSE1,<IGNORE>,300,2008X,6,<IGNORE>,level_session: Invalid value\r\n')
     1062        shutil.rmtree(os.path.dirname(fin_file))
    9911063
    9921064class PaymentProcessorTest(StudentImportExportSetup):
  • main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py

    r15055 r15163  
    4141from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
    4242from waeup.kofa.app import University
     43from waeup.kofa.interfaces import IFileStoreNameChooser, IExtFileStore
    4344from waeup.kofa.payments.interfaces import IPayer
    4445from waeup.kofa.students.payments import StudentOnlinePayment
     
    21192120        print "Sample PDF transcript.pdf written to %s" % path
    21202121
    2121     def test_process_transcript_request(self):
     2122    def test_process_transcript(self):
    21222123        IWorkflowState(self.student).setState('transcript requested')
    21232124        notify(grok.ObjectModifiedEvent(self.student))
    2124         self.student.transcript_comment = (
     2125        self.student['studycourse'].transcript_comment = (
    21252126            u'On 07/08/2013 08:59:54 UTC K1000000 wrote:\n\nComment line 1 \n'
    21262127            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
    21272128            'Address line2\n\n')
    2128         # Create transcript officer
     2129        # Create officer with both roles
    21292130        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
    21302131        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
     
    21322133        prmglobal = IPrincipalRoleManager(self.app)
    21332134        prmglobal.assignRoleToPrincipal('waeup.TranscriptOfficer', 'mrtranscript')
    2134         # Login as transcript officer
     2135        prmglobal.assignRoleToPrincipal('waeup.StudentsManager', 'mrtranscript')
     2136        prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
     2137        # Login
    21352138        self.browser.open(self.login_path)
    21362139        self.browser.getControl(name="form.login").value = 'mrtranscript'
     
    21432146            '...<div>Transcript Officer</div>...',
    21442147            self.browser.contents)
    2145         # Officer can search for students in state 'transcripr requested'
     2148        # Officer can search for students in state 'transcript requested'
    21462149        self.browser.open(self.container_path)
    2147         self.browser.getControl(name="searchtype").value = ['transcript']
     2150        self.browser.getControl(name="searchtype").value = [
     2151            'transcript requested']
    21482152        self.browser.getControl("Find student(s)").click()
    21492153        self.assertTrue('Anna Tester' in self.browser.contents)
    21502154        self.browser.getLink("K1000000").click()
    2151         self.browser.getLink("Manage transcript request").click()
     2155        self.assertFalse('Release transcript request' in self.browser.contents)
     2156        # Officers can still edit studycourse, studylevel and course tickets.
     2157        self.browser.open(self.studycourse_path + '/manage')
     2158        self.assertTrue('Undergraduate Full-Time</option>'
     2159            in self.browser.contents)
     2160        self.browser.getControl(name="form.certificate").value = ['CERT1']
     2161        self.browser.getControl(name="form.current_session").value = ['2004']
     2162        self.browser.getControl(name="form.current_verdict").value = ['A']
     2163        self.browser.getControl(name="form.entry_mode").value = ['ug_ft']
     2164        self.browser.getControl("Save").click()
     2165        self.browser.getControl(name="form.current_level").value = ['100']
     2166        self.browser.getControl("Save").click()
     2167        self.browser.getControl(name="addlevel").value = ['100']
     2168        self.browser.getControl(name="level_session").value = ['2004']
     2169        self.browser.getControl("Add study level").click()
     2170        self.browser.getLink("100").click()
     2171        self.browser.getLink("Manage").click()
     2172        self.browser.getControl(name="form.level_session").value = ['2002']
     2173        self.browser.getControl("Save").click()
     2174        self.browser.getLink("COURSE1").click()
     2175        self.browser.getLink("Manage").click()
     2176        self.browser.getControl("Save").click()
     2177        self.assertTrue('Form has been saved' in self.browser.contents)
     2178        # Officer can validate the transcript
     2179        self.browser.open(self.studycourse_path + '/transcript')
     2180        self.browser.getLink("Validate transcript").click()
     2181        # After validation all manage forms are locked.
     2182        self.browser.open(self.studycourse_path + '/manage')
     2183        self.assertTrue('The requested form is locked' in self.browser.contents)
     2184        self.assertFalse('Undergraduate Full-Time</option>'
     2185            in self.browser.contents)
     2186        self.browser.open(self.studycourse_path + '/100/manage')
     2187        self.assertTrue('The requested form is locked' in self.browser.contents)
     2188        self.browser.open(self.studycourse_path + '/100/COURSE1/manage')
     2189        self.assertTrue('The requested form is locked' in self.browser.contents)
     2190
     2191        # Transcript can be signed if officer has the permission to sign
     2192        #self.browser.open(self.studycourse_path + '/transcript')
     2193        #self.assertFalse('Sign transcript' in self.browser.contents)
     2194        #prmglobal = IPrincipalRoleManager(self.app)
     2195        #prmglobal.assignRoleToPrincipal('waeup.TranscriptSignee', 'mrtranscript')
     2196
     2197        self.browser.open(self.studycourse_path + '/transcript')
     2198        self.browser.getLink("Sign transcript electronically").click()
     2199        # Transcript signing has been logged ...
     2200        logfile = os.path.join(
     2201            self.app['datacenter'].storage, 'logs', 'students.log')
     2202        logcontent = open(logfile).read()
     2203        self.assertTrue(
     2204            'mrtranscript - students.browser.StudentTranscriptSignView - '
     2205            'K1000000 - Transcript signed' in logcontent)
     2206        # ... appears in the student's history ...
     2207        self.browser.open(self.history_path)
     2208        self.assertTrue('Transcript signed by Ruth Gordon'
     2209            in self.browser.contents)
     2210        # ... and is also stored in the transcript_signee attribute.
     2211        self.assertTrue(
     2212            u'Electronically signed by Ruth Gordon (mrtranscript) on '
     2213            in self.student['studycourse'].transcript_signees)
     2214        # Officer can release the transcript
     2215        self.browser.open(self.studycourse_path + '/transcript')
     2216        self.browser.getLink("Release transcript").click()
    21522217        self.assertTrue(' UTC K1000000 wrote:<br><br>Comment line 1 <br>'
    21532218        'Comment line2<br><br>Dispatch Address:<br>Address line 1 <br>'
     
    21552220        self.browser.getControl(name="comment").value = (
    21562221            'Hello,\nYour transcript has been sent to the address provided.')
    2157         self.browser.getControl("Save comment and mark as processed").click()
     2222        self.browser.getControl("Save comment and release transcript").click()
    21582223        self.assertTrue(
    21592224            'UTC mrtranscript wrote:\n\nHello,\nYour transcript has '
    21602225            'been sent to the address provided.\n\n'
    2161             in self.student.transcript_comment)
     2226            in self.student['studycourse'].transcript_comment)
    21622227        # The comment has been logged
    21632228        logfile = os.path.join(
     
    21652230        logcontent = open(logfile).read()
    21662231        self.assertTrue(
    2167             'mrtranscript - students.browser.StudentTranscriptRequestProcessFormPage - '
     2232            'mrtranscript - students.browser.StudentTranscriptReleaseFormPage - '
    21682233            'K1000000 - comment: Hello,<br>'
    21692234            'Your transcript has been sent to the address provided'
    21702235            in logcontent)
     2236        # File has been stored in the file system
     2237        # Check if transcript exists in the file system and is a PDF file
     2238        storage = getUtility(IExtFileStore)
     2239        file_id = IFileStoreNameChooser(
     2240            self.student).chooseName(attr='final_transcript.pdf')
     2241        pdf = storage.getFile(file_id).read()
     2242        self.assertTrue(len(pdf) > 0)
     2243        self.assertEqual(pdf[:8], '%PDF-1.4')
     2244        # Copy the file to samples_dir
     2245        path = os.path.join(samples_dir(), 'final_transcript.pdf')
     2246        open(path, 'wb').write(pdf)
     2247        print "Sample PDF final_transcript.pdf written to %s" % path
     2248        # Check if there is an transcript pdf link in UI
     2249        self.browser.open(self.student_path)
     2250        self.assertTrue('Final Transcript' in self.browser.contents)
     2251        self.browser.getLink("Final Transcript").click()
     2252        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     2253        self.assertEqual(self.browser.headers['Content-Type'],
     2254                         'application/pdf')
     2255        # Transcript views are no longer accessible
     2256        self.browser.open(self.studycourse_path)
     2257        self.assertFalse('studycourse/transcript' in self.browser.contents)
     2258        self.browser.open(self.studycourse_path + '/transcript')
     2259        self.assertTrue('Forbidden!' in self.browser.contents)
     2260        self.browser.open(self.studycourse_path + '/transcript.pdf')
     2261        self.assertTrue('Forbidden!' in self.browser.contents)
     2262        # If we reset the transcript process
     2263        # (can't be done by transcript officer), the file will be deleted
     2264        IWorkflowInfo(self.student).fireTransition('reset11')
     2265        self.browser.open(self.student_path)
     2266        self.assertFalse('Final Transcript' in self.browser.contents)
     2267        # ... and transcript process information has been removed
     2268        self.assertEqual(self.student['studycourse'].transcript_comment, None)
     2269        self.assertEqual(self.student['studycourse'].transcript_signees, None)
     2270
     2271    def test_landingpage_transcript_officer(self):
     2272        IWorkflowState(self.student).setState('transcript requested')
     2273        notify(grok.ObjectModifiedEvent(self.student))
     2274        # Create transcript officer
     2275        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
     2276        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
     2277        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
     2278        # We assign transcript officer role at faculty level
     2279        fac = self.app['faculties']['fac1']
     2280        prmlocal = IPrincipalRoleManager(fac)
     2281        prmlocal.assignRoleToPrincipal(
     2282            'waeup.local.TranscriptOfficer', 'mrtranscript')
     2283        notify(LocalRoleSetEvent(
     2284            fac, 'waeup.local.TranscriptOfficer', 'mrtranscript', granted=True))
     2285        # Login as transcript officer
     2286        self.browser.open(self.login_path)
     2287        self.browser.getControl(name="form.login").value = 'mrtranscript'
     2288        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
     2289        self.browser.getControl("Login").click()
     2290        self.assertMatches('...You logged in...', self.browser.contents)
     2291        # Officer is on landing page and does see the transcript link
     2292        self.assertTrue(
     2293            'http://localhost/app/students/K1000000/studycourse/transcript'
     2294            in self.browser.contents)
     2295        self.browser.getLink("K1000000").click()
     2296        self.assertTrue(
     2297            'Anna Tester: Transcript Data' in self.browser.contents)
     2298        # Officer is on transcript page and can validate the transcript
     2299        self.browser.getLink("Validate transcript").click()
     2300        self.assertTrue(
     2301            '<div class="alert alert-success">Transcript validated.</div>'
     2302            in self.browser.contents)
     2303        # Officer is still on transcript page and can release the transcript
     2304        self.browser.getLink("Release transcript").click()
     2305        self.browser.getControl("Save comment and release transcript").click()
     2306        self.assertTrue(
     2307            '<div class="alert alert-success">'
     2308            'Transcript released and final transcript file saved.</div>'
     2309            in self.browser.contents)
     2310
     2311    def test_landingpage_transcript_signee(self):
     2312        IWorkflowState(self.student).setState('transcript validated')
     2313        notify(grok.ObjectModifiedEvent(self.student))
     2314        # Create transcript signee
     2315        self.app['users'].addUser('mrtranscript', 'mrtranscriptsecret')
     2316        self.app['users']['mrtranscript'].email = 'mrtranscript@foo.ng'
     2317        self.app['users']['mrtranscript'].title = 'Ruth Gordon'
     2318        # We assign transcript officer role at faculty level
     2319        fac = self.app['faculties']['fac1']
     2320        prmlocal = IPrincipalRoleManager(fac)
     2321        prmlocal.assignRoleToPrincipal(
     2322            'waeup.local.TranscriptSignee', 'mrtranscript')
     2323        notify(LocalRoleSetEvent(
     2324            fac, 'waeup.local.TranscriptSignee', 'mrtranscript', granted=True))
     2325        # Login as transcript officer
     2326        self.browser.open(self.login_path)
     2327        self.browser.getControl(name="form.login").value = 'mrtranscript'
     2328        self.browser.getControl(name="form.password").value = 'mrtranscriptsecret'
     2329        self.browser.getControl("Login").click()
     2330        self.assertMatches('...You logged in...', self.browser.contents)
     2331        # Officer is on landing page and does see the transcript link
     2332        self.assertTrue(
     2333            'http://localhost/app/students/K1000000/studycourse/transcript'
     2334            in self.browser.contents)
     2335        self.browser.getLink("K1000000").click()
     2336        self.assertTrue(
     2337            'Anna Tester: Transcript Data' in self.browser.contents)
     2338        # Officer is on transcript page and can sign the transcript
     2339        self.browser.getLink("Sign transcript").click()
     2340        self.assertTrue(
     2341            '<div class="alert alert-success">Transcript signed.</div>'
     2342            in self.browser.contents)
     2343        # Officer is still on transcript page
     2344        self.assertTrue(
     2345            'Anna Tester: Transcript Data' in self.browser.contents)
     2346        # Officer can sign the transcript only once
     2347        self.browser.getLink("Sign transcript").click()
     2348        self.assertTrue(
     2349            '<div class="alert alert-warning">'
     2350            'You have already signed this transcript.</div>'
     2351            in self.browser.contents)
     2352        # Signature can be seen on transcript page
     2353        self.assertTrue(
     2354            'Electronically signed by Ruth Gordon (mrtranscript) on'
     2355            in self.browser.contents)
     2356
    21712357
    21722358class StudentUITests(StudentsFullSetup):
     
    33033489            '... UTC K1000000 wrote:\n\nComment line 1 \n'
    33043490            'Comment line2\n\nDispatch Address:\nAddress line 1 \n'
    3305             'Address line2\n\n', self.student.transcript_comment)
     3491            'Address line2\n\n', self.student['studycourse'].transcript_comment)
    33063492        # The comment has been logged
    33073493        logfile = os.path.join(
     
    35723758            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
    35733759            'officer_comment,perm_address,personal_updated,phone,reg_number,'
    3574             'sex,student_id,suspended,suspended_comment,transcript_comment,'
    3575             'password,state,history,certcode,is_postgrad,current_level,'
    3576             'current_session\r\n'
     3760            'sex,student_id,suspended,suspended_comment,'
     3761            'password,state,history,certcode,is_postgrad,'
     3762            'current_level,current_session\r\n'
    35773763            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
    3578             '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
     3764            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
    35793765
    35803766    def test_payment_dates(self):
     
    36783864            'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
    36793865            'officer_comment,perm_address,personal_updated,phone,reg_number,'
    3680             'sex,student_id,suspended,suspended_comment,transcript_comment,'
    3681             'password,state,history,certcode,is_postgrad,current_level,'
    3682             'current_session\r\n'
     3866            'sex,student_id,suspended,suspended_comment,'
     3867            'password,state,history,certcode,is_postgrad,'
     3868            'current_level,current_session\r\n'
    36833869            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
    3684             '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
     3870            '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
    36853871        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
    36863872        job_id = self.app['datacenter'].running_exports[0][0]
  • main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py

    r15060 r15163  
    127127        'my adm code,my clr code,1981-02-04#,anna@sample.com,,'
    128128        'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n",,'
    129         '+234-123-12345#,123,f,A111111,0,,,,created'
     129        '+234-123-12345#,123,f,A111111,0,,,created'
    130130        )
    131131
     
    160160            'nationality,officer_comment,perm_address,personal_updated,'
    161161            'phone,reg_number,sex,student_id,suspended,suspended_comment,'
    162             'transcript_comment,password,state,history,certcode,is_postgrad,'
    163             'current_level,current_session\r\n'
     162            'password,state,history,'
     163            'certcode,is_postgrad,current_level,current_session\r\n'
    164164            'my adm code,my clr code,'
    165165            '1981-02-04#,anna@sample.com,,Anna,,Tester,234,M.,NG,,'
    166166            '"Studentroad 21\nLagos 123456\n",,+234-123-12345#,123,f,'
    167             'A111111,0,,,,created'
     167            'A111111,0,,,created'
    168168            in result
    169169            )
     
    182182            'nationality,officer_comment,perm_address,personal_updated,'
    183183            'phone,reg_number,sex,student_id,suspended,suspended_comment,'
    184             'transcript_comment,password,state,history,certcode,'
     184            'password,state,history,certcode,'
    185185            'is_postgrad,current_level,current_session\r\n'
    186186            'my adm code,my clr code,1981-02-04#,anna@sample.com,,'
    187187            'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n"'
    188             ',,+234-123-12345#,123,f,A111111,0,,,,created'
     188            ',,+234-123-12345#,123,f,A111111,0,,,created'
    189189            in result
    190190            )
     
    202202            'nationality,officer_comment,perm_address,personal_updated,'
    203203            'phone,reg_number,sex,student_id,suspended,suspended_comment,'
    204             'transcript_comment,password,state,history,certcode,'
     204            'password,state,history,certcode,'
    205205            'is_postgrad,current_level,current_session\r\n'
    206206            'my adm code,my clr code,1981-02-04#,anna@sample.com,,'
    207207            'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n"'
    208             ',,+234-123-12345#,123,f,A111111,0,,,,created'
     208            ',,+234-123-12345#,123,f,A111111,0,,,created'
    209209            in result
    210210            )
  • main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py

    r15102 r15163  
    2020import grok
    2121from time import time
     22from cStringIO import StringIO
    2223from reportlab.lib import colors
    2324from reportlab.lib.units import cm
     
    3334from waeup.kofa.interfaces import (
    3435    IExtFileStore, IKofaUtils, RETURNING, PAID, CLEARED,
    35     academic_sessions_vocab)
     36    academic_sessions_vocab, IFileStoreNameChooser)
    3637from waeup.kofa.interfaces import MessageFactory as _
    3738from waeup.kofa.students.interfaces import IStudentsUtils
     
    854855            level_obj.cumulative_params[0], level_obj.student)[0]
    855856
     857    def _saveTranscriptPDF(self, student, transcript):
     858        """Create a transcript PDF file and store it in student folder.
     859        """
     860        file_store = getUtility(IExtFileStore)
     861        file_id = IFileStoreNameChooser(student).chooseName(
     862            attr="final_transcript.pdf")
     863        file_store.createFile(file_id, StringIO(transcript))
     864        return
     865
    856866    def renderPDFTranscript(self, view, filename='transcript.pdf',
    857867                  student=None,
    858868                  studentview=None,
    859                   note=None, signatures=None, sigs_in_footer=(),
     869                  note=None,
     870                  signatures=(),
     871                  sigs_in_footer=(),
     872                  digital_sigs=(),
    860873                  show_scans=True, topMargin=1.5,
    861874                  omit_fields=(),
    862875                  tableheader=None,
    863                   no_passport=False):
     876                  no_passport=False,
     877                  save_file=False):
    864878        """Render pdf slip of a transcripts.
    865879        """
     
    957971                data.append(signaturetables[0])
    958972
     973        # Insert digital signatures
     974        if digital_sigs:
     975            data.append(Spacer(1, 20))
     976            sigs = digital_sigs.split('\n')
     977            for sig in sigs:
     978                data.append(Paragraph(sig, NOTE_STYLE))
     979
    959980        view.response.setHeader(
    960981            'Content-Type', 'application/pdf')
     
    966987            view.flash(_('Error in image file.'))
    967988            return view.redirect(view.url(view.context))
     989        if save_file:
     990            self._saveTranscriptPDF(student, pdf_stream)
     991            return
    968992        return pdf_stream
    969993
  • main/waeup.kofa/trunk/src/waeup/kofa/students/viewlets.py

    r14158 r15163  
    3636    IStudentsContainer, IStudent, IStudentStudyCourse, IStudentStudyLevel,
    3737    ICourseTicket, IStudentOnlinePayment, IBedTicket,
    38     IStudentPaymentsContainer, IStudentsUtils
     38    IStudentPaymentsContainer, IStudentsUtils,
    3939    )
    4040from waeup.kofa.students.workflow import (
    4141    ADMITTED, PAID, REQUESTED, CLEARED, REGISTERED, VALIDATED, GRADUATED,
    42     TRANSCRIPT)
     42    TRANSREQ, TRANSVAL, TRANSREL)
    4343from waeup.kofa.university.interfaces import ICourse
    4444
     
    9696        return ''
    9797
    98 
    9998class StudentManageBaseLink(StudentManageLink):
    10099    grok.order(2)
     
    144143
    145144
     145class StudentManageTranscriptLink(StudentManageLink):
     146    grok.order(9)
     147    link = 'final_transcript'
     148    text = _(u'Final Transcript')
     149
     150    def render(self):
     151        slip = getUtility(IExtFileStore).getFileByContext(
     152            self.context.student, attr=self.link)
     153        if slip:
     154            lang = self.request.cookies.get('kofa.language')
     155            text = translate(
     156                self.text, 'waeup.kofa', target_language=lang)
     157            url = self.view.url(self.context.student, self.link)
     158            return u'<li><a href="%s">%s</a></li>' % (
     159                url, text)
     160        return ''
     161
     162
    146163class StudentsContainerManageActionButton(ManageActionButton):
    147164    grok.order(1)
     
    370387    def target_url(self):
    371388        if self.context.is_current:
    372             return self.view.url(self.view.context, self.target)
    373         return False
    374 
    375 
    376 class StudyCourseTranscriptActionButton(ManageActionButton):
    377     grok.order(2)
    378     grok.name('transcript')
    379     grok.context(IStudentStudyCourse)
    380     grok.view(StudyCourseDisplayFormPage)
    381     grok.require('waeup.viewTranscript')
    382     text = _('Transcript')
    383     target = 'transcript'
    384     icon = 'actionicon_transcript.png'
    385 
    386     @property
    387     def target_url(self):
    388         if self.context.student.transcript_enabled:
    389             return self.view.url(self.view.context, self.target)
    390         return False
    391 
    392 
    393 class TranscriptSlipActionButton(ManageActionButton):
    394     grok.order(1)
    395     grok.name('transcript_slip')
    396     grok.context(IStudentStudyCourse)
    397     grok.view(StudyCourseTranscriptPage)
    398     grok.require('waeup.viewTranscript')
    399     text = _('Academic Transcript')
    400     target = 'transcript.pdf'
    401     icon = 'actionicon_pdf.png'
    402 
    403     @property
    404     def target_url(self):
    405         if self.context.student.transcript_enabled:
    406389            return self.view.url(self.view.context, self.target)
    407390        return False
     
    743726
    744727
     728class StudyCourseTranscriptActionButton(ManageActionButton):
     729    grok.order(2)
     730    grok.name('transcript')
     731    grok.context(IStudentStudyCourse)
     732    grok.view(StudyCourseDisplayFormPage)
     733    grok.require('waeup.viewTranscript')
     734    text = _('Transcript')
     735    target = 'transcript'
     736    icon = 'actionicon_transcript.png'
     737
     738    @property
     739    def target_url(self):
     740        final_slip = getUtility(IExtFileStore).getFileByContext(
     741            self.context.student, attr='final_transcript')
     742        if self.context.student.transcript_enabled and not final_slip:
     743            return self.view.url(self.view.context, self.target)
     744        return False
     745
     746
    745747class RequestTranscriptActionButton(ManageActionButton):
    746748    grok.order(8)
     
    759761
    760762
    761 class ProcessTranscriptRequestActionButton(ManageActionButton):
    762     grok.order(9)
    763     grok.context(IStudent)
    764     grok.view(StudentBaseDisplayFormPage)
     763class ValidateTranscriptActionButton(ManageActionButton):
     764    grok.order(8)
     765    grok.context(IStudentStudyCourse)
     766    grok.view(StudyCourseTranscriptPage)
     767    grok.require('waeup.processTranscript')
     768    text = _('Validate transcript')
     769    target = 'validate_transcript'
     770    icon = 'actionicon_transcript.png'
     771
     772    @property
     773    def target_url(self):
     774        if self.context.student.state != TRANSREQ:
     775            return ''
     776        return self.view.url(self.view.context, self.target)
     777
     778    @property
     779    def onclick(self):
     780        return "return window.confirm(%s);" % _(
     781            "'Course results cannot be changed after transcript "
     782            "validation. \\n\\n"
     783            "You really want to validate the transcript?'")
     784
     785
     786class ReleaseTranscriptActionButton(ManageActionButton):
     787    grok.order(8)
     788    grok.context(IStudentStudyCourse)
     789    grok.view(StudyCourseTranscriptPage)
     790    grok.require('waeup.processTranscript')
     791    text = _('Release transcript')
     792    target = 'release_transcript'
     793    icon = 'actionicon_transcript.png'
     794
     795    @property
     796    def target_url(self):
     797        if self.context.student.state != TRANSVAL:
     798            return ''
     799        return self.view.url(self.view.context, self.target)
     800
     801
     802class TranscriptSlipActionButton(ManageActionButton):
     803    grok.order(1)
     804    grok.name('transcript_slip')
     805    grok.context(IStudentStudyCourse)
     806    grok.view(StudyCourseTranscriptPage)
    765807    grok.require('waeup.viewTranscript')
    766     text = _('Manage transcript request')
    767     target = 'process_transcript_request'
     808    text = _('Academic Transcript')
     809    target = 'transcript.pdf'
     810    icon = 'actionicon_pdf.png'
     811
     812    @property
     813    def target_url(self):
     814        final_slip = getUtility(IExtFileStore).getFileByContext(
     815            self.context.student, attr='final_transcript')
     816        if self.context.student.transcript_enabled \
     817            and not final_slip:
     818            return self.view.url(self.view.context, self.target)
     819        return False
     820
     821
     822class SignTranscriptActionButton(ManageActionButton):
     823    grok.order(2)
     824    grok.context(IStudentStudyCourse)
     825    grok.view(StudyCourseTranscriptPage)
     826    grok.require('waeup.signTranscript')
     827    text = _('Sign transcript electronically')
     828    target = 'sign_transcript'
    768829    icon = 'actionicon_transcript.png'
    769830
    770831    @property
    771832    def target_url(self):
    772         if self.context.state != TRANSCRIPT:
    773             return ''
    774         return self.view.url(self.view.context, self.target)
     833        if self.context.student.state != TRANSVAL:
     834            return ''
     835        return self.view.url(self.view.context, self.target)
     836
     837    @property
     838    def onclick(self):
     839        return "return window.confirm(%s);" % _(
     840            "'Signing a transcript electronically cannot be revoked. "
     841            "The electronic signature replaces your handwritten signature.\\n\\n"
     842            "You really want to sign the transcript?'")
    775843
    776844
  • main/waeup.kofa/trunk/src/waeup/kofa/students/workflow.py

    r13610 r15163  
    99    IObjectHistory, IKofaWorkflowInfo, IKofaUtils,
    1010    CREATED, ADMITTED, CLEARANCE, REQUESTED, CLEARED, PAID, RETURNING,
    11     REGISTERED, VALIDATED, GRADUATED, TRANSCRIPT)
     11    REGISTERED, VALIDATED, GRADUATED, TRANSREQ, TRANSVAL, TRANSREL,
     12    IExtFileStore)
    1213from waeup.kofa.interfaces import MessageFactory as _
    1314from waeup.kofa.workflow import KofaWorkflow, KofaWorkflowInfo
     
    198199        msg = _("Transcript requested"),
    199200        source = GRADUATED,
    200         destination = TRANSCRIPT),
    201 
    202     Transition(
    203         transition_id = 'process_transcript',
    204         title = _('Transcript processed'),
    205         msg = _("Transcript processed"),
    206         source = TRANSCRIPT,
     201        destination = TRANSREQ),
     202
     203    Transition(
     204        transition_id = 'reset10',
     205        title = _('Reject transcript request'),
     206        msg = _("Transcript request rejected"),
     207        source = TRANSREQ,
     208        destination = GRADUATED),
     209
     210    Transition(
     211        transition_id = 'validate_transcript',
     212        title = _('Validate transcript'),
     213        msg = _("Transcript validated"),
     214        source = TRANSREQ,
     215        destination = TRANSVAL),
     216
     217    Transition(
     218        transition_id = 'release_transcript',
     219        title = _('Release transcript'),
     220        msg = _("Transcript released"),
     221        source = TRANSVAL,
     222        destination = TRANSREL),
     223
     224    Transition(
     225        transition_id = 'reset11',
     226        title = _('Reset to graduated'),
     227        msg = _("Transcript process reset"),
     228        source = TRANSREL,
    207229        destination = GRADUATED),
    208230    )
     
    275297            level_object.validated_by = None
    276298            level_object.validation_date = None
     299    elif event.transition.transition_id == 'reset11':
     300        transcript_file = getUtility(IExtFileStore).getFileByContext(
     301            obj, attr='final_transcript')
     302        if transcript_file:
     303            getUtility(IExtFileStore).deleteFileByContext(
     304                obj, attr='final_transcript')
     305        obj['studycourse'].transcript_comment = None
     306        obj['studycourse'].transcript_signees = None
    277307    # In some tests we don't have a students container
    278308    try:
  • main/waeup.kofa/trunk/src/waeup/kofa/university/faculty.py

    r14511 r15163  
    6363        'waeup.local.LocalStudentsManager',
    6464        'waeup.local.LocalWorkflowManager',
     65        'waeup.local.TranscriptOfficer',
     66        'waeup.local.TranscriptSignee',
    6567        ]
    6668
Note: See TracChangeset for help on using the changeset viewer.