Ignore:
Timestamp:
20 Sep 2012, 08:49:37 (12 years ago)
Author:
uli
Message:

Merge changes from update branch (includes trunk changes until r9107).

Location:
main/waeup.kofa/branches/uli-zc-async
Files:
1 deleted
29 edited
1 copied

Legend:

Unmodified
Added
Removed
  • main/waeup.kofa/branches/uli-zc-async

  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/authentication.py

    r8757 r9209  
    102102    def checkPassword(self, password):
    103103        """Check whether the given `password` matches the one stored.
     104
     105        We additionally check if student account has been suspended.
    104106        """
    105107        if not isinstance(password, basestring):
     
    107109        if not getattr(self.context, 'password', None):
    108110            # unset/empty passwords do never match
     111            return False
     112        if self.context.suspended == True:
    109113            return False
    110114        passwordmanager = getUtility(IPasswordManager, 'SSHA')
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/batching.py

    r8736 r9209  
    2626import grok
    2727import csv
     28from time import time
    2829from zope.interface import Interface
    2930from zope.schema import getFields
     
    4243    IStudentOnlinePayment, IStudentVerdictUpdate)
    4344from waeup.kofa.students.workflow import  (
    44     IMPORTABLE_STATES, IMPORTABLE_TRANSITIONS)
     45    IMPORTABLE_STATES, IMPORTABLE_TRANSITIONS,
     46    FORBIDDEN_POSTGRAD_TRANS, FORBIDDEN_POSTGRAD_STATES)
    4547from waeup.kofa.utils.batching import BatchProcessor
    4648
     
    170172            if transition not in allowed_transitions:
    171173                return 'Transition not allowed.'
     174            if transition in FORBIDDEN_POSTGRAD_TRANS and \
     175                obj.is_postgrad:
     176                return 'Transition not allowed (pg student).'
     177        state = row.get('state', IGNORE_MARKER)
     178        if state not in (IGNORE_MARKER, ''):
     179            if state in FORBIDDEN_POSTGRAD_STATES and \
     180                obj.is_postgrad:
     181                return 'State not allowed (pg student).'
    172182        return None
    173183
     
    183193
    184194        # Update password
     195        # XXX: Tale DELETION_MARKER into consideration
    185196        if row.has_key('password'):
    186197            passwd = row.get('password', IGNORE_MARKER)
     
    222233        parent = self.getParent(row, site)
    223234        if hasattr(obj,'student_id'):
    224             # Update mode: the student exists and we can get the student_id
     235            # Update mode: the student exists and we can get the student_id.
     236            # Create mode: the record contains the student_id
    225237            parent.logger.info(
    226238                '%s - Student record updated: %s'
     
    259271        errs, inv_errs, conv_dict =  converter.fromStringDict(
    260272            row, self.factory_name, mode=mode)
    261         if row.has_key('transition') and \
    262             not row['transition'] in IMPORTABLE_TRANSITIONS:
    263             if row['transition'] not in (IGNORE_MARKER, ''):
    264                 errs.append(('transition','not allowed'))
    265         if row.has_key('state') and \
    266             not row['state'] in IMPORTABLE_STATES:
    267             if row['state'] not in (IGNORE_MARKER, ''):
    268                 errs.append(('state','not allowed'))
    269             else:
    270                 # state is an attribute of Student and must not
    271                 # be changed if empty
    272                 conv_dict['state'] = IGNORE_MARKER
    273 
     273        if row.has_key('transition'):
     274            if row['transition'] not in IMPORTABLE_TRANSITIONS:
     275                if row['transition'] not in (IGNORE_MARKER, ''):
     276                    errs.append(('transition','not allowed'))
     277        if row.has_key('state'):
     278            if row['state'] not in IMPORTABLE_STATES:
     279                if row['state'] not in (IGNORE_MARKER, ''):
     280                    errs.append(('state','not allowed'))
     281                else:
     282                    # State is an attribute of Student and must not
     283                    # be changed if empty.
     284                    conv_dict['state'] = IGNORE_MARKER
    274285        try:
    275286            # Correct stud_id counter. As the IConverter for students
     
    291302    grok.baseclass()
    292303
    293     #: required fields beside 'student_id', 'reg_number' and 'matric_number'
     304    # additional available  fields
     305    # beside 'student_id', 'reg_number' and 'matric_number'
    294306    additional_fields = []
    295307
    296     #: header fields additional required
     308    #: header fields additionally required
    297309    additional_headers = []
    298310
     
    361373        return errs, inv_errs, conv_dict
    362374
     375    def getMapping(self, path, headerfields, mode):
     376        """Get a mapping from CSV file headerfields to actually used fieldnames.
     377        """
     378        result = dict()
     379        reader = csv.reader(open(path, 'rb'))
     380        raw_header = reader.next()
     381        for num, field in enumerate(headerfields):
     382            if field not in ['student_id', 'reg_number', 'matric_number',
     383                             'p_id', 'code', 'level'
     384                             ] and mode == 'remove':
     385                continue
     386            if field == u'--IGNORE--':
     387                # Skip ignored columns in failed and finished data files.
     388                continue
     389            result[raw_header[num]] = field
     390        return result
     391
    363392
    364393class StudentStudyCourseProcessor(StudentProcessorBase):
     
    406435            StudentStudyCourseProcessor, self).checkConversion(row, mode=mode)
    407436        # We have to check if current_level is in range of certificate.
    408         if conv_dict.has_key('certificate'):
    409           cert = conv_dict['certificate']
    410           if conv_dict['current_level'] < cert.start_level or \
    411               conv_dict['current_level'] > cert.end_level+120:
    412               errs.append(('current_level','not in range'))
     437        if conv_dict.has_key('certificate') and \
     438            conv_dict.has_key('current_level'):
     439            cert = conv_dict['certificate']
     440            level = conv_dict['current_level']
     441            if level < cert.start_level or level > cert.end_level+120:
     442                errs.append(('current_level','not in range'))
    413443        return errs, inv_errs, conv_dict
     444
     445    def checkUpdateRequirements(self, obj, row, site):
     446        """Checks requirements the object must fulfill when being updated.
     447
     448        Returns error messages as strings in case of requirement
     449        problems.
     450        """
     451        current_level = row.get('current_level', None)
     452        if current_level == 999 and \
     453            obj.__parent__.state in FORBIDDEN_POSTGRAD_STATES:
     454            return 'Not a pg student.'
     455        return None
    414456
    415457class StudentStudyLevelProcessor(StudentProcessorBase):
     
    427469
    428470    location_fields = []
     471
    429472    additional_fields = ['level']
    430473    additional_headers = ['level']
     
    509552        items_changed = super(CourseTicketProcessor, self).updateEntry(
    510553            obj, row, site)
     554        parent = self.getParent(row, site)
    511555        student = self.getParent(row, site).__parent__.__parent__
    512556        student.__parent__.logger.info(
    513             '%s - Course ticket updated: %s'
    514             % (student.student_id, items_changed))
     557            '%s - Course ticket in %s updated: %s'
     558            % (student.student_id,  parent.level, items_changed))
    515559        return
    516560
     
    528572        return
    529573
     574    def delEntry(self, row, site):
     575        ticket = self.getEntry(row, site)
     576        parent = self.getParent(row, site)
     577        if ticket is not None:
     578            student = self._getStudent(row, site)
     579            student.__parent__.logger.info('%s - Course ticket in %s removed: %s'
     580                % (student.student_id, parent.level, ticket.code))
     581            del parent[ticket.code]
     582        return
     583
    530584    def checkConversion(self, row, mode='ignore'):
    531585        """Validates all values in row.
     
    552606    grok.name(util_name)
    553607
    554     name = u'Payment Processor'
     608    name = u'Student Payment Processor'
    555609    iface = IStudentOnlinePayment
    556610    factory_name = 'waeup.StudentOnlinePayment'
     
    558612    location_fields = []
    559613    additional_fields = ['p_id']
    560     additional_headers = ['p_id']
     614    additional_headers = []
     615
     616    def checkHeaders(self, headerfields, mode='ignore'):
     617        super(StudentOnlinePaymentProcessor, self).checkHeaders(headerfields)
     618        if mode in ('update', 'remove') and not 'p_id' in headerfields:
     619            raise FatalCSVError(
     620                "Need p_id for import in update and remove modes!")
     621        return True
    561622
    562623    def parentsExist(self, row, site):
     
    573634        if payments is None:
    574635            return None
     636        p_id = row.get('p_id', None)
     637        if p_id is None:
     638            return None
    575639        # We can use the hash symbol at the end of p_id in import files
    576640        # to avoid annoying automatic number transformation
    577641        # by Excel or Calc
    578         p_id = row['p_id'].strip('#')
    579         if p_id.startswith('p'):
    580             entry = payments.get(p_id)
    581         else:
    582             # For data migration from old SRP
    583             entry = payments.get('p' + p_id[6:])
     642        p_id = p_id.strip('#')
     643        if not p_id.startswith('p'):
     644            # For data migration from old SRP only
     645            p_id = 'p' + p_id[7:] + '0'
     646        entry = payments.get(p_id)
    584647        return entry
    585648
     
    600663        if not p_id.startswith('p'):
    601664            # For data migration from old SRP
    602             obj.p_id = 'p' + p_id[6:]
     665            obj.p_id = 'p' + p_id[7:] + '0'
    603666            parent[obj.p_id] = obj
    604667        else:
     
    606669        return
    607670
     671    def delEntry(self, row, site):
     672        payment = self.getEntry(row, site)
     673        parent = self.getParent(row, site)
     674        if payment is not None:
     675            student = self._getStudent(row, site)
     676            student.__parent__.logger.info('%s - Payment ticket removed: %s'
     677                % (student.student_id, payment.p_id))
     678            del parent[payment.p_id]
     679        return
     680
    608681    def checkConversion(self, row, mode='ignore'):
    609682        """Validates all values in row.
     
    613686
    614687        # We have to check p_id.
    615         p_id = row['p_id'].strip('#')
     688        p_id = row.get('p_id', None)
     689        if not p_id:
     690            timestamp = ("%d" % int(time()*10000))[1:]
     691            p_id = "p%s" % timestamp
     692            conv_dict['p_id'] = p_id
     693            return errs, inv_errs, conv_dict
     694        else:
     695            p_id = p_id.strip('#')
    616696        if p_id.startswith('p'):
    617697            if not len(p_id) == 14:
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser.py

    r9166 r9209  
    4343from waeup.kofa.interfaces import (
    4444    IKofaObject, IUserAccount, IExtFileStore, IPasswordValidator, IContactForm,
    45     IKofaUtils, IUniversity)
     45    IKofaUtils, IUniversity, IObjectHistory)
    4646from waeup.kofa.interfaces import MessageFactory as _
    4747from waeup.kofa.widgets.datewidget import (
     
    5353    IUGStudentClearance,IPGStudentClearance,
    5454    IStudentPersonal, IStudentBase, IStudentStudyCourse,
     55    IStudentStudyCourseTransfer,
    5556    IStudentAccommodation, IStudentStudyLevel,
    5657    ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
    57     IStudentOnlinePayment, IBedTicket, IStudentsUtils, IStudentRequestPW
     58    IStudentOnlinePayment, IStudentPreviousPayment,
     59    IBedTicket, IStudentsUtils, IStudentRequestPW
    5860    )
    5961from waeup.kofa.students.catalog import search
    6062from waeup.kofa.students.workflow import (CREATED, ADMITTED, PAID,
    61     CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED)
     63    CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED,
     64    FORBIDDEN_POSTGRAD_TRANS)
    6265from waeup.kofa.students.studylevel import StudentStudyLevel, CourseTicket
    6366from waeup.kofa.students.vocabularies import StudyLevelSource
     
    6568from waeup.kofa.hostels.hostel import NOT_OCCUPIED
    6669from waeup.kofa.utils.helpers import get_current_principal, to_timezone
     70from waeup.kofa.mandates.mandate import PasswordMandate
    6771
    6872grok.context(IKofaObject) # Make IKofaObject the default context
     
    9195    return
    9296
     97def translated_values(view):
     98    lang = view.request.cookies.get('kofa.language')
     99    for value in view.context.values():
     100        value_dict = dict([i for i in value.__dict__.items()])
     101        value_dict['mandatory_bool'] = value.mandatory
     102        value_dict['mandatory'] = translate(str(value.mandatory), 'zope',
     103            target_language=lang)
     104        value_dict['carry_over'] = translate(str(value.carry_over), 'zope',
     105            target_language=lang)
     106        value_dict['automatic'] = translate(str(value.automatic), 'zope',
     107            target_language=lang)
     108        yield value_dict
     109
    93110class StudentsBreadcrumb(Breadcrumb):
    94111    """A breadcrumb for the students container.
     
    116133    """
    117134    grok.context(IStudentStudyCourse)
    118     title = _('Study Course')
     135
     136    def title(self):
     137        if self.context.is_current:
     138            return _('Study Course')
     139        else:
     140            return _('Previous Study Course')
    119141
    120142class PaymentsBreadcrumb(Breadcrumb):
     
    279301    grok.require('waeup.viewStudent')
    280302    grok.template('basepage')
    281     form_fields = grok.AutoFields(IStudentBase).omit('password')
     303    form_fields = grok.AutoFields(IStudentBase).omit('password', 'suspended')
    282304    pnav = 4
    283305
    284306    @property
    285307    def label(self):
    286         return _('${a}: Base Data',
     308        if self.context.suspended:
     309            return _('${a}: Base Data (account deactivated)',
     310                mapping = {'a':self.context.display_fullname})
     311        return  _('${a}: Base Data',
    287312            mapping = {'a':self.context.display_fullname})
    288313
     
    292317            return _('set')
    293318        return _('unset')
     319
     320class StudentBasePDFFormPage(KofaDisplayFormPage):
     321    """ Page to display student base data in pdf files.
     322    """
     323    form_fields = grok.AutoFields(IStudentBase).omit(
     324        'password', 'suspended', 'phone', 'adm_code', 'sex')
    294325
    295326class ContactStudentForm(ContactAdminForm):
     
    330361        return
    331362
     363class ExportPDFAdmissionSlipPage(UtilityView, grok.View):
     364    """Deliver a PDF Admission slip.
     365    """
     366    grok.context(IStudent)
     367    grok.name('admission_slip.pdf')
     368    grok.require('waeup.viewStudent')
     369    prefix = 'form'
     370
     371    form_fields = grok.AutoFields(IStudentBase).select('student_id', 'reg_number')
     372
     373    @property
     374    def label(self):
     375        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
     376        return translate(_('Admission Letter of'),
     377            'waeup.kofa', target_language=portal_language) \
     378            + ' %s' % self.context.display_fullname
     379
     380    def render(self):
     381        students_utils = getUtility(IStudentsUtils)
     382        return students_utils.renderPDFAdmissionLetter(self,
     383            self.context.student)
     384
    332385class StudentBaseManageFormPage(KofaEditFormPage):
    333386    """ View to manage student base data
     
    336389    grok.name('manage_base')
    337390    grok.require('waeup.manageStudent')
    338     form_fields = grok.AutoFields(IStudentBase).omit('student_id')
     391    form_fields = grok.AutoFields(IStudentBase).omit(
     392        'student_id', 'adm_code', 'suspended')
    339393    grok.template('basemanagepage')
    340394    label = _('Manage base data')
     
    362416        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
    363417            if not t[0].startswith('pay')]
     418        if self.context.is_postgrad:
     419            allowed_transitions = [t for t in allowed_transitions
     420                if not t[0] in FORBIDDEN_POSTGRAD_TRANS]
    364421        return [dict(name='', title=_('No transition'))] +[
    365422            dict(name=x, title=y) for x, y in allowed_transitions]
     
    396453        return
    397454
     455class StudentActivatePage(UtilityView, grok.View):
     456    """ Activate student account
     457    """
     458    grok.context(IStudent)
     459    grok.name('activate')
     460    grok.require('waeup.manageStudent')
     461
     462    def update(self):
     463        self.context.suspended = False
     464        self.context.writeLogMessage(self, 'account activated')
     465        history = IObjectHistory(self.context)
     466        history.addMessage('Student account activated')
     467        self.flash(_('Student account has been activated.'))
     468        self.redirect(self.url(self.context))
     469        return
     470
     471    def render(self):
     472        return
     473
     474class StudentDeactivatePage(UtilityView, grok.View):
     475    """ Deactivate student account
     476    """
     477    grok.context(IStudent)
     478    grok.name('deactivate')
     479    grok.require('waeup.manageStudent')
     480
     481    def update(self):
     482        self.context.suspended = True
     483        self.context.writeLogMessage(self, 'account deactivated')
     484        history = IObjectHistory(self.context)
     485        history.addMessage('Student account deactivated')
     486        self.flash(_('Student account has been deactivated.'))
     487        self.redirect(self.url(self.context))
     488        return
     489
     490    def render(self):
     491        return
     492
    398493class StudentClearanceDisplayFormPage(KofaDisplayFormPage):
    399494    """ Page to display student clearance data
     
    411506    def form_fields(self):
    412507        if self.context.is_postgrad:
    413             form_fields = grok.AutoFields(IPGStudentClearance).omit('clearance_locked')
    414         else:
    415             form_fields = grok.AutoFields(IUGStudentClearance).omit('clearance_locked')
     508            form_fields = grok.AutoFields(
     509                IPGStudentClearance).omit('clearance_locked')
     510        else:
     511            form_fields = grok.AutoFields(
     512                IUGStudentClearance).omit('clearance_locked')
    416513        return form_fields
    417514
     
    432529    def form_fields(self):
    433530        if self.context.is_postgrad:
    434             form_fields = grok.AutoFields(IPGStudentClearance).omit('clearance_locked')
    435         else:
    436             form_fields = grok.AutoFields(IUGStudentClearance).omit('clearance_locked')
     531            form_fields = grok.AutoFields(
     532                IPGStudentClearance).omit('clearance_locked')
     533        else:
     534            form_fields = grok.AutoFields(
     535                IUGStudentClearance).omit('clearance_locked')
    437536        return form_fields
    438537
     
    446545    def label(self):
    447546        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
    448         return translate(_('Clearance Slip of '),
     547        return translate(_('Clearance Slip of'),
    449548            'waeup.kofa', target_language=portal_language) \
    450549            + ' %s' % self.context.display_fullname
    451550
     551    def _signatures(self):
     552        if self.context.state == CLEARED:
     553            return (_('Student Signature'), _('Clearance Officer Signature'))
     554        return
     555
    452556    def render(self):
    453         studentview = StudentBaseDisplayFormPage(self.context.student,
     557        studentview = StudentBasePDFFormPage(self.context.student,
    454558            self.request)
    455559        students_utils = getUtility(IStudentsUtils)
    456560        return students_utils.renderPDF(
    457561            self, 'clearance.pdf',
    458             self.context.student, studentview)
     562            self.context.student, studentview, signatures=self._signatures())
    459563
    460564class StudentClearanceManageFormPage(KofaEditFormPage):
     
    475579    def form_fields(self):
    476580        if self.context.is_postgrad:
    477             form_fields = grok.AutoFields(IPGStudentClearance)
    478         else:
    479             form_fields = grok.AutoFields(IUGStudentClearance)
     581            form_fields = grok.AutoFields(IPGStudentClearance).omit('clr_code')
     582        else:
     583            form_fields = grok.AutoFields(IUGStudentClearance).omit('clr_code')
    480584        return form_fields
    481585
     
    557661            mapping = {'a':self.context.display_fullname})
    558662
    559 class StudentPersonalEditFormPage(KofaEditFormPage):
    560     """ Page to edit personal data
     663class StudentPersonalManageFormPage(KofaEditFormPage):
     664    """ Page to manage personal data
    561665    """
    562666    grok.context(IStudent)
    563     grok.name('edit_personal')
    564     grok.require('waeup.handleStudent')
     667    grok.name('manage_personal')
     668    grok.require('waeup.manageStudent')
    565669    form_fields = grok.AutoFields(IStudentPersonal)
    566     label = _('Edit personal data')
     670    label = _('Manage personal data')
    567671    pnav = 4
    568672
     
    572676        return
    573677
     678class StudentPersonalEditFormPage(StudentPersonalManageFormPage):
     679    """ Page to edit personal data
     680    """
     681    grok.name('edit_personal')
     682    grok.require('waeup.handleStudent')
     683    label = _('Edit personal data')
     684    pnav = 4
     685
    574686class StudyCourseDisplayFormPage(KofaDisplayFormPage):
    575687    """ Page to display the student study course data
     
    578690    grok.name('index')
    579691    grok.require('waeup.viewStudent')
    580     form_fields = grok.AutoFields(IStudentStudyCourse)
    581692    grok.template('studycoursepage')
    582693    pnav = 4
    583694
    584695    @property
     696    def form_fields(self):
     697        if self.context.is_postgrad:
     698            form_fields = grok.AutoFields(IStudentStudyCourse).omit(
     699                'current_verdict', 'previous_verdict')
     700        else:
     701            form_fields = grok.AutoFields(IStudentStudyCourse)
     702        return form_fields
     703
     704    @property
    585705    def label(self):
    586         return _('${a}: Study Course',
    587             mapping = {'a':self.context.__parent__.display_fullname})
     706        if self.context.is_current:
     707            return _('${a}: Study Course',
     708                mapping = {'a':self.context.__parent__.display_fullname})
     709        else:
     710            return _('${a}: Previous Study Course',
     711                mapping = {'a':self.context.__parent__.display_fullname})
    588712
    589713    @property
     
    606730        return
    607731
     732    @property
     733    def prev_studycourses(self):
     734        if self.context.is_current:
     735            if self.context.__parent__.get('studycourse_2', None) is not None:
     736                return (
     737                        {'href':self.url(self.context.student) + '/studycourse_1',
     738                        'title':_('First Study Course, ')},
     739                        {'href':self.url(self.context.student) + '/studycourse_2',
     740                        'title':_('Second Study Course')}
     741                        )
     742            if self.context.__parent__.get('studycourse_1', None) is not None:
     743                return (
     744                        {'href':self.url(self.context.student) + '/studycourse_1',
     745                        'title':_('First Study Course')},
     746                        )
     747        return
     748
    608749class StudyCourseManageFormPage(KofaEditFormPage):
    609750    """ Page to edit the student study course data
     
    613754    grok.require('waeup.manageStudent')
    614755    grok.template('studycoursemanagepage')
    615     form_fields = grok.AutoFields(IStudentStudyCourse)
    616756    label = _('Manage study course')
    617757    pnav = 4
     
    620760    tabthreeactions = [_('Add study level')]
    621761
     762    @property
     763    def form_fields(self):
     764        if self.context.is_postgrad:
     765            form_fields = grok.AutoFields(IStudentStudyCourse).omit(
     766                'current_verdict', 'previous_verdict')
     767        else:
     768            form_fields = grok.AutoFields(IStudentStudyCourse)
     769        return form_fields
     770
    622771    def update(self):
     772        if not self.context.is_current:
     773            emit_lock_message(self)
     774            return
    623775        super(StudyCourseManageFormPage, self).update()
    624776        tabs.need()
     
    685837        return
    686838
     839class StudentTransferFormPage(KofaAddFormPage):
     840    """Page to transfer the student.
     841    """
     842    grok.context(IStudent)
     843    grok.name('transfer')
     844    grok.require('waeup.manageStudent')
     845    label = _('Transfer student')
     846    form_fields = grok.AutoFields(IStudentStudyCourseTransfer).omit(
     847        'entry_mode', 'entry_session')
     848    pnav = 4
     849
     850    def update(self):
     851        super(StudentTransferFormPage, self).update()
     852        warning.need()
     853        return
     854
     855    @jsaction(_('Transfer'))
     856    def transferStudent(self, **data):
     857        error = self.context.transfer(**data)
     858        if error == -1:
     859            self.flash(_('Current level does not match certificate levels.'))
     860        elif error == -2:
     861            self.flash(_('Former study course record incomplete.'))
     862        elif error == -3:
     863            self.flash(_('Maximum number of transfers exceeded.'))
     864        else:
     865            self.flash(_('Successfully transferred.'))
     866        return
     867
    687868class StudyLevelDisplayFormPage(KofaDisplayFormPage):
    688869    """ Page to display student study levels
     
    692873    grok.require('waeup.viewStudent')
    693874    form_fields = grok.AutoFields(IStudentStudyLevel)
     875    form_fields[
     876        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    694877    grok.template('studylevelpage')
    695878    pnav = 4
     
    702885    @property
    703886    def translated_values(self):
    704         lang = self.request.cookies.get('kofa.language')
    705         for value in self.context.values():
    706             value_dict = dict([i for i in value.__dict__.items()])
    707             value_dict['mandatory'] = translate(str(value.mandatory), 'zope',
    708                 target_language=lang)
    709             value_dict['carry_over'] = translate(str(value.carry_over), 'zope',
    710                 target_language=lang)
    711             value_dict['automatic'] = translate(str(value.automatic), 'zope',
    712                 target_language=lang)
    713             yield value_dict
     887        return translated_values(self)
    714888
    715889    @property
     
    771945        Mand = translate(_('Mand.'), 'waeup.kofa', target_language=portal_language)
    772946        Score = translate(_('Score'), 'waeup.kofa', target_language=portal_language)
    773         studentview = StudentBaseDisplayFormPage(self.context.student,
     947        studentview = StudentBasePDFFormPage(self.context.student,
    774948            self.request)
    775949        students_utils = getUtility(IStudentsUtils)
     
    796970    grok.require('waeup.manageStudent')
    797971    grok.template('studylevelmanagepage')
    798     form_fields = grok.AutoFields(IStudentStudyLevel)
     972    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
     973        'validation_date', 'validated_by')
    799974    pnav = 4
    800975    taboneactions = [_('Save'),_('Cancel')]
     
    803978
    804979    def update(self):
     980        if not self.context.__parent__.is_current:
     981            emit_lock_message(self)
     982            return
    805983        super(StudyLevelManageFormPage, self).update()
    806984        tabs.need()
     
    813991        datatable.need()
    814992        return
     993
     994    @property
     995    def translated_values(self):
     996        return translated_values(self)
    815997
    816998    @property
     
    8611043
    8621044    def update(self):
     1045        if not self.context.__parent__.is_current:
     1046            emit_lock_message(self)
     1047            return
    8631048        if str(self.context.__parent__.current_level) != self.context.__name__:
    8641049            self.flash(_('This level does not correspond current level.'))
     
    8831068
    8841069    def update(self):
     1070        if not self.context.__parent__.is_current:
     1071            emit_lock_message(self)
     1072            return
    8851073        if str(self.context.__parent__.current_level) != self.context.__name__:
    8861074            self.flash(_('This level does not correspond current level.'))
     
    9181106    pnav = 4
    9191107
     1108    def update(self):
     1109        if not self.context.__parent__.is_current:
     1110            emit_lock_message(self)
     1111            return
     1112        super(CourseTicketAddFormPage, self).update()
     1113        return
     1114
    9201115    @action(_('Add course ticket'))
    9211116    def addCourseTicket(self, **data):
     
    9241119        ticket.automatic = False
    9251120        ticket.carry_over = False
    926         ticket.code = course.code
    927         ticket.title = course.title
    928         ticket.fcode = course.__parent__.__parent__.__parent__.code
    929         ticket.dcode = course.__parent__.__parent__.code
    930         ticket.credits = course.credits
    931         ticket.passmark = course.passmark
    932         ticket.semester = course.semester
    9331121        try:
    934             self.context.addCourseTicket(ticket)
     1122            self.context.addCourseTicket(ticket, course)
    9351123        except KeyError:
    9361124            self.flash(_('The ticket exists.'))
     
    10301218                mapping = {'a': ', '.join(deleted)}))
    10311219            self.context.writeLogMessage(
    1032                 self,'removed: % s' % ', '.join(deleted))
     1220                self,'removed: %s' % ', '.join(deleted))
    10331221        self.redirect(self.url(self.context))
    10341222        return
     
    10521240    def createTicket(self, **data):
    10531241        p_category = data['p_category']
     1242        previous_session = data.get('p_session', None)
     1243        previous_level = data.get('p_level', None)
    10541244        student = self.context.__parent__
    10551245        if p_category == 'bed_allocation' and student[
     
    10621252                return
    10631253        students_utils = getUtility(IStudentsUtils)
    1064         error, payment = students_utils.setPaymentDetails(p_category, student)
     1254        error, payment = students_utils.setPaymentDetails(
     1255            p_category, student, previous_session, previous_level)
    10651256        if error is not None:
    10661257            self.flash(error)
     1258            if 'previous session' in error:
     1259                self.redirect(self.url(self.context) + '/@@addpp')
     1260                return
    10671261            self.redirect(self.url(self.context))
    10681262            return
     
    10711265        self.redirect(self.url(self.context))
    10721266        return
     1267
     1268class PreviousPaymentAddFormPage(OnlinePaymentAddFormPage):
     1269    """ Page to add an online payment ticket for previous sessions
     1270    """
     1271    grok.context(IStudentPaymentsContainer)
     1272    grok.name('addpp')
     1273    grok.require('waeup.payStudent')
     1274    form_fields = grok.AutoFields(IStudentPreviousPayment).select(
     1275        'p_category', 'p_session', 'p_level')
     1276    label = _('Add previous session online payment')
     1277    pnav = 4
    10731278
    10741279class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
     
    11491354        #    self.redirect(self.url(self.context))
    11501355        #    return
    1151         studentview = StudentBaseDisplayFormPage(self.context.student,
     1356        studentview = StudentBasePDFFormPage(self.context.student,
    11521357            self.request)
    11531358        students_utils = getUtility(IStudentsUtils)
     
    12261431    buttonname = _('Create bed ticket')
    12271432    notice = ''
     1433    with_ac = True
    12281434
    12291435    def update(self, SUBMIT=None):
     
    12661472            self.redirect(self.url(self.context))
    12671473            return
    1268         self.ac_series = self.request.form.get('ac_series', None)
    1269         self.ac_number = self.request.form.get('ac_number', None)
     1474        if self.with_ac:
     1475            self.ac_series = self.request.form.get('ac_series', None)
     1476            self.ac_number = self.request.form.get('ac_number', None)
    12701477        if SUBMIT is None:
    12711478            return
    1272         pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
    1273         code = get_access_code(pin)
    1274         if not code:
    1275             self.flash(_('Activation code is invalid.'))
    1276             return
     1479        if self.with_ac:
     1480            pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
     1481            code = get_access_code(pin)
     1482            if not code:
     1483                self.flash(_('Activation code is invalid.'))
     1484                return
    12771485        # Search and book bed
    12781486        cat = queryUtility(ICatalog, name='beds_catalog', default=None)
     
    12801488            owner=(student.student_id,student.student_id))
    12811489        if len(entries):
    1282             # If bed space has bee manually allocated use this bed
     1490            # If bed space has been manually allocated use this bed
    12831491            bed = [entry for entry in entries][0]
    12841492        else:
     
    12961504                    mapping = {'a':acc_details['bt']}))
    12971505                return
    1298         # Mark pin as used (this also fires a pin related transition)
    1299         if code.state == USED:
    1300             self.flash(_('Activation code has already been used.'))
    1301             return
    1302         else:
    1303             comment = _(u'invalidated')
    1304             # Here we know that the ac is in state initialized so we do not
    1305             # expect an exception, but the owner might be different
    1306             if not invalidate_accesscode(
    1307                 pin,comment,self.context.student.student_id):
    1308                 self.flash(_('You are not the owner of this access code.'))
     1506        if self.with_ac:
     1507            # Mark pin as used (this also fires a pin related transition)
     1508            if code.state == USED:
     1509                self.flash(_('Activation code has already been used.'))
    13091510                return
     1511            else:
     1512                comment = _(u'invalidated')
     1513                # Here we know that the ac is in state initialized so we do not
     1514                # expect an exception, but the owner might be different
     1515                if not invalidate_accesscode(
     1516                    pin,comment,self.context.student.student_id):
     1517                    self.flash(_('You are not the owner of this access code.'))
     1518                    return
    13101519        # Create bed ticket
    13111520        bedticket = createObject(u'waeup.BedTicket')
    1312         bedticket.booking_code = pin
     1521        if self.with_ac:
     1522            bedticket.booking_code = pin
    13131523        bedticket.booking_session = acc_details['booking_session']
    13141524        bedticket.bed_type = acc_details['bt']
    13151525        bedticket.bed = bed
    13161526        hall_title = bed.__parent__.hostel_name
    1317         coordinates = bed.getBedCoordinates()[1:]
     1527        coordinates = bed.coordinates[1:]
    13181528        block, room_nr, bed_nr = coordinates
    13191529        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
     
    13381548    grok.require('waeup.handleAccommodation')
    13391549    form_fields = grok.AutoFields(IBedTicket)
     1550    form_fields['booking_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    13401551    pnav = 4
    13411552
     
    13641575    def label(self):
    13651576        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
    1366         return translate(_('Bed Allocation: '),
     1577        #return translate(_('Bed Allocation: '),
     1578        #    'waeup.kofa', target_language=portal_language) \
     1579        #    + ' %s' % self.context.bed_coordinates
     1580        return translate(_('Bed Allocation Slip'),
    13671581            'waeup.kofa', target_language=portal_language) \
    1368             + ' %s' % self.context.bed_coordinates
     1582            + ' %s' % self.context.getSessionString()
    13691583
    13701584    def render(self):
    1371         studentview = StudentBaseDisplayFormPage(self.context.student,
     1585        studentview = StudentBasePDFFormPage(self.context.student,
    13721586            self.request)
    13731587        students_utils = getUtility(IStudentsUtils)
     
    14311645        self.context.bed = new_bed
    14321646        hall_title = new_bed.__parent__.hostel_name
    1433         coordinates = new_bed.getBedCoordinates()[1:]
     1647        coordinates = new_bed.coordinates[1:]
    14341648        block, room_nr, bed_nr = coordinates
    14351649        bc = _('${a}, Block ${b}, Room ${c}, Bed ${d} (${e})', mapping = {
     
    15691783            self.flash(_('Activation code is invalid.'))
    15701784            return
     1785        if code.state == USED:
     1786            self.flash(_('Activation code has already been used.'))
     1787            return
    15711788        # Mark pin as used (this also fires a pin related transition)
    15721789        # and fire transition start_clearance
    1573         if code.state == USED:
    1574             self.flash(_('Activation code has already been used.'))
    1575             return
    1576         else:
    1577             comment = _(u"invalidated")
    1578             # Here we know that the ac is in state initialized so we do not
    1579             # expect an exception, but the owner might be different
    1580             if not invalidate_accesscode(pin,comment,self.context.student_id):
    1581                 self.flash(_('You are not the owner of this access code.'))
    1582                 return
    1583             self.context.clr_code = pin
     1790        comment = _(u"invalidated")
     1791        # Here we know that the ac is in state initialized so we do not
     1792        # expect an exception, but the owner might be different
     1793        if not invalidate_accesscode(pin, comment, self.context.student_id):
     1794            self.flash(_('You are not the owner of this access code.'))
     1795            return
     1796        self.context.clr_code = pin
    15841797        IWorkflowInfo(self.context).fireTransition('start_clearance')
    15851798        self.flash(_('Clearance process has been started.'))
     
    15981811    def form_fields(self):
    15991812        if self.context.is_postgrad:
    1600             form_fields = grok.AutoFields(IPGStudentClearance).omit('clearance_locked')
    1601         else:
    1602             form_fields = grok.AutoFields(IUGStudentClearance).omit('clearance_locked')
     1813            form_fields = grok.AutoFields(IPGStudentClearance).omit(
     1814                'clearance_locked', 'clr_code')
     1815        else:
     1816            form_fields = grok.AutoFields(IUGStudentClearance).omit(
     1817                'clearance_locked', 'clr_code')
    16031818        return form_fields
    16041819
     
    16271842            return
    16281843        self.flash(_('Clearance form has been saved.'))
    1629         self.redirect(self.url(self.context,'request_clearance'))
     1844        if self.context.clr_code:
     1845            self.redirect(self.url(self.context, 'request_clearance'))
     1846        else:
     1847            # We bypass the request_clearance page if student
     1848            # has been imported in state 'clearance started' and
     1849            # no clr_code was entered before.
     1850            state = IWorkflowState(self.context).getState()
     1851            if state != CLEARANCE:
     1852                # This shouldn't happen, but the application officer
     1853                # might have forgotten to lock the form after changing the state
     1854                self.flash(_('This form cannot be submitted. Wrong state!'))
     1855                return
     1856            IWorkflowInfo(self.context).fireTransition('request_clearance')
     1857            self.flash(_('Clearance has been requested.'))
     1858            self.redirect(self.url(self.context))
    16301859        return
    16311860
     
    16471876            return
    16481877        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
    1649         if self.context.clr_code != pin:
     1878        if self.context.clr_code and self.context.clr_code != pin:
    16501879            self.flash(_("This isn't your CLR access code."))
    16511880            return
    16521881        state = IWorkflowState(self.context).getState()
    1653         # This shouldn't happen, but the application officer
    1654         # might have forgotten to lock the form after changing the state
    16551882        if state != CLEARANCE:
     1883            # This shouldn't happen, but the application officer
     1884            # might have forgotten to lock the form after changing the state
    16561885            self.flash(_('This form cannot be submitted. Wrong state!'))
    16571886            return
     
    16731902
    16741903    def update(self, SUBMIT=None):
     1904        if not self.context.is_current:
     1905            emit_lock_message(self)
     1906            return
     1907        super(StartSessionPage, self).update()
    16751908        if not self.context.next_session_allowed:
    16761909            self.flash(_("You are not entitled to start session."))
     
    17301963
    17311964    def update(self):
     1965        if not self.context.is_current:
     1966            emit_lock_message(self)
     1967            return
    17321968        if self.context.student.state != PAID:
    17331969            emit_lock_message(self)
     
    17621998
    17631999    def update(self):
     2000        if not self.context.__parent__.is_current:
     2001            emit_lock_message(self)
     2002            return
    17642003        if self.context.student.state != PAID:
    17652004            emit_lock_message(self)
     
    17762015        level_title = translate(self.context.level_title, 'waeup.kofa',
    17772016            target_language=lang)
    1778         return _('Add and remove course tickets of study level ${a}',
     2017        return _('Edit course list of ${a}',
    17792018            mapping = {'a':level_title})
    17802019
     
    17852024            total_credits += val.credits
    17862025        return total_credits
     2026
     2027    @property
     2028    def translated_values(self):
     2029        return translated_values(self)
    17872030
    17882031    @action(_('Add course ticket'))
     
    18472090        ticket = createObject(u'waeup.CourseTicket')
    18482091        course = data['course']
    1849         for name in ['code', 'title', 'credits', 'passmark', 'semester']:
    1850             setattr(ticket, name, getattr(course, name))
    18512092        ticket.automatic = False
     2093        ticket.carry_over = False
    18522094        try:
    1853             self.context.addCourseTicket(ticket)
     2095            self.context.addCourseTicket(ticket, course)
    18542096        except KeyError:
    18552097            self.flash(_('The ticket exists.'))
     
    19272169    grok.template('requestpw')
    19282170    form_fields = grok.AutoFields(IStudentRequestPW).select(
    1929         'firstname','reg_number','email')
     2171        'firstname','number','email')
    19302172    label = _('Request password for first-time login')
    19312173
     
    19482190        return True
    19492191
    1950     @action(_('Get login credentials'), style='primary')
     2192    @action(_('Send login credentials to email address'), style='primary')
    19512193    def get_credentials(self, **data):
    19522194        if not self.captcha_result.is_valid:
     
    19542196            # No need to flash something.
    19552197            return
    1956         reg_number = data.get('reg_number','')
     2198        number = data.get('number','')
    19572199        firstname = data.get('firstname','')
    19582200        cat = getUtility(ICatalog, name='students_catalog')
    19592201        results = list(
    1960             cat.searchResults(reg_number=(reg_number, reg_number)))
     2202            cat.searchResults(reg_number=(number, number)))
     2203        if not results:
     2204            results = list(
     2205                cat.searchResults(matric_number=(number, number)))
    19612206        if results:
    19622207            student = results[0]
     
    19842229        kofa_utils = getUtility(IKofaUtils)
    19852230        password = kofa_utils.genPassword()
    1986         IUserAccount(student).setPassword(password)
     2231        mandate = PasswordMandate()
     2232        mandate.params['password'] = password
     2233        mandate.params['user'] = student
     2234        site = grok.getSite()
     2235        site['mandates'].addMandate(mandate)
    19872236        # Send email with credentials
    1988         login_url = self.url(grok.getSite(), 'login')
     2237        args = {'mandate_id':mandate.mandate_id}
     2238        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
     2239        url_info = u'Confirmation link: %s' % mandate_url
    19892240        msg = _('You have successfully requested a password for the')
    19902241        if kofa_utils.sendCredentials(IUserAccount(student),
    1991             password, login_url, msg):
     2242            password, url_info, msg):
    19922243            email_sent = student.email
    19932244        else:
     
    19952246        self._redirect(email=email_sent, password=password,
    19962247            student_id=student.student_id)
     2248        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
     2249        self.context.logger.info(
     2250            '%s - %s (%s) - %s' % (ob_class, number, student.student_id, email_sent))
    19972251        return
    19982252
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser_templates/basepage.pt

    r7811 r9209  
    1818          <tal:password replace="view/hasPassword" />
    1919      </td>
    20     <tr>
     20    </tr>
    2121    <tal:files content="structure provider:files" />
    2222  </tbody>
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser_templates/requestpw.pt

    r8782 r9209  
    1717  </table>
    1818  <p i18n:translate="">
    19     Your student record will be looked up and an email with your login
    20     credentials will be sent to the address provided.
     19    Your student record will be looked up and
     20    your login credentials will be sent to the email address given above.
     21    To be able to proceed you must provide a valid email address!
    2122  </p>
    2223  <div tal:condition="view/availableActions">
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser_templates/requestpwmailsent.pt

    r8782 r9209  
    2929    <tr>
    3030  </table>
    31   <p i18n:translate="">
    32     Print this page and proceed to the
     31  <p>
     32    <span i18n:translate="">Print this page and proceed to the</span>
    3333    <a tal:attributes="href python: view.url(layout.site, 'login')">login form</a>.
    34     Please note that passwords are case-sensitive,
     34    <span i18n:translate="">Please note that passwords are case-sensitive,
    3535    <br />when entering your credentials, and keep your password secret!
     36    </span>
    3637  </p>
    3738  <p tal:condition = "view/email">
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser_templates/studycoursepage.pt

    r7876 r9209  
    4747        </td>
    4848      </tr>
     49      <tr tal:condition="view/prev_studycourses">
     50        <td i18n:translate="">
     51            Previous Study Courses:
     52        </td>
     53        <td>
     54          <span tal:repeat="value view/prev_studycourses">
     55            <a tal:attributes="href value/href" tal:content="value/title">
     56              FACULTY
     57            </a>
     58          </span>
     59        </td>
     60      </tr>
    4961  </tbody>
    5062</table>
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser_templates/studyleveleditpage.pt

    r8553 r9209  
    2222  </thead>
    2323  <tbody>
    24     <tr tal:repeat="value context/values" class="gradeC">
     24    <tr tal:repeat="value view/translated_values" class="gradeC">
    2525       <td>
    2626        <input type="checkbox" name="val_id"
    2727                  tal:attributes="value value/__name__"
    28         tal:condition="not: value/mandatory" />
     28        tal:condition="not: value/mandatory_bool" />
    2929      </td>
    3030      <td tal:content="value/semester">SEMESTER</td>
     
    3636      <td tal:content="value/fcode">FACULTY</td>
    3737      <td tal:content="value/credits">CREDITS</td>
    38       <td tal:content="value/score">SCORE</td>
     38      <td tal:content="value/score|nothing">SCORE</td>
    3939      <td tal:content="value/carry_over">CO</td>
    4040    </tr>
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser_templates/studylevelmanagepage.pt

    r7811 r9209  
    4646      </thead>
    4747      <tbody>
    48         <tr tal:repeat="value context/values" class="gradeC">
     48        <tr tal:repeat="value view/translated_values" class="gradeC">
    4949          <td>
    5050            <input type="checkbox" name="val_id"
     
    6060          <td tal:content="value/credits">CREDITS</td>
    6161          <td tal:content="value/mandatory">MANDATORY</td>
    62           <td tal:content="value/score">SCORE</td>
     62          <td tal:content="value/score|nothing">SCORE</td>
    6363          <td tal:content="value/carry_over">CO</td>
    6464          <td tal:content="value/automatic">AUTO</td>
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/browser_templates/studylevelpage.pt

    r8141 r9209  
    3434      <th i18n:translate="">Code</th>
    3535      <th i18n:translate="">Title</th>
    36       <th i18n:translate="">Dept.</th>
    37       <th i18n:translate="">Fact.</th>
     36      <th i18n:translate="">Dep.</th>
     37      <th i18n:translate="">Fac.</th>
    3838      <th i18n:translate="">Cred.</th>
    3939      <th i18n:translate="">Mand.</th>
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/dynamicroles.py

    r7811 r9209  
    5151        """Get roles for principal with id `principal_id`.
    5252
    53         Different to the default implementation, this method also
    54         takes into account local roles set on any department connected
    55         to the context student.
    56 
    57         If the given principal has at least one of the
    58         `external_rolenames` roles granted for the external object, it
    59         additionally gets `additional_rolename` role for the context
    60         student.
    61 
    62         For the additional roles the `extra_attrib` and all its parent
    63         objects are looked up, because 'role inheritance' does not
    64         work on that basic level of permission handling.
    65 
    66         Some advantages of this approach:
    67 
    68         - we don't have to store extra local roles for clearance
    69           officers in ZODB for each student
    70 
    71         - when local roles on a department change, we don't have to
    72           update thousands of students; the local role is assigned
    73           dynamically.
    74 
    75         Disadvantage:
    76 
    77         - More expensive role lookups when a clearance officer wants
    78           to see an student form.
    79 
    80         This implementation is designed to be usable also for other
    81         contexts than students. You can inherit from it and set
    82         different role names to lookup/set easily via the static class
    83         attributes.
     53        See waeup.kofa.applicants.dynamicroles.ApplicantPrincipalRoleManager
     54        for further information.
    8455        """
    8556        apr_manager = AnnotationPrincipalRoleManager(self._context)
     
    11889                        result.append(
    11990                            ('waeup.StudentsOfficer', setting))
     91                elif 'UGClearanceOfficer' in role_id:
     92                    if not self._context.is_postgrad:
     93                        result.append(
     94                            ('waeup.StudentsClearanceOfficer', setting))
     95                    else:
     96                        # Otherwise grant at least view permissions.
     97                        result.append(
     98                            ('waeup.StudentsOfficer', setting))
     99                elif 'PGClearanceOfficer' in role_id:
     100                    if self._context.is_postgrad:
     101                        result.append(
     102                            ('waeup.StudentsClearanceOfficer', setting))
     103                    else:
     104                        # Otherwise grant at least view permissions.
     105                        result.append(
     106                            ('waeup.StudentsOfficer', setting))
    120107                elif role_id in self.rolename_mapping.keys():
    121108                    # Grant additional role
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/export.py

    r8736 r9209  
    113113        if name == 'history':
    114114            value = value.messages
     115        if name == 'phone' and value is not None:
     116            # Append hash '#' to phone numbers to circumvent
     117            # unwanted excel automatic
     118            value = str('%s#' % value)
    115119        return super(
    116120            StudentsExporter, self).mangle_value(
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/interfaces.py

    r9004 r9209  
    4343    def getValues(self, context):
    4444        verdicts_dict = getUtility(IStudentsUtils).VERDICTS_DICT
    45         return verdicts_dict.keys()
     45        return sorted(verdicts_dict.keys())
    4646
    4747    def getToken(self, context, value):
     
    5050    def getTitle(self, context, value):
    5151        verdicts_dict = getUtility(IStudentsUtils).VERDICTS_DICT
     52        if value != '0':
     53            return verdicts_dict[value] + ' (%s)' % value
    5254        return verdicts_dict[value]
    5355
     
    6567        """
    6668
    67     def setPaymentDetails(category, student):
     69    def setPaymentDetails(category, student, previous_session=None,
     70            previous_level=None,):
    6871        """Create Payment object and set the payment data of a student for
    6972        the payment category specified.
     
    151154    faccode = Attribute('The faculty code of any chosen study course')
    152155    current_session = Attribute('The current session of the student')
     156    current_level = Attribute('The current level of the student')
    153157    current_mode = Attribute('The current mode of the student')
     158    current_verdict = Attribute('The current verdict of the student')
    154159    fullname = Attribute('All name parts separated by hyphens')
    155160    display_fullname = Attribute('The fullname of an applicant')
     161    is_postgrad = Attribute('True if postgraduate student')
     162
     163    suspended = schema.Bool(
     164        title = _(u'Account suspended'),
     165        default = False,
     166        required = False,
     167        )
    156168
    157169    student_id = schema.TextLine(
     
    198210        title = _(u'PWD Activation Code'),
    199211        required = False,
    200         readonly = True,
     212        readonly = False,
    201213        )
    202214
     
    212224        )
    213225
     226    def transfer(certificate, current_session,
     227        current_level, current_verdict):
     228        """ Creates a new studycourse and backups the old one.
     229
     230        """
     231
    214232class IUGStudentClearance(IKofaObject):
    215233    """Representation of undergraduate student clearance data.
     
    225243        title = _(u'Clearance form locked'),
    226244        default = False,
     245        required = False,
    227246        )
    228247
     
    230249        title = _(u'CLR Activation Code'),
    231250        required = False,
    232         readonly = True,
     251        readonly = False,
    233252        )
    234253
     
    288307    login for the the first time.
    289308    """
    290     reg_number = schema.TextLine(
    291         title = u'Registration Number',
     309    number = schema.TextLine(
     310        title = _(u'Registr. or Matric. Number'),
    292311        required = True,
    293312        )
     
    346365        title = _(u'Current Verdict'),
    347366        source = VerdictSource(),
    348         default = 'NY',
     367        default = '0',
    349368        required = False,
    350369        )
     
    353372        title = _(u'Previous Verdict'),
    354373        source = VerdictSource(),
    355         default = 'NY',
    356         required = False,
    357         )
     374        default = '0',
     375        required = False,
     376        )
     377
     378class IStudentStudyCourseTransfer(IStudentStudyCourse):
     379    """An student transfers.
     380
     381    """
     382
     383    certificate = schema.Choice(
     384        title = _(u'Certificate'),
     385        source = CertificateSource(),
     386        required = True,
     387        )
     388
     389    current_level = schema.Choice(
     390        title = _(u'Current Level'),
     391        source = StudyLevelSource(),
     392        required = True,
     393        readonly = False,
     394        )
     395
     396
     397IStudentStudyCourseTransfer['certificate'].order = IStudentStudyCourse[
     398    'certificate'].order
     399IStudentStudyCourseTransfer['current_level'].order = IStudentStudyCourse[
     400    'current_level'].order
    358401
    359402class IStudentVerdictUpdate(IKofaObject):
     
    385428    """
    386429    level = Attribute('The level code')
    387     validation_date = Attribute('The date of validation')
    388     validated_by = Attribute('User Id of course adviser')
    389430
    390431    level_session = schema.Choice(
    391432        title = _(u'Session'),
    392433        source = academic_sessions_vocab,
    393         required = True,
     434        required = False,
    394435        )
    395436
     
    397438        title = _(u'Verdict'),
    398439        source = VerdictSource(),
    399         default = 'NY',
    400         required = False,
    401         )
    402 
    403     def addCourseTicket(courseticket):
     440        default = '0',
     441        required = False,
     442        )
     443
     444    validated_by = schema.TextLine(
     445        title = _(u'Validated by'),
     446        default = None,
     447        required = False,
     448        )
     449
     450    validation_date = schema.Datetime(
     451        title = _(u'Validation Date'),
     452        required = False,
     453        readonly = False,
     454        )
     455
     456    def addCourseTicket(ticket, course):
    404457        """Add a course ticket object.
    405458        """
     
    520573    """
    521574
     575    p_current = schema.Bool(
     576        title = _(u'Current Session Payment'),
     577        default = True,
     578        required = False,
     579        )
     580
    522581    p_level = schema.Int(
    523582        title = _(u'Payment Level'),
     
    543602IStudentOnlinePayment['p_level'].order = IStudentOnlinePayment[
    544603    'p_session'].order
     604
     605class IStudentPreviousPayment(IOnlinePayment):
     606    """An interface for adding previous session payments.
     607
     608    """
     609
     610    p_session = schema.Choice(
     611        title = _(u'Payment Session'),
     612        source = academic_sessions_vocab,
     613        required = True,
     614        )
     615
     616    p_level = schema.Choice(
     617        title = _(u'Payment Level'),
     618        source = StudyLevelSource(),
     619        required = True,
     620        )
    545621
    546622class ICSVStudentExporter(ICSVExporter):
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/payments.py

    r8736 r9209  
    9797        """Process student after payment was made.
    9898        """
    99         error = self._createActivationCodes()
    100         if error is not None:
    101             return False, error, error
     99        if self.p_current:
     100            error = self._createActivationCodes()
     101            if error is not None:
     102                return False, error, error
    102103        log = 'successful payment: %s' % self.p_id
    103104        msg = _('Successful payment')
     
    107108        """Process student after payment was approved.
    108109        """
    109         error = self._createActivationCodes()
    110         if error is not None:
    111             return False, error, error
     110        if self.p_current:
     111            error = self._createActivationCodes()
     112            if error is not None:
     113                return False, error, error
    112114        log = 'payment approved: %s' % self.p_id
    113115        msg = _('Payment approved')
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/student.py

    r8737 r9209  
    2828from zope.interface import implementedBy
    2929from zope.securitypolicy.interfaces import IPrincipalRoleManager
     30from zope.schema.interfaces import ConstraintNotSatisfied
    3031
    3132from waeup.kofa.image import KofaImageFile
     
    130131
    131132    @property
     133    def current_level(self):
     134        level = getattr(
     135            self.get('studycourse', None), 'current_level', None)
     136        return level
     137
     138    @property
     139    def current_verdict(self):
     140        level = getattr(
     141            self.get('studycourse', None), 'current_verdict', None)
     142        return level
     143
     144    @property
    132145    def current_mode(self):
    133146        certificate = getattr(
     
    142155            self.get('studycourse', None), 'is_postgrad', False)
    143156        return is_postgrad
     157
     158    def transfer(self, certificate, current_session=None,
     159        current_level=None, current_verdict=None, previous_verdict=None):
     160        """ Creates a new studycourse and backups the old one.
     161
     162        """
     163        studycourse = createObject(u'waeup.StudentStudyCourse')
     164        try:
     165            studycourse.certificate = certificate
     166            studycourse.entry_mode = 'transfer'
     167            studycourse.current_session = current_session
     168            studycourse.current_level = current_level
     169            studycourse.current_verdict = current_verdict
     170            studycourse.previous_verdict = previous_verdict
     171        except ConstraintNotSatisfied:
     172            return -1
     173        old = self['studycourse']
     174        if getattr(old, 'entry_session', None) is None or\
     175            getattr(old, 'certificate', None) is None:
     176            return -2
     177        studycourse.entry_session = old.entry_session
     178        # Students can be transferred only two times.
     179        if 'studycourse_1' in self.keys():
     180            if 'studycourse_2' in self.keys():
     181                return -3
     182            self['studycourse_2'] = old
     183        else:
     184            self['studycourse_1'] = old
     185        del self['studycourse']
     186        self['studycourse'] = studycourse
     187        self.__parent__.logger.info(
     188            '%s - transferred from %s to %s' % (
     189            self.student_id, old.certificate.code, studycourse.certificate.code))
     190        history = IObjectHistory(self)
     191        history.addMessage('Transferred from %s to %s' % (
     192            old.certificate.code, studycourse.certificate.code))
     193        return
    144194
    145195
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/studycourse.py

    r8736 r9209  
    6161    @property
    6262    def is_postgrad(self):
     63        if self.certificate is None:
     64            return False
    6365        return self.certificate.study_mode.startswith('pg')
    64         #return cert.start_level == 999 or cert.end_level == 999
     66
     67    @property
     68    def is_current(self):
     69        if '_' in self.__name__:
     70            return False
     71        return True
    6572
    6673    def addStudentStudyLevel(self, cert, studylevel):
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/studylevel.py

    r8736 r9209  
    3737        super(StudentStudyLevel, self).__init__()
    3838        self.level = None
    39         self.validation_date = None
    40         self.validated_by = None
    4139        return
    4240
     
    5654        return studylevelsource.factory.getTitle(self.__parent__, self.level)
    5755
    58     def addCourseTicket(self, courseticket):
     56    def addCourseTicket(self, ticket, course):
    5957        """Add a course ticket object.
    6058        """
    61         if not ICourseTicket.providedBy(courseticket):
     59        if not ICourseTicket.providedBy(ticket):
    6260            raise TypeError(
    6361                'StudentStudyLeves contain only ICourseTicket instances')
    64         self[courseticket.code] = courseticket
     62        ticket.code = course.code
     63        ticket.title = course.title
     64        ticket.fcode = course.__parent__.__parent__.__parent__.code
     65        ticket.dcode = course.__parent__.__parent__.code
     66        ticket.credits = course.credits
     67        ticket.passmark = course.passmark
     68        ticket.semester = course.semester
     69        self[ticket.code] = ticket
    6570        return
    6671
     
    8792
    8893    A course ticket contains a copy of the original course and
    89     course referrer data. If the courses and/or their referrers are removed, the
    90     corresponding tickets remain unchanged. So we do not need any event
     94    certificate course data. If the courses and/or the referrin certificate
     95    courses are removed, the corresponding tickets remain unchanged.
     96    So we do not need any event
    9197    triggered actions on course tickets.
    9298    """
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/sample_payment_data.csv

    r8244 r9209  
    1 matric_number,p_state,p_category,p_id,reg_number,session_id,r_amount_approved,p_item,amount_auth,r_card_num,r_code,creation_date,type
    2 ,paid,schoolfee,3816951290797973744#,1,2010,19500,BTECHBDT,19500,0942,00,2010/11/26 19:59:33.744 GMT+1,online
    3 100001,unpaid,schoolfee,3816951290712593757,,2010,0,BTECHBDT,19500,0942,Z0,2010-11-25 20:16:33.757 GMT+1,online
    4 ,paid,schoolfee,p1266236341955,3,2009,19500,BTECHBDT,19500,0615,00,2010/02/15 13:19:01,online
     1matric_number,p_state,p_category,p_id,reg_number,session_id,r_amount_approved,p_item,amount_auth,r_card_num,r_code,creation_date,type,p_current
     2,paid,schoolfee,3816951290797973744#,1,2010,19500,BTECHBDT,19500,0942,00,2010/11/26 19:59:33.744 GMT+1,online,1
     3100001,unpaid,schoolfee,3816951290712593757,,2010,0,BTECHBDT,19500,0942,Z0,2010-11-25 20:16:33.757 WAT,online,0
     4,paid,schoolfee,p1266236341955,3,2009,19500,BTECHBDT,19500,0615,00,2010/02/15 13:19:01,online,1
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/test_authentication.py

    r8351 r9209  
    6565    email = None
    6666    phone = None
     67    suspended = False
    6768
    6869
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/test_batching.py

    r8626 r9209  
    2323import unittest
    2424import datetime
     25import grok
    2526from time import time
     27from zope.event import notify
    2628from zope.component import createObject
    2729from zope.component.hooks import setSite, clearSite
    2830from zope.interface.verify import verifyClass, verifyObject
     31from hurry.workflow.interfaces import IWorkflowState
    2932
    3033from waeup.kofa.app import University
     
    126129    '\n')[0].split(',')
    127130
     131PAYMENT_CREATE_SAMPLE_DATA = open(
     132    os.path.join(os.path.dirname(__file__), 'sample_create_payment_data.csv'),
     133    'rb').read()
     134
     135PAYMENT_CREATE_HEADER_FIELDS = PAYMENT_CREATE_SAMPLE_DATA.split(
     136    '\n')[0].split(',')
     137
    128138class StudentImportExportSetup(FunctionalTestCase):
    129139
     
    171181    def setup_student(self, student):
    172182        # set predictable values for `student`
    173         student.matric_number = u'M123456'
     183        student.matric_number = u'234'
    174184        student.adm_code = u'my adm code'
    175185        student.clearance_locked = False
    176186        student.clr_code = u'my clr code'
    177187        student.perm_address = u'Studentroad 21\nLagos 123456\n'
    178         student.reg_number = u'123456'
    179         student.student_id = u'A111111'
     188        student.reg_number = u'123'
    180189        student.firstname = u'Anna'
    181190        student.lastname = u'Tester'
     
    211220        ticket.passmark = 100
    212221        ticket.semester = 2
    213         study_level.addCourseTicket(ticket)
     222        study_level[ticket.code] = ticket
    214223        self.add_payment(student)
    215224        return student
     
    232241
    233242
    234 
    235 class StudentProcessorTest(FunctionalTestCase):
     243class StudentProcessorTest(StudentImportExportSetup):
    236244
    237245    layer = FunctionalLayer
     
    239247    def setUp(self):
    240248        super(StudentProcessorTest, self).setUp()
    241         # Setup a sample site for each test
    242         app = University()
    243         self.dc_root = tempfile.mkdtemp()
    244         app['datacenter'].setStoragePath(self.dc_root)
    245 
    246         # Prepopulate the ZODB...
    247         self.getRootFolder()['app'] = app
    248         # we add the site immediately after creation to the
    249         # ZODB. Catalogs and other local utilities are not setup
    250         # before that step.
    251         self.app = self.getRootFolder()['app']
    252         # Set site here. Some of the following setup code might need
    253         # to access grok.getSite() and should get our new app then
    254         setSite(app)
    255249
    256250        # Add student with subobjects
    257251        student = Student()
    258         student.firstname = u'Anna'
    259         student.lastname = u'Tester'
    260         student.reg_number = u'123'
    261         student.matric_number = u'234'
    262252        self.app['students'].addStudent(student)
     253        student = self.setup_student(student)
     254        notify(grok.ObjectModifiedEvent(student))
    263255        self.student = self.app['students'][student.student_id]
     256
    264257        self.processor = StudentProcessor()
    265258        self.workdir = tempfile.mkdtemp()
     
    285278        open(self.csv_file_duplicates, 'wb').write(STUDENT_SAMPLE_DATA_DUPLICATES)
    286279
    287     def tearDown(self):
    288         super(StudentProcessorTest, self).tearDown()
    289         shutil.rmtree(self.workdir)
    290         shutil.rmtree(self.dc_root)
    291         clearSite()
    292         return
    293 
    294280    def test_interface(self):
    295281        # Make sure we fulfill the interface contracts.
     
    343329        self.assertEqual(initial_stud_id, new_stud_id)
    344330        return
     331
     332    def test_checkUpdateRequirements(self):
     333        # Make sure that pg students can't be updated with wrong transition.
     334        err = self.processor.checkUpdateRequirements(self.student,
     335            dict(reg_number='1', state='returning'), self.app)
     336        self.assertTrue(err is None)
     337        self.certificate.study_mode = 'pg_ft'
     338        err = self.processor.checkUpdateRequirements(self.student,
     339            dict(reg_number='1', state='returning'), self.app)
     340        self.assertEqual(err, 'State not allowed (pg student).')
     341        IWorkflowState(self.student).setState('school fee paid')
     342        err = self.processor.checkUpdateRequirements(self.student,
     343            dict(reg_number='1', transition='reset6'), self.app)
     344        self.assertEqual(err, 'Transition not allowed (pg student).')
     345        err = self.processor.checkUpdateRequirements(self.student,
     346            dict(reg_number='1', transition='register_courses'), self.app)
     347        self.assertEqual(err, 'Transition not allowed (pg student).')
     348
    345349
    346350    def test_delEntry(self):
     
    486490        super(StudentStudyCourseProcessorTest, self).setUp()
    487491
     492        # Add student with subobjects
     493        student = Student()
     494        self.app['students'].addStudent(student)
     495        student = self.setup_student(student)
     496        notify(grok.ObjectModifiedEvent(student))
     497        self.student = self.app['students'][student.student_id]
     498
    488499        # Import students with subobjects
    489500        student_file = os.path.join(self.workdir, 'sample_student_data.csv')
     
    532543            dict(reg_number='1', current_level='100'))
    533544        self.assertEqual(len(errs),0)
     545
     546    def test_checkUpdateRequirements(self):
     547        # Make sure that pg students can't be updated with wrong transition.
     548        err = self.processor.checkUpdateRequirements(self.student['studycourse'],
     549            dict(reg_number='1', current_level='100'), self.app)
     550        self.assertTrue(err is None)
     551        # Since row has passed the converter, current_level is an integer.
     552        err = self.processor.checkUpdateRequirements(self.student['studycourse'],
     553            dict(reg_number='1', current_level=999), self.app)
     554        self.assertTrue(err is None)
     555        IWorkflowState(self.student).setState('returning')
     556        err = self.processor.checkUpdateRequirements(self.student['studycourse'],
     557            dict(reg_number='1', current_level=999), self.app)
     558        self.assertEqual(err, 'Not a pg student.')
    534559
    535560    def test_import(self):
     
    667692        self.assertEqual(num_warns,2)
    668693        shutil.rmtree(os.path.dirname(fin_file))
    669        
    670694
    671695class CourseTicketProcessorTest(StudentImportExportSetup):
     
    681705        shutil.rmtree(os.path.dirname(fin_file))
    682706
    683         # Add course and course referrer
     707        # Add course and certificate course
    684708        self.course = createObject('waeup.Course')
    685709        self.course.code = 'COURSE1'
     
    689713        self.app['faculties']['fac1']['dep1'].courses.addCourse(
    690714            self.course)
    691         self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCourseRef(
     715        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCertCourse(
    692716            self.course, level=100)
    693717
     
    755779        # Logging message from updateEntry,
    756780        self.assertTrue(
    757             'INFO - system - K1000000 - Course ticket updated: code=COURSE1, '
     781            'INFO - system - K1000000 - Course ticket in 100 updated: code=COURSE1, '
    758782            'mandatory=False, score=3'
    759783            in logcontent)
     
    769793        self.assertEqual(num_warns,2)
    770794        shutil.rmtree(os.path.dirname(fin_file))
     795
     796    def test_import_remove(self):
     797        # We perform the same import twice,
     798        # the second time in remove mode. The number
     799        # of warnings must be the same.
     800        num, num_warns, fin_file, fail_file = self.processor.doImport(
     801            self.csv_file, COURSETICKET_HEADER_FIELDS,'create')
     802        assert self.processor.entryExists(
     803            dict(reg_number='1', level='100', code='COURSE1'), self.app) is True
     804        num, num_warns, fin_file, fail_file = self.processor.doImport(
     805            self.csv_file, COURSETICKET_HEADER_FIELDS,'remove')
     806        self.assertEqual(num_warns,2)
     807        assert self.processor.entryExists(
     808            dict(reg_number='1', level='100', code='COURSE1'), self.app) is False
     809        shutil.rmtree(os.path.dirname(fin_file))
     810        logcontent = open(self.logfile).read()
     811        self.assertTrue(
     812            'INFO - system - K1000000 - Course ticket in 100 removed: COURSE1'
     813            in logcontent)
    771814
    772815class PaymentProcessorTest(StudentImportExportSetup):
     
    784827        self.student = self.app['students'][student.student_id]
    785828        payment = createObject(u'waeup.StudentOnlinePayment')
    786         payment.p_id = 'p123'
     829        payment.p_id = 'p120'
    787830        self.student['payments'][payment.p_id] = payment
    788831
     
    798841            self.workdir, 'sample_payment_data.csv')
    799842        open(self.csv_file, 'wb').write(PAYMENT_SAMPLE_DATA)
     843        self.csv_file2 = os.path.join(
     844            self.workdir, 'sample_create_payment_data.csv')
     845        open(self.csv_file2, 'wb').write(PAYMENT_CREATE_SAMPLE_DATA)
    800846
    801847    def test_interface(self):
     
    809855            dict(student_id='ID_NONE', p_id='nonsense'), self.app) is None
    810856        assert self.processor.getEntry(
    811             dict(student_id=self.student.student_id, p_id='p123'),
    812             self.app) is self.student['payments']['p123']
     857            dict(student_id=self.student.student_id, p_id='p120'),
     858            self.app) is self.student['payments']['p120']
    813859        assert self.processor.getEntry(
    814             dict(student_id=self.student.student_id, p_id='XXXXXX123'),
    815             self.app) is self.student['payments']['p123']
     860            dict(student_id=self.student.student_id, p_id='XXXXXX112'),
     861            self.app) is self.student['payments']['p120']
     862
     863    def test_delEntry(self):
     864        assert self.processor.getEntry(
     865            dict(student_id=self.student.student_id, p_id='p120'),
     866            self.app) is self.student['payments']['p120']
     867        self.assertEqual(len(self.student['payments'].keys()),1)
     868        self.processor.delEntry(
     869            dict(student_id=self.student.student_id, p_id='p120'),
     870            self.app)
     871        assert self.processor.getEntry(
     872            dict(student_id=self.student.student_id, p_id='p120'),
     873            self.app) is None
     874        self.assertEqual(len(self.student['payments'].keys()),0)
    816875
    817876    def test_addEntry(self):
     
    831890            self.app)
    832891        self.assertEqual(len(self.student['payments'].keys()),3)
    833         self.assertEqual(self.student['payments']['p456'].p_id, 'p456')
     892        self.assertEqual(self.student['payments']['p560'].p_id, 'p560')
    834893
    835894    def test_checkConversion(self):
    836895        errs, inv_errs, conv_dict = self.processor.checkConversion(
    837             dict(reg_number='1', p_id='3816951266236341955'))
     896            dict(p_id='3816951266236341955'))
    838897        self.assertEqual(len(errs),0)
    839898        errs, inv_errs, conv_dict = self.processor.checkConversion(
    840             dict(reg_number='1', p_id='p1266236341955'))
     899            dict(p_id='p1266236341955'))
    841900        self.assertEqual(len(errs),0)
    842901        errs, inv_errs, conv_dict = self.processor.checkConversion(
    843             dict(reg_number='1', p_id='nonsense'))
     902            dict(p_id='nonsense'))
    844903        self.assertEqual(len(errs),1)
    845         timestamp = "%d" % int(time()*1000)
     904        timestamp = ("%d" % int(time()*10000))[1:]
    846905        p_id = "p%s" % timestamp
    847906        errs, inv_errs, conv_dict = self.processor.checkConversion(
    848             dict(reg_number='1', p_id=p_id))
     907            dict(p_id=p_id))
    849908        self.assertEqual(len(errs),0)
    850909
     
    854913        self.assertEqual(num_warns,0)
    855914        payment = self.processor.getEntry(dict(reg_number='1',
    856             p_id='p1290797973744'), self.app)
    857         self.assertEqual(payment.p_id, 'p1290797973744')
     915            p_id='p2907979737440'), self.app)
     916        self.assertEqual(payment.p_id, 'p2907979737440')
     917        self.assertTrue(payment.p_current)
    858918        cdate = payment.creation_date.strftime("%Y-%m-%d %H:%M:%S")
    859919        self.assertEqual(cdate, "2010-11-26 18:59:33")
    860920        self.assertEqual(str(payment.creation_date.tzinfo),'UTC')
    861         shutil.rmtree(os.path.dirname(fin_file))
    862 
     921        payment = self.processor.getEntry(dict(matric_number='100001',
     922            p_id='p2907125937570'), self.app)
     923        self.assertEqual(payment.p_id, 'p2907125937570')
     924        self.assertFalse(payment.p_current)
     925        cdate = payment.creation_date.strftime("%Y-%m-%d %H:%M:%S")
     926        # Ooooh, still the old problem, see
     927        # http://mail.dzug.org/mailman/archives/zope/2006-August/001153.html.
     928        # WAT is interpreted as GMT-1 and not GMT+1
     929        self.assertEqual(cdate, "2010-11-25 21:16:33")
     930        self.assertEqual(str(payment.creation_date.tzinfo),'UTC')
     931        shutil.rmtree(os.path.dirname(fin_file))
    863932        logcontent = open(self.logfile).read()
    864         # Logging message from updateEntry,
     933        # Logging message from updateEntry
    865934        self.assertTrue(
    866935            'INFO - system - K1000001 - Payment ticket updated: '
    867             'p_id=p1266236341955, p_item=BTECHBDT, '
    868             'creation_date=2010-02-15 13:19:01+00:00, r_code=00, '
    869             'r_amount_approved=19500.0, p_category=schoolfee, '
    870             'amount_auth=19500.0, p_state=paid'
     936            'p_item=BTECHBDT, creation_date=2010-02-15 13:19:01+00:00, '
     937            'p_category=schoolfee, amount_auth=19500.0, p_current=True, '
     938            'p_id=p1266236341955, r_code=00, r_amount_approved=19500.0, '
     939            'p_state=paid'
    871940            in logcontent)
    872941
     
    881950        self.assertEqual(num_warns,0)
    882951        shutil.rmtree(os.path.dirname(fin_file))
     952
     953    def test_import_remove(self):
     954        num, num_warns, fin_file, fail_file = self.processor.doImport(
     955            self.csv_file, PAYMENT_HEADER_FIELDS,'create')
     956        num, num_warns, fin_file, fail_file = self.processor.doImport(
     957            self.csv_file, PAYMENT_HEADER_FIELDS,'remove')
     958        self.assertEqual(num_warns,0)
     959        shutil.rmtree(os.path.dirname(fin_file))
     960        logcontent = open(self.logfile).read()
     961        self.assertTrue(
     962            'INFO - system - K1000001 - Payment ticket removed: p1266236341955'
     963            in logcontent)
     964
     965    def test_import_wo_pid(self):
     966        num, num_warns, fin_file, fail_file = self.processor.doImport(
     967            self.csv_file2, PAYMENT_CREATE_HEADER_FIELDS,'create')
     968        self.assertEqual(num_warns,0)
     969        shutil.rmtree(os.path.dirname(fin_file))
     970        self.assertEqual(len(self.app['students']['X666666']['payments']), 50)
     971
    883972
    884973def test_suite():
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/test_browser.py

    r8779 r9209  
    118118        self.manage_clearance_path = self.student_path + '/manage_clearance'
    119119        self.edit_personal_path = self.student_path + '/edit_personal'
     120        self.manage_personal_path = self.student_path + '/manage_personal'
    120121        self.studycourse_path = self.student_path + '/studycourse'
    121122        self.payments_path = self.student_path + '/payments'
     
    170171        self.app['faculties']['fac1']['dep1'].courses.addCourse(
    171172            self.course)
    172         self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCourseRef(
     173        self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCertCourse(
    173174            self.course, level=100)
    174175
     
    403404    layer = FunctionalLayer
    404405
     406    def test_student_properties(self):
     407        self.student['studycourse'].current_level = 100
     408        self.assertEqual(self.student.current_level, 100)
     409        self.student['studycourse'].current_session = 2011
     410        self.assertEqual(self.student.current_session, 2011)
     411        self.student['studycourse'].current_verdict = 'A'
     412        self.assertEqual(self.student.current_verdict, 'A')
     413        return
     414
    405415    def test_basic_auth(self):
    406416        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     
    454464        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    455465        self.assertEqual(self.browser.url, self.personal_path)
     466        self.browser.getLink("Manage").click()
     467        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     468        self.assertEqual(self.browser.url, self.manage_personal_path)
     469        self.browser.open(self.personal_path)
    456470        self.browser.getLink("Edit").click()
    457471        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     
    474488        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    475489        self.assertEqual(self.browser.url, self.history_path)
    476         self.assertMatches('...Student admitted by Manager...',
     490        self.assertMatches('...Admitted by Manager...',
    477491                           self.browser.contents)
    478492        # Only the Application Slip does not exist
     
    754768        self.assertEqual(student['studycourse'].current_session, 2005) # +1
    755769        self.assertEqual(student['studycourse'].current_level, 200) # +100
    756         self.assertEqual(student['studycourse'].current_verdict, 'NY') # NY = not set
     770        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
    757771        self.assertEqual(student['studycourse'].previous_verdict, 'A')
    758772        self.browser.getControl(name="transition").value = ['register_courses']
     
    762776        self.browser.getControl(name="transition").value = ['return']
    763777        self.browser.getControl("Save").click()
     778        return
     779
     780    def test_manage_pg_workflow(self):
     781        # Managers can pass through the whole workflow
     782        IWorkflowState(self.student).setState('school fee paid')
     783        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     784        student = self.app['students'][self.student_id]
     785        self.browser.open(self.manage_student_path)
     786        self.assertTrue('<option value="reset6">' in self.browser.contents)
     787        self.assertTrue('<option value="register_courses">' in self.browser.contents)
     788        self.assertTrue('<option value="reset5">' in self.browser.contents)
     789        self.certificate.study_mode = 'pg_ft'
     790        self.browser.open(self.manage_student_path)
     791        self.assertFalse('<option value="reset6">' in self.browser.contents)
     792        self.assertFalse('<option value="register_courses">' in self.browser.contents)
     793        self.assertTrue('<option value="reset5">' in self.browser.contents)
    764794        return
    765795
     
    776806        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    777807        self.browser.open(datacenter_path)
    778         self.browser.getLink('Upload CSV file').click()
     808        self.browser.getLink('Upload data').click()
    779809        filecontents = StringIO(open('students.csv', 'rb').read())
    780810        filewidget = self.browser.getControl(name='uploadfile:file')
    781811        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
    782812        self.browser.getControl(name='SUBMIT').click()
    783         self.browser.getLink('Batch processing').click()
     813        self.browser.getLink('Process data').click()
    784814        button = lookup_submit_value(
    785815            'select', 'students_zope.mgr.csv', self.browser)
     
    802832""")
    803833        self.browser.open(datacenter_path)
    804         self.browser.getLink('Upload CSV file').click()
     834        self.browser.getLink('Upload data').click()
    805835        filecontents = StringIO(open('studycourses.csv', 'rb').read())
    806836        filewidget = self.browser.getControl(name='uploadfile:file')
    807837        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
    808838        self.browser.getControl(name='SUBMIT').click()
    809         self.browser.getLink('Batch processing').click()
     839        self.browser.getLink('Process data').click()
    810840        button = lookup_submit_value(
    811841            'select', 'studycourses_zope.mgr.csv', self.browser)
     
    917947        self.assertTrue('clearance started' in self.browser.contents)
    918948        self.browser.open(self.history_path)
    919         self.assertTrue("Reset to 'clearance' by My Public Name" in
     949        self.assertTrue("Reset to 'clearance started' by My Public Name" in
    920950            self.browser.contents)
    921951        IWorkflowInfo(self.student).fireTransition('request_clearance')
     
    946976        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
    947977        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
    948         self.app['users']['mrsadvise'].title = 'Helen Procter'
     978        self.app['users']['mrsadvise'].title = u'Helen Procter'
    949979        # Assign local CourseAdviser100 role for a certificate
    950980        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
     
    9971027        # the 100L CA does see the 'Validate' button
    9981028        self.browser.open(L110_student_path)
    999         self.assertFalse('Validate' in self.browser.contents)
     1029        self.assertFalse('Validate courses' in self.browser.contents)
    10001030        IWorkflowInfo(self.student).fireTransition('register_courses')
    10011031        self.browser.open(L110_student_path)
    1002         self.assertFalse('Validate' in self.browser.contents)
     1032        self.assertFalse('Validate courses' in self.browser.contents)
    10031033        self.student['studycourse'].current_level = 110
    10041034        self.browser.open(L110_student_path)
    1005         self.assertTrue('Validate' in self.browser.contents)
     1035        self.assertTrue('Validate courses' in self.browser.contents)
    10061036        # ... but a 100L CA does not see the button on other levels
    10071037        studylevel2 = StudentStudyLevel()
     
    10111041        L200_student_path = self.studycourse_path + '/200'
    10121042        self.browser.open(L200_student_path)
    1013         self.assertFalse('Validate' in self.browser.contents)
     1043        self.assertFalse('Validate courses' in self.browser.contents)
    10141044        self.browser.open(L110_student_path)
    10151045        self.browser.getLink("Validate courses").click()
    10161046        self.assertTrue('Course list has been validated' in self.browser.contents)
    10171047        self.assertTrue('courses validated' in self.browser.contents)
     1048        self.assertEqual(self.student['studycourse']['110'].validated_by,
     1049            'Helen Procter')
     1050        self.assertMatches(
     1051            '<YYYY-MM-DD hh:mm:ss>',
     1052            self.student['studycourse']['110'].validation_date.strftime(
     1053                "%Y-%m-%d %H:%M:%S"))
    10181054        self.browser.getLink("Reject courses").click()
    10191055        self.assertTrue('Course list request has been annulled.'
     
    10231059            '/contactstudent?subject=%s' % urlmessage)
    10241060        self.assertTrue('school fee paid' in self.browser.contents)
     1061        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
     1062        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
    10251063        IWorkflowInfo(self.student).fireTransition('register_courses')
    10261064        self.browser.open(L110_student_path)
     
    11421180        # and can perform actions
    11431181        IWorkflowInfo(self.student).fireTransition('admit')
     1182        # Students can't login if their account is suspended/deactivated
     1183        self.student.suspended = True
    11441184        self.browser.open(self.login_path)
    11451185        self.browser.getControl(name="form.login").value = self.student_id
    11461186        self.browser.getControl(name="form.password").value = 'spwd'
    11471187        self.browser.getControl("Login").click()
    1148         # Student can upload a passport picture
     1188        self.assertTrue(
     1189            'Your account has been deactivated.' in self.browser.contents)
     1190        self.student.suspended = False
     1191        self.browser.getControl("Login").click()
     1192        self.assertTrue(
     1193            'You logged in.' in self.browser.contents)
     1194        # Admitted student can upload a passport picture
    11491195        self.browser.open(self.student_path + '/change_portrait')
    11501196        ctrl = self.browser.getControl(name='passportuploadedit')
     
    11571203            '<img align="middle" height="125px" src="passport.jpg" />'
    11581204            in self.browser.contents)
     1205        # Students can open admission letter
     1206        self.browser.getLink("Base Data").click()
     1207        self.browser.getLink("Download admission letter").click()
     1208        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     1209        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
    11591210        # Student can view the clearance data
     1211        self.browser.open(self.student_path)
    11601212        self.browser.getLink("Clearance Data").click()
    11611213        # Student can't open clearance edit form before starting clearance
     
    11701222        self.assertMatches('...Not all required fields filled...',
    11711223                           self.browser.contents)
    1172         self.student.email = 'aa@aa.ng'
     1224        self.browser.open(self.student_path + '/edit_base')
     1225        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
     1226        self.browser.getControl("Save").click()
    11731227        self.browser.open(self.student_path + '/start_clearance')
    11741228        self.browser.getControl(name="ac_series").value = '3'
     
    11851239        # Set the correct owner
    11861240        self.existing_clrac.owner = self.student_id
     1241        # clr_code might be set (and thus returns None) due importing
     1242        # an empty clr_code column.
     1243        self.student.clr_code = None
    11871244        self.browser.getControl("Start clearance now").click()
    11881245        self.assertMatches('...Clearance process has been started...',
     
    12351292        self.browser.getControl("Create course list now").click()
    12361293        self.browser.getLink("100").click()
    1237         self.browser.getLink("Add and remove courses").click()
     1294        self.browser.getLink("Edit course list").click()
    12381295        self.browser.getControl("Add course ticket").click()
    12391296        self.browser.getControl(name="form.course").value = ['COURSE1']
     
    12481305        self.browser.getControl("Create course list now").click()
    12491306        self.browser.getLink("200").click()
    1250         self.browser.getLink("Add and remove courses").click()
     1307        self.browser.getLink("Edit course list").click()
    12511308        self.browser.getControl("Add course ticket").click()
    12521309        self.browser.getControl(name="form.course").value = ['COURSE1']
     
    12861343        self.assertEqual(self.student.state, 'courses registered')
    12871344        return
     1345
     1346    def test_student_clearance_wo_clrcode(self):
     1347        IWorkflowState(self.student).setState('clearance started')
     1348        self.browser.open(self.login_path)
     1349        self.browser.getControl(name="form.login").value = self.student_id
     1350        self.browser.getControl(name="form.password").value = 'spwd'
     1351        self.browser.getControl("Login").click()
     1352        self.student.clearance_locked = False
     1353        self.browser.open(self.edit_clearance_path)
     1354        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
     1355        self.browser.getControl("Save and request clearance").click()
     1356        self.assertMatches('...Clearance has been requested...',
     1357                           self.browser.contents)
    12881358
    12891359    def test_manage_payments(self):
     
    15161586        # The new SFE-0 pin can be used for starting new session
    15171587        self.browser.open(self.studycourse_path)
    1518         self.browser.getLink('Start session').click()
     1588        self.browser.getLink('Start new session').click()
    15191589        pin = self.app['accesscodes']['SFE-0'].keys()[0]
    15201590        parts = pin.split('-')[1:]
     
    15281598        return
    15291599
    1530     def test_postgraduate_payments(self):
     1600    def test_student_previous_payments(self):
     1601        configuration = createObject('waeup.SessionConfiguration')
     1602        configuration.academic_session = 2000
     1603        configuration.clearance_fee = 3456.0
     1604        configuration.booking_fee = 123.4
     1605        self.student['studycourse'].entry_session = 2002
     1606        self.app['configuration'].addSessionConfiguration(configuration)
     1607        configuration2 = createObject('waeup.SessionConfiguration')
     1608        configuration2.academic_session = 2003
     1609        configuration2.clearance_fee = 3456.0
     1610        configuration2.booking_fee = 123.4
     1611        self.student['studycourse'].entry_session = 2002
     1612        self.app['configuration'].addSessionConfiguration(configuration2)
     1613        # Login
     1614        self.browser.open(self.login_path)
     1615        self.browser.getControl(name="form.login").value = self.student_id
     1616        self.browser.getControl(name="form.password").value = 'spwd'
     1617        self.browser.getControl("Login").click()
     1618
     1619        # Students can add previous school fee payment tickets in any state.
     1620        IWorkflowState(self.student).setState('courses registered')
     1621        self.browser.open(self.payments_path)
     1622        self.browser.getControl("Add online payment ticket").click()
     1623        self.browser.getControl(name="form.p_category").value = ['schoolfee']
     1624        self.browser.getControl("Create ticket").click()
     1625
     1626        # Amount cannot be determined since the state is not
     1627        # 'cleared' or 'returning'
     1628        self.assertMatches('...Amount could not be determined...',
     1629                           self.browser.contents)
     1630        self.assertMatches('...Would you like to pay for a previous session?...',
     1631                           self.browser.contents)
     1632
     1633        # Previous session payment form is provided
     1634        self.browser.getControl(name="form.p_category").value = ['schoolfee']
     1635        self.browser.getControl(name="form.p_session").value = ['2000']
     1636        self.browser.getControl(name="form.p_level").value = ['300']
     1637        self.browser.getControl("Create ticket").click()
     1638        self.assertMatches('...The previous session must not fall below...',
     1639                           self.browser.contents)
     1640        self.browser.getControl(name="form.p_category").value = ['schoolfee']
     1641        self.browser.getControl(name="form.p_session").value = ['2004']
     1642        self.browser.getControl(name="form.p_level").value = ['300']
     1643        self.browser.getControl("Create ticket").click()
     1644        self.assertMatches('...This is not a previous session...',
     1645                           self.browser.contents)
     1646        self.browser.getControl(name="form.p_category").value = ['schoolfee']
     1647        self.browser.getControl(name="form.p_session").value = ['2003']
     1648        self.browser.getControl(name="form.p_level").value = ['300']
     1649        self.browser.getControl("Create ticket").click()
     1650        self.assertMatches('...ticket created...',
     1651                           self.browser.contents)
     1652        ctrl = self.browser.getControl(name='val_id')
     1653        value = ctrl.options[0]
     1654        self.browser.getLink(value).click()
     1655        self.assertMatches('...Amount Authorized...',
     1656                           self.browser.contents)
     1657        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
     1658
     1659        # Payment session is properly set
     1660        self.assertEqual(self.student['payments'][value].p_session, 2003)
     1661        self.assertEqual(self.student['payments'][value].p_level, 300)
     1662
     1663        # We simulate the approval
     1664        self.browser.open(self.browser.url + '/fake_approve')
     1665        self.assertMatches('...Payment approved...',
     1666                          self.browser.contents)
     1667
     1668        # No AC has been created
     1669        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
     1670        self.assertTrue(self.student['payments'][value].ac is None)
     1671
     1672        # Current payment flag is set False
     1673        self.assertFalse(self.student['payments'][value].p_current)
     1674        return
     1675
     1676    def test_student_postgraduate_payments(self):
    15311677        self.certificate.study_mode = 'pg_ft'
    15321678        self.certificate.start_level = 999
     
    15521698                           self.browser.contents)
    15531699        # Payment session and level are current ones.
    1554         # Postgrads have to school_fee_1.
     1700        # Postgrads have to pay school_fee_1.
    15551701        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
    15561702        self.assertEqual(self.student['payments'][value].p_session, 2004)
     
    15651711        # The new SFE-0 pin can be used for starting session
    15661712        self.browser.open(self.studycourse_path)
    1567         self.browser.getLink('Start session').click()
     1713        self.browser.getLink('Start new session').click()
    15681714        pin = self.app['accesscodes']['SFE-0'].keys()[0]
    15691715        parts = pin.split('-')[1:]
     
    16051751        # The new SFE-1 pin can be used for starting new session
    16061752        self.browser.open(self.studycourse_path)
    1607         self.browser.getLink('Start session').click()
     1753        self.browser.getLink('Start new session').click()
    16081754        self.browser.getControl(name="ac_series").value = sfeseries
    16091755        self.browser.getControl(name="ac_number").value = sfenumber
     
    18471993        self.browser.getControl(name="form.identifier").value = '123'
    18481994        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
    1849         self.browser.getControl("Get login credentials").click()
     1995        self.browser.getControl("Send login credentials").click()
    18501996        self.assertTrue('An email with' in self.browser.contents)
    1851 
    1852     def test_reindex(self):
    1853         self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    1854         self.browser.open('http://localhost/app/reindex')
    1855         self.assertTrue('No catalog name provided' in self.browser.contents)
    1856         self.browser.open('http://localhost/app/reindex?ctlg=xyz')
    1857         self.assertTrue('xyz_catalog does not exist' in self.browser.contents)
    1858         cat = queryUtility(ICatalog, name='students_catalog')
    1859         results = cat.searchResults(student_id=(None, None))
    1860         self.assertEqual(len(results),1)
    1861         cat.clear()
    1862         results = cat.searchResults(student_id=(None, None))
    1863         self.assertEqual(len(results),0)
    1864         self.browser.open('http://localhost/app/reindex?ctlg=students')
    1865         self.assertTrue('1 students re-indexed' in self.browser.contents)
    1866         results = cat.searchResults(student_id=(None, None))
    1867         self.assertEqual(len(results),1)
    18681997
    18691998    def test_change_current_mode(self):
     
    18882017        self.assertTrue('Employer' in self.browser.contents)
    18892018
     2019    def test_activate_deactivate_buttons(self):
     2020        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     2021        self.browser.open(self.student_path)
     2022        self.browser.getLink("Deactivate").click()
     2023        self.assertTrue(
     2024            'Student account has been deactivated.' in self.browser.contents)
     2025        self.assertTrue(
     2026            'Base Data (account deactivated)' in self.browser.contents)
     2027        self.assertTrue(self.student.suspended)
     2028        self.browser.getLink("Activate").click()
     2029        self.assertTrue(
     2030            'Student account has been activated.' in self.browser.contents)
     2031        self.assertFalse(
     2032            'Base Data (account deactivated)' in self.browser.contents)
     2033        self.assertFalse(self.student.suspended)
     2034        # History messages have been added ...
     2035        self.browser.getLink("History").click()
     2036        self.assertTrue(
     2037            'Student account deactivated by Manager<br />' in self.browser.contents)
     2038        self.assertTrue(
     2039            'Student account activated by Manager<br />' in self.browser.contents)
     2040        # ... and actions have been logged.
     2041        logfile = os.path.join(
     2042            self.app['datacenter'].storage, 'logs', 'students.log')
     2043        logcontent = open(logfile).read()
     2044        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
     2045                        'K1000000 - account deactivated' in logcontent)
     2046        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
     2047                        'K1000000 - account activated' in logcontent)
     2048
     2049    def test_student_transfer(self):
     2050        # Add second certificate
     2051        self.certificate2 = createObject('waeup.Certificate')
     2052        self.certificate2.code = u'CERT2'
     2053        self.certificate2.study_mode = 'ug_ft'
     2054        self.certificate2.start_level = 999
     2055        self.certificate2.end_level = 999
     2056        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
     2057            self.certificate2)
     2058
     2059        # Add study level to old study course
     2060        studylevel = createObject(u'waeup.StudentStudyLevel')
     2061        studylevel.level = 200
     2062        self.student['studycourse'].addStudentStudyLevel(
     2063            self.certificate, studylevel)
     2064
     2065        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     2066        self.browser.open(self.student_path)
     2067        self.browser.getLink("Transfer").click()
     2068        self.browser.getControl(name="form.certificate").value = ['CERT2']
     2069        self.browser.getControl(name="form.current_session").value = ['2011']
     2070        self.browser.getControl(name="form.current_level").value = ['200']
     2071        self.browser.getControl("Transfer").click()
     2072        self.assertTrue(
     2073            'Current level does not match certificate levels'
     2074            in self.browser.contents)
     2075        self.browser.getControl(name="form.current_level").value = ['999']
     2076        self.browser.getControl("Transfer").click()
     2077        self.assertTrue('Successfully transferred' in self.browser.contents)
     2078
     2079        # Add study level to new study course
     2080        studylevel = createObject(u'waeup.StudentStudyLevel')
     2081        studylevel.level = 200
     2082        self.student['studycourse'].addStudentStudyLevel(
     2083            self.certificate, studylevel)
     2084
     2085        # Edit and add pages are locked for old study courses
     2086        self.browser.open(self.student_path + '/studycourse/manage')
     2087        self.assertFalse('The requested form is locked' in self.browser.contents)
     2088        self.browser.open(self.student_path + '/studycourse_1/manage')
     2089        self.assertTrue('The requested form is locked' in self.browser.contents)
     2090
     2091        self.browser.open(self.student_path + '/studycourse/start_session')
     2092        self.assertFalse('The requested form is locked' in self.browser.contents)
     2093        self.browser.open(self.student_path + '/studycourse_1/start_session')
     2094        self.assertTrue('The requested form is locked' in self.browser.contents)
     2095
     2096        IWorkflowState(self.student).setState('school fee paid')
     2097        self.browser.open(self.student_path + '/studycourse/add')
     2098        self.assertFalse('The requested form is locked' in self.browser.contents)
     2099        self.browser.open(self.student_path + '/studycourse_1/add')
     2100        self.assertTrue('The requested form is locked' in self.browser.contents)
     2101
     2102        self.browser.open(self.student_path + '/studycourse/200/manage')
     2103        self.assertFalse('The requested form is locked' in self.browser.contents)
     2104        self.browser.open(self.student_path + '/studycourse_1/200/manage')
     2105        self.assertTrue('The requested form is locked' in self.browser.contents)
     2106
     2107        self.browser.open(self.student_path + '/studycourse/200/validate_courses')
     2108        self.assertFalse('The requested form is locked' in self.browser.contents)
     2109        self.browser.open(self.student_path + '/studycourse_1/200/validate_courses')
     2110        self.assertTrue('The requested form is locked' in self.browser.contents)
     2111
     2112        self.browser.open(self.student_path + '/studycourse/200/reject_courses')
     2113        self.assertFalse('The requested form is locked' in self.browser.contents)
     2114        self.browser.open(self.student_path + '/studycourse_1/200/reject_courses')
     2115        self.assertTrue('The requested form is locked' in self.browser.contents)
     2116
     2117        self.browser.open(self.student_path + '/studycourse/200/add')
     2118        self.assertFalse('The requested form is locked' in self.browser.contents)
     2119        self.browser.open(self.student_path + '/studycourse_1/200/add')
     2120        self.assertTrue('The requested form is locked' in self.browser.contents)
     2121
     2122        self.browser.open(self.student_path + '/studycourse/200/edit')
     2123        self.assertFalse('The requested form is locked' in self.browser.contents)
     2124        self.browser.open(self.student_path + '/studycourse_1/200/edit')
     2125        self.assertTrue('The requested form is locked' in self.browser.contents)
     2126
    18902127class StudentRequestPWTests(StudentsFullSetup):
    18912128    # Tests for student registration
     
    18942131
    18952132    def test_request_pw(self):
    1896         # Student with wrong reg_number can't be found.
     2133        # Student with wrong number can't be found.
    18972134        self.browser.open('http://localhost/app/requestpw')
    18982135        self.browser.getControl(name="form.firstname").value = 'Anna'
    1899         self.browser.getControl(name="form.reg_number").value = 'anynumber'
     2136        self.browser.getControl(name="form.number").value = 'anynumber'
    19002137        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
    1901         self.browser.getControl("Get login credentials").click()
     2138        self.browser.getControl("Send login credentials").click()
    19022139        self.assertTrue('No student record found.'
    19032140            in self.browser.contents)
     
    19062143        self.browser.open('http://localhost/app/requestpw')
    19072144        self.browser.getControl(name="form.firstname").value = 'Johnny'
    1908         self.browser.getControl(name="form.reg_number").value = '123'
     2145        self.browser.getControl(name="form.number").value = '123'
    19092146        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
    1910         self.browser.getControl("Get login credentials").click()
     2147        self.browser.getControl("Send login credentials").click()
    19112148        self.assertTrue('No student record found.'
    19122149            in self.browser.contents)
     
    19142151        # password has been set and used.
    19152152        self.browser.getControl(name="form.firstname").value = 'Anna'
    1916         self.browser.getControl(name="form.reg_number").value = '123'
    1917         self.browser.getControl("Get login credentials").click()
     2153        self.browser.getControl(name="form.number").value = '123'
     2154        self.browser.getControl("Send login credentials").click()
    19182155        self.assertTrue('Your password has already been set and used.'
    19192156            in self.browser.contents)
     
    19222159        # The firstname field, used for verification, is not case-sensitive.
    19232160        self.browser.getControl(name="form.firstname").value = 'aNNa'
    1924         self.browser.getControl(name="form.reg_number").value = '123'
     2161        self.browser.getControl(name="form.number").value = '123'
    19252162        self.browser.getControl(name="form.email").value = 'new@yy.zz'
    1926         self.browser.getControl("Get login credentials").click()
     2163        self.browser.getControl("Send login credentials").click()
    19272164        # Yeah, we succeded ...
     2165        self.assertTrue('Your password request was successful.'
     2166            in self.browser.contents)
     2167        # We can also use the matric_number instead.
     2168        self.browser.open('http://localhost/app/requestpw')
     2169        self.browser.getControl(name="form.firstname").value = 'aNNa'
     2170        self.browser.getControl(name="form.number").value = '234'
     2171        self.browser.getControl(name="form.email").value = 'new@yy.zz'
     2172        self.browser.getControl("Send login credentials").click()
    19282173        self.assertTrue('Your password request was successful.'
    19292174            in self.browser.contents)
     
    19342179            email=('new@yy.zz', 'new@yy.zz')))
    19352180        self.assertEqual(self.student,results[0])
     2181        logfile = os.path.join(
     2182            self.app['datacenter'].storage, 'logs', 'main.log')
     2183        logcontent = open(logfile).read()
     2184        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
     2185                        '234 (K1000000) - new@yy.zz' in logcontent)
    19362186        return
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/test_dynamicroles.py

    r7811 r9209  
    2525    Test as APRMTest, Manageable)
    2626from waeup.kofa.testing import FunctionalLayer
     27from waeup.kofa.app import University
    2728from waeup.kofa.students.tests.test_browser import StudentsFullSetup
    2829from waeup.kofa.students import Student, StudentPrincipalRoleManager
     
    4849    def setUp(self):
    4950        super(StudentPrincipalRoleManagerFunctionalTests, self).setUp()
    50         self.officer_role = 'waeup.StudentsClearanceOfficer'
    5151        # assign clearance permissions for a virtual officer
    5252        prm = IPrincipalRoleManager(self.app['faculties']['fac1']['dep1'])
    5353        prm.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'alice')
     54        prm.assignRoleToPrincipal('waeup.local.PGClearanceOfficer', 'bob')
     55        prm.assignRoleToPrincipal('waeup.local.UGClearanceOfficer', 'anne')
    5456        return
    5557
     
    7476        # student
    7577        prm = IPrincipalRoleManager(self.student)
    76         result = prm.getRolesForPrincipal('bob')
     78        result = prm.getRolesForPrincipal('claus')
    7779        self.assertEqual(result, [])
    7880        return
     
    8385        prm = IPrincipalRoleManager(self.student)
    8486        result = prm.getRolesForPrincipal('alice')
    85         self.assertEqual(result, [(self.officer_role, Allow)])
     87        self.assertEqual(result, [('waeup.StudentsClearanceOfficer', Allow)])
     88        # Student is a UG student
     89        self.assertFalse(self.student.is_postgrad)
     90        result = prm.getRolesForPrincipal('bob')
     91        self.assertEqual(result, [('waeup.StudentsOfficer', Allow)])
     92        result = prm.getRolesForPrincipal('anne')
     93        self.assertEqual(result, [('waeup.StudentsClearanceOfficer', Allow)])
     94        # Make student a PG student
     95        self.certificate.study_mode = u'pg_ft'
     96        self.assertTrue(self.student.is_postgrad)
     97        result = prm.getRolesForPrincipal('bob')
     98        # The dynamic roles changed
     99        self.assertEqual(result, [('waeup.StudentsClearanceOfficer', Allow)])
     100        result = prm.getRolesForPrincipal('anne')
     101        self.assertEqual(result, [('waeup.StudentsOfficer', Allow)])
    86102        return
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/test_export.py

    r8621 r9209  
    4444            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
    4545            'firstname,lastname,matric_number,middlename,nationality,'
    46             'perm_address,phone,reg_number,sex,student_id,password,'
     46            'perm_address,phone,reg_number,sex,student_id,suspended,password,'
    4747            'state,history,certcode\r\n'
    4848
    4949            'my adm code,0,my clr code,1981-02-04#,anna@sample.com,,'
    50             'Anna,Tester,M123456,M.,NG,"Studentroad 21\nLagos 123456\n",'
    51             '+234-123-12345,123456,f,A111111,,created'
     50            'Anna,Tester,234,M.,NG,"Studentroad 21\nLagos 123456\n",'
     51            '+234-123-12345#,123,f,A111111,0,,created'
    5252            in result
    5353            )
     
    6464            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
    6565            'firstname,lastname,matric_number,middlename,nationality,'
    66             'perm_address,phone,reg_number,sex,student_id,password,'
     66            'perm_address,phone,reg_number,sex,student_id,suspended,password,'
    6767            'state,history,certcode\r\n'
    6868
    6969            'my adm code,0,my clr code,1981-02-04#,anna@sample.com,,'
    70             'Anna,Tester,M123456,M.,NG,"Studentroad 21\nLagos 123456\n",'
    71             '+234-123-12345,123456,f,A111111,,created'
     70            'Anna,Tester,234,M.,NG,"Studentroad 21\nLagos 123456\n",'
     71            '+234-123-12345#,123,f,A111111,0,,created'
    7272            in result
    7373            )
     
    8383            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
    8484            'firstname,lastname,matric_number,middlename,nationality,'
    85             'perm_address,phone,reg_number,sex,student_id,password,'
     85            'perm_address,phone,reg_number,sex,student_id,suspended,password,'
    8686            'state,history,certcode\r\n'
    8787
    8888            'my adm code,0,my clr code,1981-02-04#,anna@sample.com,,'
    89             'Anna,Tester,M123456,M.,NG,"Studentroad 21\nLagos 123456\n",'
    90             '+234-123-12345,123456,f,A111111,,created'
     89            'Anna,Tester,234,M.,NG,"Studentroad 21\nLagos 123456\n",'
     90            '+234-123-12345#,123,f,A111111,0,,created'
    9191            in result
    9292            )
     
    127127            'entry_mode,entry_session,previous_verdict,student_id\r\n'
    128128
    129             ',,,NY,,,NY,\r\n'
     129            ',,,0,,,0,\r\n'
    130130            )
    131131        return
     
    144144            'entry_mode,entry_session,previous_verdict,student_id\r\n'
    145145
    146             'CERT1,200,2012,NY,ug_ft,2010,NY,A111111\r\n'
     146            'CERT1,200,2012,0,ug_ft,2010,0,A111111\r\n'
    147147            )
    148148        return
     
    160160            'entry_mode,entry_session,previous_verdict,student_id\r\n'
    161161
    162             'CERT1,200,2012,NY,ug_ft,2010,NY,A111111\r\n'
     162            'CERT1,200,2012,0,ug_ft,2010,0,A111111\r\n'
    163163            )
    164164        return
     
    175175            'entry_mode,entry_session,previous_verdict,student_id\r\n'
    176176
    177             'CERT1,200,2012,NY,ug_ft,2010,NY,A111111\r\n'
     177            'CERT1,200,2012,0,ug_ft,2010,0,A111111\r\n'
    178178            )
    179179        return
     
    209209        self.assertEqual(
    210210            result,
    211             'level,level_session,level_verdict,student_id\r\n'
    212             ',,NY,\r\n'
     211            'level,level_session,level_verdict,validated_by,validation_date,'
     212            'student_id\r\n'
     213            ',,0,,,\r\n'
    213214            )
    214215        return
     
    225226        self.assertEqual(
    226227            result,
    227             'level,level_session,level_verdict,student_id\r\n'
    228             '100,2012,A,A111111\r\n'
     228            'level,level_session,level_verdict,validated_by,validation_date,'
     229            'student_id\r\n'
     230            '100,2012,A,,,A111111\r\n'
    229231            )
    230232        return
     
    239241        self.assertEqual(
    240242            result,
    241             'level,level_session,level_verdict,student_id\r\n'
    242             '100,2012,A,A111111\r\n'
     243            'level,level_session,level_verdict,validated_by,validation_date,'
     244            'student_id\r\n'
     245            '100,2012,A,,,A111111\r\n'
    243246            )
    244247        return
     
    252255        self.assertEqual(
    253256            result,
    254             'level,level_session,level_verdict,student_id\r\n'
    255             '100,2012,A,A111111\r\n'
     257            'level,level_session,level_verdict,validated_by,validation_date,'
     258            'student_id\r\n'
     259            '100,2012,A,,,A111111\r\n'
    256260            )
    257261        return
     
    371375        self.assertEqual(
    372376            result,
    373             'ac,amount_auth,creation_date,p_category,p_id,'
     377            'ac,amount_auth,creation_date,p_category,p_current,p_id,'
    374378            'p_item,p_level,p_session,p_state,payment_date,r_amount_approved,'
    375379            'r_code,r_desc,student_id\r\n'
    376380
    377             ',0.0,2012-04-01 13:12:01,schoolfee,,,,,unpaid,,0.0,,,\r\n'
     381            ',0.0,2012-04-01 13:12:01,schoolfee,1,,,,,unpaid,,0.0,,,\r\n'
    378382            )
    379383        return
     
    389393        self.assertEqual(
    390394            result,
    391             'ac,amount_auth,creation_date,p_category,p_id,'
     395            'ac,amount_auth,creation_date,p_category,p_current,p_id,'
    392396            'p_item,p_level,p_session,p_state,payment_date,r_amount_approved,'
    393397            'r_code,r_desc,student_id\r\n'
    394398
    395             '666,0.0,2012-04-01 13:12:01,schoolfee,my-id,'
     399            '666,0.0,2012-04-01 13:12:01,schoolfee,1,my-id,'
    396400            'p-item,100,2012,unpaid,2012-04-01 14:12:01,12.12,'
    397401            'r-code,,A111111\r\n'
     
    408412        self.assertEqual(
    409413            result,
    410             'ac,amount_auth,creation_date,p_category,p_id,'
     414            'ac,amount_auth,creation_date,p_category,p_current,p_id,'
    411415            'p_item,p_level,p_session,p_state,payment_date,r_amount_approved,'
    412416            'r_code,r_desc,student_id\r\n'
    413417
    414             '666,0.0,2012-04-01 13:12:01,schoolfee,my-id,'
     418            '666,0.0,2012-04-01 13:12:01,schoolfee,1,my-id,'
    415419            'p-item,100,2012,unpaid,2012-04-01 14:12:01,12.12,'
    416420            'r-code,,A111111\r\n'
     
    427431        self.assertEqual(
    428432            result,
    429             'ac,amount_auth,creation_date,p_category,p_id,'
     433            'ac,amount_auth,creation_date,p_category,p_current,p_id,'
    430434            'p_item,p_level,p_session,p_state,payment_date,r_amount_approved,'
    431435            'r_code,r_desc,student_id\r\n'
    432436
    433             '666,0.0,2012-04-01 13:12:01,schoolfee,my-id,'
     437            '666,0.0,2012-04-01 13:12:01,schoolfee,1,my-id,'
    434438            'p-item,100,2012,unpaid,2012-04-01 14:12:01,12.12,'
    435439            'r-code,,A111111\r\n'
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/tests/test_student.py

    r8735 r9209  
    2121import re
    2222import unittest
     23import grok
    2324from cStringIO import StringIO
    2425from datetime import tzinfo
    25 from zope.component import getUtility
     26from zope.component import getUtility, queryUtility, createObject
     27from zope.catalog.interfaces import ICatalog
    2628from zope.component.interfaces import IFactory
     29from zope.event import notify
    2730from zope.interface import verify
     31from zope.schema.interfaces import RequiredMissing
    2832from waeup.kofa.interfaces import IExtFileStore, IFileStoreNameChooser
    2933from waeup.kofa.students.export import EXPORTER_NAMES
     
    129133        studylevel = StudentStudyLevel()
    130134        self.assertRaises(
    131             TypeError, studylevel.addCourseTicket, department)
     135            TypeError, studylevel.addCourseTicket, department, department)
    132136
    133137    def test_booking_date(self):
     
    236240        return
    237241
     242class StudentTransferTests(StudentImportExportSetup):
     243
     244    layer = FunctionalLayer
     245
     246    def setUp(self):
     247        super(StudentTransferTests, self).setUp()
     248
     249        # Add additional certificate
     250        self.certificate2 = createObject('waeup.Certificate')
     251        self.certificate2.code = 'CERT2'
     252        self.certificate2.application_category = 'basic'
     253        self.certificate2.start_level = 200
     254        self.certificate2.end_level = 500
     255        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
     256            self.certificate2)
     257
     258        # Add student with subobjects
     259        student = Student()
     260        self.app['students'].addStudent(student)
     261        student = self.setup_student(student)
     262        notify(grok.ObjectModifiedEvent(student))
     263        self.student = self.app['students'][student.student_id]
     264        return
     265
     266    def test_transfer_student(self):
     267        self.assertRaises(
     268            RequiredMissing, self.student.transfer, self.certificate2)
     269        error = self.student.transfer(self.certificate2, current_session=1000)
     270        self.assertTrue(error == -1)
     271        error = self.student.transfer(self.certificate2, current_session=2013)
     272        self.assertTrue(error == None)
     273        self.assertEqual(self.student['studycourse_1'].certificate.code, 'CERT1')
     274        self.assertEqual(self.student['studycourse'].certificate.code, 'CERT2')
     275        self.assertEqual(self.student['studycourse_1'].current_session, 2012)
     276        self.assertEqual(self.student['studycourse'].current_session, 2013)
     277        self.assertEqual(self.student['studycourse'].entry_session,
     278            self.student['studycourse_1'].entry_session)
     279        self.assertEqual(self.student['studycourse_1'].__name__, 'studycourse_1')
     280        logfile = os.path.join(
     281            self.app['datacenter'].storage, 'logs', 'students.log')
     282        logcontent = open(logfile).read()
     283        self.assertTrue('system - K1000000 - transferred from CERT1 to CERT2'
     284            in logcontent)
     285        messages = ' '.join(self.student.history.messages)
     286        self.assertMatches(
     287            '...<YYYY-MM-DD hh:mm:ss> UTC - '
     288            'Transferred from CERT1 to CERT2 by system', messages)
     289
     290        # The students_catalog has been updated.
     291        cat = queryUtility(ICatalog, name='students_catalog')
     292        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
     293        results = [x for x in results]
     294        self.assertEqual(len(results), 0)
     295        results = cat.searchResults(certcode=('CERT2', 'CERT2'))
     296        results = [x for x in results]
     297        self.assertEqual(len(results), 1)
     298        assert results[0] is self.app['students'][self.student.student_id]
     299        results = cat.searchResults(current_session=(2013,2013))
     300        results = [x for x in results]
     301        self.assertEqual(len(results), 1)
     302        assert results[0] is self.app['students'][self.student.student_id]
     303
     304        # Students can be transferred (only) two times.
     305        error = self.student.transfer(self.certificate,
     306            current_session=2013)
     307        self.assertTrue(error == None)
     308        error = self.student.transfer(self.certificate2,
     309            current_session=2013)
     310        self.assertTrue(error == -3)
     311        self.assertEqual([i for i in self.student.keys()],
     312            [u'accommodation', u'payments', u'studycourse',
     313             u'studycourse_1', u'studycourse_2'])
     314
     315        # The students_catalog has been updated again.
     316        cat = queryUtility(ICatalog, name='students_catalog')
     317        results = cat.searchResults(certcode=('CERT1', 'CERT1'))
     318        results = [x for x in results]
     319        self.assertEqual(len(results), 1)
     320        assert results[0] is self.app['students'][self.student.student_id]
     321        return
    238322
    239323class StudentFactoryTest(FunctionalTestCase):
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/utils.py

    r8708 r9209  
    1919"""
    2020import grok
    21 from random import SystemRandom as r
    2221from time import time
    23 from datetime import datetime
    24 from zope.i18n import translate
    25 from zope.component import getUtility, createObject
    26 from reportlab.pdfgen import canvas
    2722from reportlab.lib import colors
    2823from reportlab.lib.units import cm
    29 from reportlab.lib.enums import TA_RIGHT
    3024from reportlab.lib.pagesizes import A4
    31 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
    32 from reportlab.platypus import (Frame, Paragraph, Image, PageBreak, Table,
    33                                 Spacer)
    34 from reportlab.platypus.tables import TableStyle
    35 from reportlab.platypus.flowables import PageBreak
    36 from zope.component import getUtility
     25from reportlab.lib.styles import getSampleStyleSheet
     26from reportlab.platypus import Paragraph, Image, Table, Spacer
     27from zope.component import getUtility, createObject
    3728from zope.formlib.form import setUpEditWidgets
    38 
     29from zope.i18n import translate
    3930from waeup.kofa.interfaces import (
    4031    IExtFileStore, IKofaUtils, RETURNING, PAID, CLEARED)
    4132from waeup.kofa.interfaces import MessageFactory as _
    4233from waeup.kofa.students.interfaces import IStudentsUtils
    43 from waeup.kofa.utils.helpers import now
    4434
    4535SLIP_STYLE = [
     
    127117    #data.append([Spacer(1, 12)])
    128118    portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
     119
     120    f_label = formatted_label(size=12) % _('Name')
     121    f_label = Paragraph(f_label, style["Normal"])
     122    f_text = formatted_text(studentview.context.display_fullname, size=12)
     123    f_text = Paragraph(f_text, style["Normal"])
     124    data_right.append([f_label,f_text])
     125
    129126    for widget in studentview.widgets:
    130         if widget.name == 'form.adm_code':
     127        if 'name' in widget.name:
    131128            continue
    132129        f_label = formatted_label(size=12) % translate(
     
    137134        f_text = Paragraph(f_text, style["Normal"])
    138135        data_right.append([f_label,f_text])
     136
     137    if hasattr(studentview.context, 'certcode'):
     138        f_label = formatted_label(size=12) % _('Study Course')
     139        f_label = Paragraph(f_label, style["Normal"])
     140        f_text = formatted_text(
     141            studentview.context['studycourse'].certificate.longtitle(), size=12)
     142        f_text = Paragraph(f_text, style["Normal"])
     143        data_right.append([f_label,f_text])
     144
     145        f_label = formatted_label(size=12) % _('Department')
     146        f_label = Paragraph(f_label, style["Normal"])
     147        f_text = formatted_text(
     148            studentview.context[
     149            'studycourse'].certificate.__parent__.__parent__.longtitle(),
     150            size=12)
     151        f_text = Paragraph(f_text, style["Normal"])
     152        data_right.append([f_label,f_text])
     153
     154        f_label = formatted_label(size=12) % _('Faculty')
     155        f_label = Paragraph(f_label, style["Normal"])
     156        f_text = formatted_text(
     157            studentview.context[
     158            'studycourse'].certificate.__parent__.__parent__.__parent__.longtitle(),
     159            size=12)
     160        f_text = Paragraph(f_text, style["Normal"])
     161        data_right.append([f_label,f_text])
     162
    139163    table_left = Table(data_left,style=SLIP_STYLE)
    140164    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[5*cm, 6*cm])
     
    165189    return table
    166190
     191def get_signature_table(signatures, lang='en'):
     192    """Return a reportlab table containing signature fields (with date).
     193    """
     194    style = getSampleStyleSheet()
     195    space_width = 0.4  # width in cm of space between signatures
     196    table_width = 16.0 # supposed width of signature table in cms
     197    # width of signature cells in cm...
     198    sig_col_width = table_width - ((len(signatures) - 1) * space_width)
     199    sig_col_width = sig_col_width / len(signatures)
     200    data = []
     201    col_widths = [] # widths of columns
     202
     203    sig_style = [
     204        ('VALIGN',(0,-1),(-1,-1),'TOP'),
     205        ('FONT', (0,0), (-1,-1), 'Helvetica-BoldOblique', 12),
     206        ('BOTTOMPADDING', (0,0), (-1,0), 36),
     207        ('TOPPADDING', (0,-1), (-1,-1), 0),
     208        ]
     209    for num, elem in enumerate(signatures):
     210        # draw a line above each signature cell (not: empty cells in between)
     211        sig_style.append(
     212            ('LINEABOVE', (num*2,-1), (num*2, -1), 1, colors.black))
     213
     214    row = []
     215    for signature in signatures:
     216        row.append(trans(_('Date:'), lang))
     217        row.append('')
     218        if len(signatures) > 1:
     219            col_widths.extend([sig_col_width*cm, space_width*cm])
     220        else:
     221            col_widths.extend([sig_col_width/2*cm, sig_col_width/2*cm])
     222            row.append('') # empty spaceholder on right
     223    data.append(row[:-1])
     224    data.extend(([''],)*3) # insert 3 empty rows...
     225    row = []
     226    for signature in signatures:
     227        row.append(Paragraph(trans(signature, lang), style["Normal"]))
     228        row.append('')
     229    data.append(row[:-1])
     230    table = Table(data, style=sig_style, repeatRows=len(data),
     231                  colWidths=col_widths)
     232    return table
     233
    167234def docs_as_flowables(view, lang='en'):
    168235    """Create reportlab flowables out of scanned docs.
     
    190257            if img_path is None:
    191258                pass
    192             elif not img_path.endswith('.jpg'):
     259            elif not img_path[-4:] in ('.jpg', '.JPG'):
    193260                # reportlab requires jpg images, I think.
    194                 f_text = Paragraph('%s (Not displayable)' % (
     261                f_text = Paragraph('%s (not displayable)' % (
    195262                    viewlet.title,), ENTRY1_STYLE)
    196263            else:
     
    202269    return data
    203270
    204 def insert_footer(pdf,width,style,text=None, number_of_pages=1):
    205       """Render the whole footer frame.
    206       """
    207       story = []
    208       frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
    209       tz = getUtility(IKofaUtils).tzinfo
    210       timestamp = now(tz).strftime("%d/%m/%Y %H:%M:%S %Z")
    211       left_text = '<font size=10>%s</font>' % timestamp
    212       story.append(Paragraph(left_text, style["Normal"]))
    213       frame_footer.addFromList(story,pdf)
    214       story = []
    215       frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
    216       portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
    217       right_text = translate(_('<font size=10>${a} Page ${b} of ${c}</font>',
    218           mapping = {'a':text, 'b':pdf.getPageNumber(), 'c':number_of_pages}),
    219           'waeup.kofa', target_language=portal_language)
    220       story.append(Paragraph(right_text, style["Right"]))
    221       frame_footer.addFromList(story,pdf)
    222 
    223271class StudentsUtils(grok.GlobalUtility):
    224272    """A collection of methods subject to customization.
     
    227275
    228276    def getReturningData(self, student):
    229         """ This method defines what happens after school fee payment
     277        """ Define what happens after school fee payment
    230278        depending on the student's senate verdict.
    231279
     
    238286
    239287    def setReturningData(self, student):
    240         """ This method defines what happens after school fee payment
    241         depending on the student's senate verdict. It folllows
    242         the same algorithm as getReturningData but it also sets the new
    243         values
    244 
    245         In the base configuration current level is always increased
    246         by 100 no matter which verdict has been assigned.
     288        """ Define what happens after school fee payment
     289        depending on the student's senate verdict.
     290
     291        This method folllows the same algorithm as getReturningData but
     292        it also sets the new values.
    247293        """
    248294        new_session, new_level = self.getReturningData(student)
     
    250296        student['studycourse'].current_session = new_session
    251297        verdict = student['studycourse'].current_verdict
    252         student['studycourse'].current_verdict = 'NY'
     298        student['studycourse'].current_verdict = '0'
    253299        student['studycourse'].previous_verdict = verdict
    254300        return
    255301
    256     def setPaymentDetails(self, category, student):
     302    def setPaymentDetails(self, category, student,
     303            previous_session, previous_level):
    257304        """Create Payment object and set the payment data of a student for
    258305        the payment category specified.
    259306
    260307        """
    261         details = {}
    262308        p_item = u''
    263309        amount = 0.0
    264         error = u''
    265         p_session = student['studycourse'].current_session
    266         p_level = student['studycourse'].current_level
     310        if previous_session:
     311            p_session = previous_session
     312            p_level = previous_level
     313            p_current = False
     314        else:
     315            p_session = student['studycourse'].current_session
     316            p_level = student['studycourse'].current_level
     317            p_current = True
    267318        session = str(p_session)
    268319        try:
     
    276327            except (AttributeError, TypeError):
    277328                return _('Study course data are incomplete.'), None
    278             if student.state == CLEARED:
    279                 amount = getattr(certificate, 'school_fee_1', 0.0)
    280             elif student.state == RETURNING:
    281                 # In case of returning school fee payment the payment session
    282                 # and level contain the values of the session the student
    283                 # has paid for.
    284                 p_session, p_level = self.getReturningData(student)
    285                 amount = getattr(certificate, 'school_fee_2', 0.0)
    286             elif student.is_postgrad and student.state == PAID:
    287                 # Returning postgraduate students also pay for the next session
    288                 # but their level always remains the same.
    289                 p_session += 1
    290                 amount = getattr(certificate, 'school_fee_2', 0.0)
     329            if previous_session:
     330                if previous_session < student['studycourse'].entry_session:
     331                    return _('The previous session must not fall below '
     332                             'your entry session.'), None
     333                if previous_session > student['studycourse'].current_session - 1:
     334                    return _('This is not a previous session.'), None
     335                if previous_level == 100:
     336                    amount = getattr(certificate, 'school_fee_1', 0.0)
     337                else:
     338                    amount = getattr(certificate, 'school_fee_2', 0.0)
     339            else:
     340                if student.state == CLEARED:
     341                    amount = getattr(certificate, 'school_fee_1', 0.0)
     342                elif student.state == RETURNING:
     343                    # In case of returning school fee payment the payment session
     344                    # and level contain the values of the session the student
     345                    # has paid for.
     346                    p_session, p_level = self.getReturningData(student)
     347                    amount = getattr(certificate, 'school_fee_2', 0.0)
     348                elif student.is_postgrad and student.state == PAID:
     349                    # Returning postgraduate students also pay for the next session
     350                    # but their level always remains the same.
     351                    p_session += 1
     352                    amount = getattr(certificate, 'school_fee_2', 0.0)
    291353        elif category == 'clearance':
    292             p_item = student['studycourse'].certificate.code
     354            try:
     355                p_item = student['studycourse'].certificate.code
     356            except (AttributeError, TypeError):
     357                return _('Study course data are incomplete.'), None
    293358            amount = academic_session.clearance_fee
    294359        elif category == 'bed_allocation':
     
    296361            amount = academic_session.booking_fee
    297362        if amount in (0.0, None):
    298             return _(u'Amount could not be determined.'), None
     363            return _('Amount could not be determined.' +
     364                     ' Would you like to pay for a previous session?'), None
    299365        for key in student['payments'].keys():
    300366            ticket = student['payments'][key]
     
    303369               ticket.p_item == p_item and \
    304370               ticket.p_session == p_session:
    305                   return _('This type of payment has already been made.'), None
     371                  return _('This type of payment has already been made.' +
     372                           ' Would you like to pay for a previous session?'), None
    306373        payment = createObject(u'waeup.StudentOnlinePayment')
    307         timestamp = "%d" % int(time()*1000)
     374        timestamp = ("%d" % int(time()*10000))[1:]
    308375        payment.p_id = "p%s" % timestamp
    309376        payment.p_category = category
     
    311378        payment.p_session = p_session
    312379        payment.p_level = p_level
     380        payment.p_current = p_current
    313381        payment.amount_auth = amount
    314382        return None, payment
     
    330398        entry_session = studycourse.entry_session
    331399        current_level = studycourse.current_level
    332         if not (entry_session and current_level and certificate):
    333             return
     400        if None in (entry_session, current_level, certificate):
     401            return d
    334402        end_level = certificate.end_level
    335         if entry_session == grok.getSite()['hostels'].accommodation_session:
     403        if current_level == 10:
     404            bt = 'pr'
     405        elif entry_session == grok.getSite()['hostels'].accommodation_session:
    336406            bt = 'fr'
    337407        elif current_level >= end_level:
     
    355425        return available_beds[0]
    356426
     427    def renderPDFAdmissionLetter(self, view, student=None):
     428        """Render pdf admission letter.
     429        """
     430        # XXX: we have to fix the import problems here.
     431        from waeup.kofa.browser.interfaces import IPDFCreator
     432        from waeup.kofa.browser.pdf import format_html, NOTE_STYLE
     433        if student is None:
     434            return
     435        style = getSampleStyleSheet()
     436        creator = getUtility(IPDFCreator)
     437        data = []
     438        doc_title = view.label
     439        author = '%s (%s)' % (view.request.principal.title,
     440                              view.request.principal.id)
     441        footer_text = view.label
     442        if getattr(student, 'student_id', None) is not None:
     443            footer_text = "%s - %s - " % (student.student_id, footer_text)
     444
     445        # Admission text
     446        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
     447        inst_name = grok.getSite()['configuration'].name
     448        text = trans(_(
     449            'This is to inform you that you have been provisionally'
     450            ' admitted into ${a} as follows:', mapping = {'a': inst_name}),
     451            portal_language)
     452        html = format_html(text)
     453        data.append(Paragraph(html, NOTE_STYLE))
     454        data.append(Spacer(1, 20))
     455
     456        # Student data
     457        data.append(render_student_data(view))
     458
     459        # Insert history
     460        data.append(Spacer(1, 20))
     461        datelist = student.history.messages[0].split()[0].split('-')
     462        creation_date = u'%s/%s/%s' % (datelist[2], datelist[1], datelist[0])
     463        text = trans(_(
     464            'Your Kofa student record was created on ${a}.',
     465            mapping = {'a': creation_date}),
     466            portal_language)
     467        html = format_html(text)
     468        data.append(Paragraph(html, NOTE_STYLE))
     469
     470        # Create pdf stream
     471        view.response.setHeader(
     472            'Content-Type', 'application/pdf')
     473        pdf_stream = creator.create_pdf(
     474            data, None, doc_title, author=author, footer=footer_text,
     475            note=None)
     476        return pdf_stream
     477
    357478    def renderPDF(self, view, filename='slip.pdf', student=None,
    358479                  studentview=None, tableheader=None, tabledata=None,
    359                   note=None):
     480                  note=None, signatures=None):
    360481        """Render pdf slips for various pages.
    361482        """
     
    373494            footer_text = "%s - %s - " % (student.student_id, footer_text)
    374495
     496        # Insert history
     497        if not filename.startswith('payment'):
     498            data.extend(creator.fromStringList(student.history.messages))
     499
    375500        # Insert student data table
    376501        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
     
    381506
    382507        # Insert widgets
    383         data.append(Paragraph(view.title, style["Heading3"]))
    384         portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
    385         separators = getattr(self, 'SEPARATORS_DICT', {})
    386         table = creator.getWidgetsTable(
    387             view.form_fields, view.context, None, lang=portal_language,
    388             separators=separators)
    389         data.append(table)
     508        if view.form_fields:
     509            data.append(Paragraph(view.title, style["Heading3"]))
     510            portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
     511            separators = getattr(self, 'SEPARATORS_DICT', {})
     512            table = creator.getWidgetsTable(
     513                view.form_fields, view.context, None, lang=portal_language,
     514                separators=separators)
     515            data.append(table)
    390516
    391517        # Insert scanned docs
     
    399525            contenttable = render_table_data(tableheader,tabledata)
    400526            data.append(contenttable)
     527
     528        # Insert signatures
     529        if signatures:
     530            data.append(Spacer(1, 20))
     531            signaturetable = get_signature_table(signatures)
     532            data.append(signaturetable)
    401533
    402534        view.response.setHeader(
     
    412544
    413545    VERDICTS_DICT = {
    414         'NY': _('(not yet)'),
     546        '0': _('(not yet)'),
    415547        'A': 'Successful student',
    416548        'B': 'Student with carryover courses',
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/viewlets.py

    r8736 r9209  
    1 ## $Id$
     13## $Id$
    22##
    33## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
     
    150150
    151151class ContactActionButton(ManageActionButton):
    152     grok.order(4)
     152    grok.order(5)
    153153    grok.context(IStudent)
    154154    grok.view(StudentBaseDisplayFormPage)
     
    165165    text = _('Manage')
    166166    target = 'manage_base'
     167
     168class AdmissionSlipActionButton(ManageActionButton):
     169    grok.order(4)
     170    grok.context(IStudent)
     171    grok.view(StudentBaseDisplayFormPage)
     172    grok.require('waeup.viewStudent')
     173    icon = 'actionicon_pdf.png'
     174    text = _('Download admission letter')
     175    target = 'admission_slip.pdf'
     176
     177class StudentTransfernButton(ManageActionButton):
     178    grok.order(6)
     179    grok.context(IStudent)
     180    grok.view(StudentBaseDisplayFormPage)
     181    grok.require('waeup.manageStudent')
     182    text = _('Transfer student')
     183    target = 'transfer'
     184    icon = 'actionicon_redo.png'
     185
     186class StudentDeactivateActionButton(ManageActionButton):
     187    grok.order(7)
     188    grok.context(IStudent)
     189    grok.view(StudentBaseDisplayFormPage)
     190    grok.require('waeup.manageStudent')
     191    text = _('Deactivate account')
     192    target = 'deactivate'
     193    icon = 'actionicon_traffic_lights_red.png'
     194
     195    @property
     196    def target_url(self):
     197        if self.context.suspended:
     198            return ''
     199        return self.view.url(self.view.context, self.target)
     200
     201    @property
     202    def onclick(self):
     203        return "return window.confirm(%s);" % _(
     204            "'A history message will be added. Are you sure?'")
     205
     206class StudentActivateActionButton(ManageActionButton):
     207    grok.order(7)
     208    grok.context(IStudent)
     209    grok.view(StudentBaseDisplayFormPage)
     210    grok.require('waeup.manageStudent')
     211    text = _('Activate account')
     212    target = 'activate'
     213    icon = 'actionicon_traffic_lights_green.png'
     214
     215    @property
     216    def target_url(self):
     217        if not self.context.suspended:
     218            return ''
     219        return self.view.url(self.view.context, self.target)
     220
     221    @property
     222    def onclick(self):
     223        return "return window.confirm(%s);" % _(
     224            "'A history message will be added. Are you sure?'")
    167225
    168226class StudentClearanceManageActionButton(ManageActionButton):
     
    231289    target = 'view_personal'
    232290
     291class StudentPersonalManageActionButton(ManageActionButton):
     292    grok.order(1)
     293    grok.context(IStudent)
     294    grok.view(StudentPersonalDisplayFormPage)
     295    grok.require('waeup.manageStudent')
     296    text = _('Manage')
     297    target = 'manage_personal'
     298
    233299class StudentPersonalEditActionButton(ManageActionButton):
    234     grok.order(1)
     300    grok.order(2)
    235301    grok.context(IStudent)
    236302    grok.view(StudentPersonalDisplayFormPage)
    237     grok.require('waeup.viewStudent')
     303    grok.require('waeup.handleStudent')
    238304    text = _('Edit')
    239305    target = 'edit_personal'
     
    247313    target = 'manage'
    248314
     315    @property
     316    def target_url(self):
     317        if self.context.is_current:
     318            return self.view.url(self.view.context, self.target)
     319        return False
     320
    249321class StudyLevelManageActionButton(ManageActionButton):
    250322    grok.order(1)
     
    254326    text = _('Manage')
    255327    target = 'manage'
     328
     329    @property
     330    def target_url(self):
     331        is_current = self.context.__parent__.is_current
     332        if not is_current:
     333            return ''
     334        return self.view.url(self.view.context, self.target)
    256335
    257336class StudentValidateCoursesActionButton(ManageActionButton):
     
    266345    @property
    267346    def target_url(self):
     347        is_current = self.context.__parent__.is_current
    268348        if self.context.student.state != REGISTERED or \
    269             str(self.context.__parent__.current_level) != self.context.__name__:
     349            str(self.context.__parent__.current_level) != self.context.__name__ or\
     350            not is_current:
    270351            return ''
    271352        return self.view.url(self.view.context, self.target)
     
    282363    @property
    283364    def target_url(self):
     365        is_current = self.context.__parent__.is_current
    284366        if self.context.student.state not in (VALIDATED, REGISTERED) or \
    285             str(self.context.__parent__.current_level) != self.context.__name__:
     367            str(self.context.__parent__.current_level) != self.context.__name__ or\
     368            not is_current:
    286369            return ''
    287370        return self.view.url(self.view.context, self.target)
     
    296379    target = 'course_registration.pdf'
    297380
     381    @property
     382    def target_url(self):
     383        is_current = self.context.__parent__.is_current
     384        if not is_current:
     385            return ''
     386        return self.view.url(self.view.context, self.target)
     387
    298388class CourseTicketManageActionButton(ManageActionButton):
    299389    grok.order(1)
     
    313403
    314404class PaymentReceiptActionButton(ManageActionButton):
    315     grok.order(1)
     405    grok.order(9) # This button should always be the last one.
    316406    grok.context(IStudentOnlinePayment)
    317407    grok.view(OnlinePaymentDisplayFormPage)
     
    327417        return self.view.url(self.view.context, self.target)
    328418
    329 
    330419class ApprovePaymentActionButton(ManageActionButton):
    331     grok.order(2)
     420    grok.order(8)
    332421    grok.context(IStudentOnlinePayment)
    333422    grok.view(OnlinePaymentDisplayFormPage)
     
    437526    grok.require('waeup.handleStudent')
    438527    icon = 'actionicon_start.gif'
    439     text = _('Start session')
     528    text = _('Start new session')
    440529    target = 'start_session'
    441530
    442531    @property
    443532    def target_url(self):
    444         if self.context.next_session_allowed:
     533        if self.context.next_session_allowed and self.context.is_current:
    445534            return self.view.url(self.view.context, self.target)
    446535        return False
     
    460549        condition2 = str(student['studycourse'].current_level) in \
    461550            self.view.context.keys()
    462         if condition1 or condition2:
     551        condition3 = not self.context.is_current
     552        if condition1 or condition2 or condition3:
    463553            return ''
    464554        return self.view.url(self.view.context, self.target)
     
    469559    grok.view(StudyLevelDisplayFormPage)
    470560    grok.require('waeup.handleStudent')
    471     text = _('Add and remove courses')
     561    text = _('Edit course list')
    472562    target = 'edit'
    473563
     
    478568        condition2 = student[
    479569            'studycourse'].current_level != self.view.context.level
    480         if condition1 or condition2:
     570        is_current = self.context.__parent__.is_current
     571        if condition1 or condition2 or not is_current:
    481572            return ''
    482573        return self.view.url(self.view.context, self.target)
     
    542633    @property
    543634    def targets(self):
    544         student_url = self.view.application_url() + (
    545             '/students/%s' % self.request.principal.id)
    546         #app_slip = getUtility(IExtFileStore).getFileByContext(
    547         #    self.context.student, 'application_slip')
     635        student = grok.getSite()['students'][self.request.principal.id]
     636        student_url = self.view.url(student)
     637        app_slip = getUtility(IExtFileStore).getFileByContext(
     638            student, 'application_slip')
    548639        targets = []
    549         #if app_slip:
    550         #    targets = [{'url':student_url + '/application_slip', 'title':'Application Slip'},]
     640        if app_slip:
     641            targets = [{'url':student_url + '/application_slip', 'title':'Application Slip'},]
    551642        targets += [
    552643            {'url':student_url, 'title':'Base Data'},
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/vocabularies.py

    r8766 r9209  
    3636nats_vocab = SimpleKofaVocabulary(*COUNTRIES)
    3737
    38 def study_levels(studycourse):
    39     if studycourse.certificate is not None:
    40         start_level = int(studycourse.certificate.start_level)
    41         end_level = int(studycourse.certificate.end_level)
     38def study_levels(context):
     39    certificate = getattr(context, 'certificate', None)
     40    if  certificate is not None:
     41        start_level = int(certificate.start_level)
     42        end_level = int(certificate.end_level)
    4243        if start_level == 999 or end_level == 999:
    4344            levels = [999]
     
    5051    else:
    5152        # default level range
    52         levels = [level for level in range(100,1000,10) if level % 100 < 30]
     53        levels = [10, ]
     54        levels += [level for level in range(100,1000,10) if level % 100 < 30]
     55        levels.append(999)
    5356    return levels
    5457
     
    6871
    6972    def getTitle(self, context, value):
    70         if context.certificate is not None:
    71             start_level = int(context.certificate.start_level)
    72             end_level = int(context.certificate.end_level)
     73        certificate = getattr(context, 'certificate', None)
     74        if certificate is not None:
     75            start_level = int(certificate.start_level)
     76            end_level = int(certificate.end_level)
    7377        else:
    7478            # default level range
    75             start_level = 100
     79            start_level = 10
    7680            end_level = 1000
    7781        if start_level == 999 or end_level == 999:
     
    7983                return _('Error: wrong level id ${value}',
    8084                    mapping={'value': value})
     85        if value == 999:
    8186            return course_levels.by_value[999].title
    8287        if value < start_level or value > end_level + 120:
  • main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/students/workflow.py

    r8471 r9209  
    22"""
    33import grok
     4from datetime import datetime
    45from zope.component import getUtility
    56from hurry.workflow.workflow import Transition, WorkflowState, NullCondition
     
    1213from waeup.kofa.workflow import KofaWorkflow, KofaWorkflowInfo
    1314from waeup.kofa.students.interfaces import IStudent, IStudentsUtils
     15from waeup.kofa.utils.helpers import get_current_principal
    1416
    1517
    1618IMPORTABLE_STATES = (ADMITTED, CLEARANCE, REQUESTED, CLEARED, PAID, RETURNING,
    1719    REGISTERED, VALIDATED)
     20
     21FORBIDDEN_POSTGRAD_STATES = (RETURNING, REGISTERED, VALIDATED)
    1822
    1923REGISTRATION_TRANSITIONS = (
     
    2327        source = None,
    2428        condition = NullCondition,
    25         msg = _('Student record created'),
     29        msg = _('Record created'),
    2630        destination = CREATED),
    2731
     
    2933        transition_id = 'admit',
    3034        title = _('Admit student'),
    31         msg = _('Student admitted'),
     35        msg = _('Admitted'),
    3236        source = CREATED,
    3337        destination = ADMITTED),
     
    6367    Transition(
    6468        transition_id = 'reset3',
    65         title = _('Reset to clearance'),
    66         msg = _("Reset to 'clearance'"),
     69        title = _('Reset to clearance started'),
     70        msg = _("Reset to 'clearance started'"),
    6771        source = REQUESTED,
    6872        destination = CLEARANCE),
     
    7781    Transition(
    7882        transition_id = 'reset4',
    79         title = _('Reset to clearance'),
    80         msg = _("Reset to 'clearance'"),
     83        title = _('Reset to clearance started'),
     84        msg = _("Reset to 'clearance started'"),
    8185        source = CLEARED,
    8286        destination = CLEARANCE),
     
    8589        transition_id = 'pay_first_school_fee',
    8690        title = _('Pay school fee'),
    87         msg = _('School fee paid'),
     91        msg = _('First school fee payment made'),
    8892        source = CLEARED,
    8993        destination = PAID),
     
    9296        transition_id = 'approve_first_school_fee',
    9397        title = _('Approve payment'),
    94         msg = _('School fee payment approved'),
     98        msg = _('First school fee payment approved'),
    9599        source = CLEARED,
    96100        destination = PAID),
     
    106110        transition_id = 'pay_school_fee',
    107111        title = _('Pay school fee'),
    108         msg = _('Payment made'),
     112        msg = _('School fee payment made'),
    109113        source = RETURNING,
    110114        destination = PAID),
     
    112116    Transition(
    113117        transition_id = 'pay_pg_fee',
    114         title = _('Pay postgraduate school fee'),
    115         msg = _('Payment made'),
     118        title = _('Pay PG school fee'),
     119        msg = _('PG school fee payment made'),
    116120        source = PAID,
    117121        destination = PAID),
     
    119123    Transition(
    120124        transition_id = 'approve_school_fee',
    121         title = _('Approve payment'),
     125        title = _('Approve school fee payment'),
    122126        msg = _('School fee payment approved'),
    123127        source = RETURNING,
     
    126130    Transition(
    127131        transition_id = 'approve_pg_fee',
    128         title = _('Approve postgraduate payment'),
    129         msg = _('School fee payment approved'),
     132        title = _('Approve PG school fee payment'),
     133        msg = _('PG school fee payment approved'),
    130134        source = PAID,
    131135        destination = PAID),
     
    147151    Transition(
    148152        transition_id = 'reset7',
    149         title = _('Reset to paid'),
    150         msg = _("Reset to 'paid'"),
     153        title = _('Reset to school fee paid'),
     154        msg = _("Reset to 'school fee paid'"),
    151155        source = REGISTERED,
    152156        destination = PAID),
     
    161165    Transition(
    162166        transition_id = 'reset8',
    163         title = _('Reset to paid'),
    164         msg = _("Reset to 'paid'"),
     167        title = _('Reset to school fee paid'),
     168        msg = _("Reset to 'school fee paid'"),
    165169        source = VALIDATED,
    166170        destination = PAID),
     
    175179    Transition(
    176180        transition_id = 'reset9',
    177         title = _('Reset to validated'),
    178         msg = _("Reset to 'validated'"),
     181        title = _('Reset to courses validated'),
     182        msg = _("Reset to 'courses validated'"),
    179183        source = RETURNING,
    180184        destination = VALIDATED),
     
    183187IMPORTABLE_TRANSITIONS = [i.transition_id for i in REGISTRATION_TRANSITIONS]
    184188
     189FORBIDDEN_POSTGRAD_TRANS = ['reset6', 'register_courses']
    185190LOCK_CLEARANCE_TRANS = ('reset2', 'request_clearance')
    186191UNLOCK_CLEARANCE_TRANS = ('reset3', 'reset4', 'start_clearance')
     
    212217
    213218    Lock and unlock clearance form.
    214     Triger actions after school fee payment.
     219    Trigger actions after school fee payment.
    215220    """
    216221
     
    231236        new_session = obj['studycourse'].current_session + 1
    232237        obj['studycourse'].current_session = new_session
     238    elif event.transition.transition_id == 'validate_courses':
     239        current_level = obj['studycourse'].current_level
     240        level_object = obj['studycourse'].get(str(current_level), None)
     241        if level_object is not None:
     242            user = get_current_principal()
     243            if user is None:
     244                usertitle = 'system'
     245            else:
     246                usertitle = getattr(user, 'public_name', None)
     247                if not usertitle:
     248                    usertitle = user.title
     249            level_object.validated_by = usertitle
     250            level_object.validation_date = datetime.utcnow()
     251    elif event.transition.transition_id == 'reset8':
     252        current_level = obj['studycourse'].current_level
     253        level_object = obj['studycourse'].get(str(current_level), None)
     254        if level_object is not None:
     255            level_object.validated_by = None
     256            level_object.validation_date = None
    233257    # In some tests we don't have a students container
    234258    try:
Note: See TracChangeset for help on using the changeset viewer.