Ignore:
Timestamp:
10 Sep 2012, 11:05:07 (12 years ago)
Author:
uli
Message:

Merge changes from trunk, r8786-HEAD

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

Legend:

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

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

    r8757 r9169  
    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-async-update/src/waeup/kofa/students/batching.py

    r8736 r9169  
    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-async-update/src/waeup/kofa/students/browser.py

    r9166 r9169  
    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):
     
    336367    grok.name('manage_base')
    337368    grok.require('waeup.manageStudent')
    338     form_fields = grok.AutoFields(IStudentBase).omit('student_id')
     369    form_fields = grok.AutoFields(IStudentBase).omit(
     370        'student_id', 'adm_code', 'suspended')
    339371    grok.template('basemanagepage')
    340372    label = _('Manage base data')
     
    362394        allowed_transitions = [t for t in self.wf_info.getManualTransitions()
    363395            if not t[0].startswith('pay')]
     396        if self.context.is_postgrad:
     397            allowed_transitions = [t for t in allowed_transitions
     398                if not t[0] in FORBIDDEN_POSTGRAD_TRANS]
    364399        return [dict(name='', title=_('No transition'))] +[
    365400            dict(name=x, title=y) for x, y in allowed_transitions]
     
    396431        return
    397432
     433class StudentActivatePage(UtilityView, grok.View):
     434    """ Activate student account
     435    """
     436    grok.context(IStudent)
     437    grok.name('activate')
     438    grok.require('waeup.manageStudent')
     439
     440    def update(self):
     441        self.context.suspended = False
     442        self.context.writeLogMessage(self, 'account activated')
     443        history = IObjectHistory(self.context)
     444        history.addMessage('Student account activated')
     445        self.flash(_('Student account has been activated.'))
     446        self.redirect(self.url(self.context))
     447        return
     448
     449    def render(self):
     450        return
     451
     452class StudentDeactivatePage(UtilityView, grok.View):
     453    """ Deactivate student account
     454    """
     455    grok.context(IStudent)
     456    grok.name('deactivate')
     457    grok.require('waeup.manageStudent')
     458
     459    def update(self):
     460        self.context.suspended = True
     461        self.context.writeLogMessage(self, 'account deactivated')
     462        history = IObjectHistory(self.context)
     463        history.addMessage('Student account deactivated')
     464        self.flash(_('Student account has been deactivated.'))
     465        self.redirect(self.url(self.context))
     466        return
     467
     468    def render(self):
     469        return
     470
    398471class StudentClearanceDisplayFormPage(KofaDisplayFormPage):
    399472    """ Page to display student clearance data
     
    411484    def form_fields(self):
    412485        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')
     486            form_fields = grok.AutoFields(
     487                IPGStudentClearance).omit('clearance_locked')
     488        else:
     489            form_fields = grok.AutoFields(
     490                IUGStudentClearance).omit('clearance_locked')
    416491        return form_fields
    417492
     
    432507    def form_fields(self):
    433508        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')
     509            form_fields = grok.AutoFields(
     510                IPGStudentClearance).omit('clearance_locked')
     511        else:
     512            form_fields = grok.AutoFields(
     513                IUGStudentClearance).omit('clearance_locked')
    437514        return form_fields
    438515
     
    446523    def label(self):
    447524        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
    448         return translate(_('Clearance Slip of '),
     525        return translate(_('Clearance Slip of'),
    449526            'waeup.kofa', target_language=portal_language) \
    450527            + ' %s' % self.context.display_fullname
    451528
     529    def _signatures(self):
     530        if self.context.state == CLEARED:
     531            return (_('Student Signature'), _('Clearance Officer Signature'))
     532        return
     533
    452534    def render(self):
    453         studentview = StudentBaseDisplayFormPage(self.context.student,
     535        studentview = StudentBasePDFFormPage(self.context.student,
    454536            self.request)
    455537        students_utils = getUtility(IStudentsUtils)
    456538        return students_utils.renderPDF(
    457539            self, 'clearance.pdf',
    458             self.context.student, studentview)
     540            self.context.student, studentview, signatures=self._signatures())
    459541
    460542class StudentClearanceManageFormPage(KofaEditFormPage):
     
    475557    def form_fields(self):
    476558        if self.context.is_postgrad:
    477             form_fields = grok.AutoFields(IPGStudentClearance)
    478         else:
    479             form_fields = grok.AutoFields(IUGStudentClearance)
     559            form_fields = grok.AutoFields(IPGStudentClearance).omit('clr_code')
     560        else:
     561            form_fields = grok.AutoFields(IUGStudentClearance).omit('clr_code')
    480562        return form_fields
    481563
     
    557639            mapping = {'a':self.context.display_fullname})
    558640
    559 class StudentPersonalEditFormPage(KofaEditFormPage):
    560     """ Page to edit personal data
     641class StudentPersonalManageFormPage(KofaEditFormPage):
     642    """ Page to manage personal data
    561643    """
    562644    grok.context(IStudent)
    563     grok.name('edit_personal')
    564     grok.require('waeup.handleStudent')
     645    grok.name('manage_personal')
     646    grok.require('waeup.manageStudent')
    565647    form_fields = grok.AutoFields(IStudentPersonal)
    566     label = _('Edit personal data')
     648    label = _('Manage personal data')
    567649    pnav = 4
    568650
     
    572654        return
    573655
     656class StudentPersonalEditFormPage(StudentPersonalManageFormPage):
     657    """ Page to edit personal data
     658    """
     659    grok.name('edit_personal')
     660    grok.require('waeup.handleStudent')
     661    label = _('Edit personal data')
     662    pnav = 4
     663
    574664class StudyCourseDisplayFormPage(KofaDisplayFormPage):
    575665    """ Page to display the student study course data
     
    578668    grok.name('index')
    579669    grok.require('waeup.viewStudent')
    580     form_fields = grok.AutoFields(IStudentStudyCourse)
    581670    grok.template('studycoursepage')
    582671    pnav = 4
    583672
    584673    @property
     674    def form_fields(self):
     675        if self.context.is_postgrad:
     676            form_fields = grok.AutoFields(IStudentStudyCourse).omit(
     677                'current_verdict', 'previous_verdict')
     678        else:
     679            form_fields = grok.AutoFields(IStudentStudyCourse)
     680        return form_fields
     681
     682    @property
    585683    def label(self):
    586         return _('${a}: Study Course',
    587             mapping = {'a':self.context.__parent__.display_fullname})
     684        if self.context.is_current:
     685            return _('${a}: Study Course',
     686                mapping = {'a':self.context.__parent__.display_fullname})
     687        else:
     688            return _('${a}: Previous Study Course',
     689                mapping = {'a':self.context.__parent__.display_fullname})
    588690
    589691    @property
     
    606708        return
    607709
     710    @property
     711    def prev_studycourses(self):
     712        if self.context.is_current:
     713            if self.context.__parent__.get('studycourse_2', None) is not None:
     714                return (
     715                        {'href':self.url(self.context.student) + '/studycourse_1',
     716                        'title':_('First Study Course, ')},
     717                        {'href':self.url(self.context.student) + '/studycourse_2',
     718                        'title':_('Second Study Course')}
     719                        )
     720            if self.context.__parent__.get('studycourse_1', None) is not None:
     721                return (
     722                        {'href':self.url(self.context.student) + '/studycourse_1',
     723                        'title':_('First Study Course')},
     724                        )
     725        return
     726
    608727class StudyCourseManageFormPage(KofaEditFormPage):
    609728    """ Page to edit the student study course data
     
    613732    grok.require('waeup.manageStudent')
    614733    grok.template('studycoursemanagepage')
    615     form_fields = grok.AutoFields(IStudentStudyCourse)
    616734    label = _('Manage study course')
    617735    pnav = 4
     
    620738    tabthreeactions = [_('Add study level')]
    621739
     740    @property
     741    def form_fields(self):
     742        if self.context.is_postgrad:
     743            form_fields = grok.AutoFields(IStudentStudyCourse).omit(
     744                'current_verdict', 'previous_verdict')
     745        else:
     746            form_fields = grok.AutoFields(IStudentStudyCourse)
     747        return form_fields
     748
    622749    def update(self):
     750        if not self.context.is_current:
     751            emit_lock_message(self)
     752            return
    623753        super(StudyCourseManageFormPage, self).update()
    624754        tabs.need()
     
    685815        return
    686816
     817class StudentTransferFormPage(KofaAddFormPage):
     818    """Page to transfer the student.
     819    """
     820    grok.context(IStudent)
     821    grok.name('transfer')
     822    grok.require('waeup.manageStudent')
     823    label = _('Transfer student')
     824    form_fields = grok.AutoFields(IStudentStudyCourseTransfer).omit(
     825        'entry_mode', 'entry_session')
     826    pnav = 4
     827
     828    def update(self):
     829        super(StudentTransferFormPage, self).update()
     830        warning.need()
     831        return
     832
     833    @jsaction(_('Transfer'))
     834    def transferStudent(self, **data):
     835        error = self.context.transfer(**data)
     836        if error == -1:
     837            self.flash(_('Current level does not match certificate levels.'))
     838        elif error == -2:
     839            self.flash(_('Former study course record incomplete.'))
     840        elif error == -3:
     841            self.flash(_('Maximum number of transfers exceeded.'))
     842        else:
     843            self.flash(_('Successfully transferred.'))
     844        return
     845
    687846class StudyLevelDisplayFormPage(KofaDisplayFormPage):
    688847    """ Page to display student study levels
     
    692851    grok.require('waeup.viewStudent')
    693852    form_fields = grok.AutoFields(IStudentStudyLevel)
     853    form_fields[
     854        'validation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
    694855    grok.template('studylevelpage')
    695856    pnav = 4
     
    702863    @property
    703864    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
     865        return translated_values(self)
    714866
    715867    @property
     
    771923        Mand = translate(_('Mand.'), 'waeup.kofa', target_language=portal_language)
    772924        Score = translate(_('Score'), 'waeup.kofa', target_language=portal_language)
    773         studentview = StudentBaseDisplayFormPage(self.context.student,
     925        studentview = StudentBasePDFFormPage(self.context.student,
    774926            self.request)
    775927        students_utils = getUtility(IStudentsUtils)
     
    796948    grok.require('waeup.manageStudent')
    797949    grok.template('studylevelmanagepage')
    798     form_fields = grok.AutoFields(IStudentStudyLevel)
     950    form_fields = grok.AutoFields(IStudentStudyLevel).omit(
     951        'validation_date', 'validated_by')
    799952    pnav = 4
    800953    taboneactions = [_('Save'),_('Cancel')]
     
    803956
    804957    def update(self):
     958        if not self.context.__parent__.is_current:
     959            emit_lock_message(self)
     960            return
    805961        super(StudyLevelManageFormPage, self).update()
    806962        tabs.need()
     
    813969        datatable.need()
    814970        return
     971
     972    @property
     973    def translated_values(self):
     974        return translated_values(self)
    815975
    816976    @property
     
    8611021
    8621022    def update(self):
     1023        if not self.context.__parent__.is_current:
     1024            emit_lock_message(self)
     1025            return
    8631026        if str(self.context.__parent__.current_level) != self.context.__name__:
    8641027            self.flash(_('This level does not correspond current level.'))
     
    8831046
    8841047    def update(self):
     1048        if not self.context.__parent__.is_current:
     1049            emit_lock_message(self)
     1050            return
    8851051        if str(self.context.__parent__.current_level) != self.context.__name__:
    8861052            self.flash(_('This level does not correspond current level.'))
     
    9181084    pnav = 4
    9191085
     1086    def update(self):
     1087        if not self.context.__parent__.is_current:
     1088            emit_lock_message(self)
     1089            return
     1090        super(CourseTicketAddFormPage, self).update()
     1091        return
     1092
    9201093    @action(_('Add course ticket'))
    9211094    def addCourseTicket(self, **data):
     
    9241097        ticket.automatic = False
    9251098        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
    9331099        try:
    934             self.context.addCourseTicket(ticket)
     1100            self.context.addCourseTicket(ticket, course)
    9351101        except KeyError:
    9361102            self.flash(_('The ticket exists.'))
     
    10301196                mapping = {'a': ', '.join(deleted)}))
    10311197            self.context.writeLogMessage(
    1032                 self,'removed: % s' % ', '.join(deleted))
     1198                self,'removed: %s' % ', '.join(deleted))
    10331199        self.redirect(self.url(self.context))
    10341200        return
     
    10521218    def createTicket(self, **data):
    10531219        p_category = data['p_category']
     1220        previous_session = data.get('p_session', None)
     1221        previous_level = data.get('p_level', None)
    10541222        student = self.context.__parent__
    10551223        if p_category == 'bed_allocation' and student[
     
    10621230                return
    10631231        students_utils = getUtility(IStudentsUtils)
    1064         error, payment = students_utils.setPaymentDetails(p_category, student)
     1232        error, payment = students_utils.setPaymentDetails(
     1233            p_category, student, previous_session, previous_level)
    10651234        if error is not None:
    10661235            self.flash(error)
     1236            if 'Would you like' in error:
     1237                self.redirect(self.url(self.context) + '/@@addpp')
     1238                return
    10671239            self.redirect(self.url(self.context))
    10681240            return
     
    10711243        self.redirect(self.url(self.context))
    10721244        return
     1245
     1246class PreviousPaymentAddFormPage(OnlinePaymentAddFormPage):
     1247    """ Page to add an online payment ticket for previous sessions
     1248    """
     1249    grok.context(IStudentPaymentsContainer)
     1250    grok.name('addpp')
     1251    grok.require('waeup.payStudent')
     1252    form_fields = grok.AutoFields(IStudentPreviousPayment).select(
     1253        'p_category', 'p_session', 'p_level')
     1254    label = _('Add previous session online payment')
     1255    pnav = 4
    10731256
    10741257class OnlinePaymentDisplayFormPage(KofaDisplayFormPage):
     
    11491332        #    self.redirect(self.url(self.context))
    11501333        #    return
    1151         studentview = StudentBaseDisplayFormPage(self.context.student,
     1334        studentview = StudentBasePDFFormPage(self.context.student,
    11521335            self.request)
    11531336        students_utils = getUtility(IStudentsUtils)
     
    13691552
    13701553    def render(self):
    1371         studentview = StudentBaseDisplayFormPage(self.context.student,
     1554        studentview = StudentBasePDFFormPage(self.context.student,
    13721555            self.request)
    13731556        students_utils = getUtility(IStudentsUtils)
     
    15691752            self.flash(_('Activation code is invalid.'))
    15701753            return
     1754        if code.state == USED:
     1755            self.flash(_('Activation code has already been used.'))
     1756            return
    15711757        # Mark pin as used (this also fires a pin related transition)
    15721758        # 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
     1759        comment = _(u"invalidated")
     1760        # Here we know that the ac is in state initialized so we do not
     1761        # expect an exception, but the owner might be different
     1762        if not invalidate_accesscode(pin, comment, self.context.student_id):
     1763            self.flash(_('You are not the owner of this access code.'))
     1764            return
     1765        self.context.clr_code = pin
    15841766        IWorkflowInfo(self.context).fireTransition('start_clearance')
    15851767        self.flash(_('Clearance process has been started.'))
     
    15981780    def form_fields(self):
    15991781        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')
     1782            form_fields = grok.AutoFields(IPGStudentClearance).omit(
     1783                'clearance_locked', 'clr_code')
     1784        else:
     1785            form_fields = grok.AutoFields(IUGStudentClearance).omit(
     1786                'clearance_locked', 'clr_code')
    16031787        return form_fields
    16041788
     
    16271811            return
    16281812        self.flash(_('Clearance form has been saved.'))
    1629         self.redirect(self.url(self.context,'request_clearance'))
     1813        if self.context.clr_code:
     1814            self.redirect(self.url(self.context, 'request_clearance'))
     1815        else:
     1816            # We bypass the request_clearance page if student
     1817            # has been imported in state 'clearance started' and
     1818            # no clr_code was entered before.
     1819            state = IWorkflowState(self.context).getState()
     1820            if state != CLEARANCE:
     1821                # This shouldn't happen, but the application officer
     1822                # might have forgotten to lock the form after changing the state
     1823                self.flash(_('This form cannot be submitted. Wrong state!'))
     1824                return
     1825            IWorkflowInfo(self.context).fireTransition('request_clearance')
     1826            self.flash(_('Clearance has been requested.'))
     1827            self.redirect(self.url(self.context))
    16301828        return
    16311829
     
    16471845            return
    16481846        pin = '%s-%s-%s' % (self.ac_prefix, self.ac_series, self.ac_number)
    1649         if self.context.clr_code != pin:
     1847        if self.context.clr_code and self.context.clr_code != pin:
    16501848            self.flash(_("This isn't your CLR access code."))
    16511849            return
    16521850        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
    16551851        if state != CLEARANCE:
     1852            # This shouldn't happen, but the application officer
     1853            # might have forgotten to lock the form after changing the state
    16561854            self.flash(_('This form cannot be submitted. Wrong state!'))
    16571855            return
     
    16731871
    16741872    def update(self, SUBMIT=None):
     1873        if not self.context.is_current:
     1874            emit_lock_message(self)
     1875            return
     1876        super(StartSessionPage, self).update()
    16751877        if not self.context.next_session_allowed:
    16761878            self.flash(_("You are not entitled to start session."))
     
    17301932
    17311933    def update(self):
     1934        if not self.context.is_current:
     1935            emit_lock_message(self)
     1936            return
    17321937        if self.context.student.state != PAID:
    17331938            emit_lock_message(self)
     
    17621967
    17631968    def update(self):
     1969        if not self.context.__parent__.is_current:
     1970            emit_lock_message(self)
     1971            return
    17641972        if self.context.student.state != PAID:
    17651973            emit_lock_message(self)
     
    17761984        level_title = translate(self.context.level_title, 'waeup.kofa',
    17771985            target_language=lang)
    1778         return _('Add and remove course tickets of study level ${a}',
     1986        return _('Edit course list of ${a}',
    17791987            mapping = {'a':level_title})
    17801988
     
    17851993            total_credits += val.credits
    17861994        return total_credits
     1995
     1996    @property
     1997    def translated_values(self):
     1998        return translated_values(self)
    17871999
    17882000    @action(_('Add course ticket'))
     
    18472059        ticket = createObject(u'waeup.CourseTicket')
    18482060        course = data['course']
    1849         for name in ['code', 'title', 'credits', 'passmark', 'semester']:
    1850             setattr(ticket, name, getattr(course, name))
    18512061        ticket.automatic = False
     2062        ticket.carry_over = False
    18522063        try:
    1853             self.context.addCourseTicket(ticket)
     2064            self.context.addCourseTicket(ticket, course)
    18542065        except KeyError:
    18552066            self.flash(_('The ticket exists.'))
     
    19272138    grok.template('requestpw')
    19282139    form_fields = grok.AutoFields(IStudentRequestPW).select(
    1929         'firstname','reg_number','email')
     2140        'firstname','number','email')
    19302141    label = _('Request password for first-time login')
    19312142
     
    19482159        return True
    19492160
    1950     @action(_('Get login credentials'), style='primary')
     2161    @action(_('Send login credentials to email address'), style='primary')
    19512162    def get_credentials(self, **data):
    19522163        if not self.captcha_result.is_valid:
     
    19542165            # No need to flash something.
    19552166            return
    1956         reg_number = data.get('reg_number','')
     2167        number = data.get('number','')
    19572168        firstname = data.get('firstname','')
    19582169        cat = getUtility(ICatalog, name='students_catalog')
    19592170        results = list(
    1960             cat.searchResults(reg_number=(reg_number, reg_number)))
     2171            cat.searchResults(reg_number=(number, number)))
     2172        if not results:
     2173            results = list(
     2174                cat.searchResults(matric_number=(number, number)))
    19612175        if results:
    19622176            student = results[0]
     
    19842198        kofa_utils = getUtility(IKofaUtils)
    19852199        password = kofa_utils.genPassword()
    1986         IUserAccount(student).setPassword(password)
     2200        mandate = PasswordMandate()
     2201        mandate.params['password'] = password
     2202        mandate.params['user'] = student
     2203        site = grok.getSite()
     2204        site['mandates'].addMandate(mandate)
    19872205        # Send email with credentials
    1988         login_url = self.url(grok.getSite(), 'login')
     2206        args = {'mandate_id':mandate.mandate_id}
     2207        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
     2208        url_info = u'Confirmation link: %s' % mandate_url
    19892209        msg = _('You have successfully requested a password for the')
    19902210        if kofa_utils.sendCredentials(IUserAccount(student),
    1991             password, login_url, msg):
     2211            password, url_info, msg):
    19922212            email_sent = student.email
    19932213        else:
     
    19952215        self._redirect(email=email_sent, password=password,
    19962216            student_id=student.student_id)
     2217        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
     2218        self.context.logger.info(
     2219            '%s - %s (%s) - %s' % (ob_class, number, student.student_id, email_sent))
    19972220        return
    19982221
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/browser_templates/requestpw.pt

    r8782 r9169  
    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-async-update/src/waeup/kofa/students/browser_templates/requestpwmailsent.pt

    r8782 r9169  
    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-async-update/src/waeup/kofa/students/browser_templates/studycoursepage.pt

    r7876 r9169  
    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-async-update/src/waeup/kofa/students/browser_templates/studyleveleditpage.pt

    r8553 r9169  
    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-async-update/src/waeup/kofa/students/browser_templates/studylevelmanagepage.pt

    r7811 r9169  
    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-async-update/src/waeup/kofa/students/browser_templates/studylevelpage.pt

    r8141 r9169  
    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-async-update/src/waeup/kofa/students/dynamicroles.py

    r7811 r9169  
    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-async-update/src/waeup/kofa/students/export.py

    r8736 r9169  
    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-async-update/src/waeup/kofa/students/interfaces.py

    r9004 r9169  
    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')
    154158    fullname = Attribute('All name parts separated by hyphens')
    155159    display_fullname = Attribute('The fullname of an applicant')
     160    is_postgrad = Attribute('True if postgraduate student')
     161
     162    suspended = schema.Bool(
     163        title = _(u'Account suspended'),
     164        default = False,
     165        required = False,
     166        )
    156167
    157168    student_id = schema.TextLine(
     
    198209        title = _(u'PWD Activation Code'),
    199210        required = False,
    200         readonly = True,
     211        readonly = False,
    201212        )
    202213
     
    212223        )
    213224
     225    def transfer(certificate, current_session,
     226        current_level, current_verdict):
     227        """ Creates a new studycourse and backups the old one.
     228
     229        """
     230
    214231class IUGStudentClearance(IKofaObject):
    215232    """Representation of undergraduate student clearance data.
     
    225242        title = _(u'Clearance form locked'),
    226243        default = False,
     244        required = False,
    227245        )
    228246
     
    230248        title = _(u'CLR Activation Code'),
    231249        required = False,
    232         readonly = True,
     250        readonly = False,
    233251        )
    234252
     
    288306    login for the the first time.
    289307    """
    290     reg_number = schema.TextLine(
    291         title = u'Registration Number',
     308    number = schema.TextLine(
     309        title = _(u'Registr. or Matric. Number'),
    292310        required = True,
    293311        )
     
    346364        title = _(u'Current Verdict'),
    347365        source = VerdictSource(),
    348         default = 'NY',
     366        default = '0',
    349367        required = False,
    350368        )
     
    353371        title = _(u'Previous Verdict'),
    354372        source = VerdictSource(),
    355         default = 'NY',
    356         required = False,
    357         )
     373        default = '0',
     374        required = False,
     375        )
     376
     377class IStudentStudyCourseTransfer(IStudentStudyCourse):
     378    """An student transfers.
     379
     380    """
     381
     382    certificate = schema.Choice(
     383        title = _(u'Certificate'),
     384        source = CertificateSource(),
     385        required = True,
     386        )
     387
     388    current_level = schema.Choice(
     389        title = _(u'Current Level'),
     390        source = StudyLevelSource(),
     391        required = True,
     392        readonly = False,
     393        )
     394
     395
     396IStudentStudyCourseTransfer['certificate'].order = IStudentStudyCourse[
     397    'certificate'].order
     398IStudentStudyCourseTransfer['current_level'].order = IStudentStudyCourse[
     399    'current_level'].order
    358400
    359401class IStudentVerdictUpdate(IKofaObject):
     
    385427    """
    386428    level = Attribute('The level code')
    387     validation_date = Attribute('The date of validation')
    388     validated_by = Attribute('User Id of course adviser')
    389429
    390430    level_session = schema.Choice(
    391431        title = _(u'Session'),
    392432        source = academic_sessions_vocab,
    393         required = True,
     433        required = False,
    394434        )
    395435
     
    397437        title = _(u'Verdict'),
    398438        source = VerdictSource(),
    399         default = 'NY',
    400         required = False,
    401         )
    402 
    403     def addCourseTicket(courseticket):
     439        default = '0',
     440        required = False,
     441        )
     442
     443    validated_by = schema.TextLine(
     444        title = _(u'Validated by'),
     445        default = None,
     446        required = False,
     447        )
     448
     449    validation_date = schema.Datetime(
     450        title = _(u'Validation Date'),
     451        required = False,
     452        readonly = False,
     453        )
     454
     455    def addCourseTicket(ticket, course):
    404456        """Add a course ticket object.
    405457        """
     
    520572    """
    521573
     574    p_current = schema.Bool(
     575        title = _(u'Current Session Payment'),
     576        default = True,
     577        required = False,
     578        )
     579
    522580    p_level = schema.Int(
    523581        title = _(u'Payment Level'),
     
    543601IStudentOnlinePayment['p_level'].order = IStudentOnlinePayment[
    544602    'p_session'].order
     603
     604class IStudentPreviousPayment(IOnlinePayment):
     605    """An interface for adding previous session payments.
     606
     607    """
     608
     609    p_session = schema.Choice(
     610        title = _(u'Payment Session'),
     611        source = academic_sessions_vocab,
     612        required = True,
     613        )
     614
     615    p_level = schema.Choice(
     616        title = _(u'Payment Level'),
     617        source = StudyLevelSource(),
     618        required = True,
     619        )
    545620
    546621class ICSVStudentExporter(ICSVExporter):
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/payments.py

    r8736 r9169  
    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-async-update/src/waeup/kofa/students/student.py

    r8737 r9169  
    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
    132139    def current_mode(self):
    133140        certificate = getattr(
     
    142149            self.get('studycourse', None), 'is_postgrad', False)
    143150        return is_postgrad
     151
     152    def transfer(self, certificate, current_session=None,
     153        current_level=None, current_verdict=None, previous_verdict=None):
     154        """ Creates a new studycourse and backups the old one.
     155
     156        """
     157        studycourse = createObject(u'waeup.StudentStudyCourse')
     158        try:
     159            studycourse.certificate = certificate
     160            studycourse.entry_mode = 'transfer'
     161            studycourse.current_session = current_session
     162            studycourse.current_level = current_level
     163            studycourse.current_verdict = current_verdict
     164            studycourse.previous_verdict = previous_verdict
     165        except ConstraintNotSatisfied:
     166            return -1
     167        old = self['studycourse']
     168        if getattr(old, 'entry_session', None) is None or\
     169            getattr(old, 'certificate', None) is None:
     170            return -2
     171        studycourse.entry_session = old.entry_session
     172        # Students can be transferred only two times.
     173        if 'studycourse_1' in self.keys():
     174            if 'studycourse_2' in self.keys():
     175                return -3
     176            self['studycourse_2'] = old
     177        else:
     178            self['studycourse_1'] = old
     179        del self['studycourse']
     180        self['studycourse'] = studycourse
     181        self.__parent__.logger.info(
     182            '%s - transferred from %s to %s' % (
     183            self.student_id, old.certificate.code, studycourse.certificate.code))
     184        history = IObjectHistory(self)
     185        history.addMessage('Transferred from %s to %s' % (
     186            old.certificate.code, studycourse.certificate.code))
     187        return
    144188
    145189
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/studycourse.py

    r8736 r9169  
    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-async-update/src/waeup/kofa/students/studylevel.py

    r8736 r9169  
    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-async-update/src/waeup/kofa/students/tests/sample_payment_data.csv

    r8244 r9169  
    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-async-update/src/waeup/kofa/students/tests/test_authentication.py

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

    r8626 r9169  
    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-async-update/src/waeup/kofa/students/tests/test_browser.py

    r8779 r9169  
    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
     
    454455        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    455456        self.assertEqual(self.browser.url, self.personal_path)
     457        self.browser.getLink("Manage").click()
     458        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     459        self.assertEqual(self.browser.url, self.manage_personal_path)
     460        self.browser.open(self.personal_path)
    456461        self.browser.getLink("Edit").click()
    457462        self.assertEqual(self.browser.headers['Status'], '200 Ok')
     
    474479        self.assertEqual(self.browser.headers['Status'], '200 Ok')
    475480        self.assertEqual(self.browser.url, self.history_path)
    476         self.assertMatches('...Student admitted by Manager...',
     481        self.assertMatches('...Admitted by Manager...',
    477482                           self.browser.contents)
    478483        # Only the Application Slip does not exist
     
    754759        self.assertEqual(student['studycourse'].current_session, 2005) # +1
    755760        self.assertEqual(student['studycourse'].current_level, 200) # +100
    756         self.assertEqual(student['studycourse'].current_verdict, 'NY') # NY = not set
     761        self.assertEqual(student['studycourse'].current_verdict, '0') # 0 = Zero = not set
    757762        self.assertEqual(student['studycourse'].previous_verdict, 'A')
    758763        self.browser.getControl(name="transition").value = ['register_courses']
     
    762767        self.browser.getControl(name="transition").value = ['return']
    763768        self.browser.getControl("Save").click()
     769        return
     770
     771    def test_manage_pg_workflow(self):
     772        # Managers can pass through the whole workflow
     773        IWorkflowState(self.student).setState('school fee paid')
     774        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     775        student = self.app['students'][self.student_id]
     776        self.browser.open(self.manage_student_path)
     777        self.assertTrue('<option value="reset6">' in self.browser.contents)
     778        self.assertTrue('<option value="register_courses">' in self.browser.contents)
     779        self.assertTrue('<option value="reset5">' in self.browser.contents)
     780        self.certificate.study_mode = 'pg_ft'
     781        self.browser.open(self.manage_student_path)
     782        self.assertFalse('<option value="reset6">' in self.browser.contents)
     783        self.assertFalse('<option value="register_courses">' in self.browser.contents)
     784        self.assertTrue('<option value="reset5">' in self.browser.contents)
    764785        return
    765786
     
    776797        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
    777798        self.browser.open(datacenter_path)
    778         self.browser.getLink('Upload CSV file').click()
     799        self.browser.getLink('Upload data').click()
    779800        filecontents = StringIO(open('students.csv', 'rb').read())
    780801        filewidget = self.browser.getControl(name='uploadfile:file')
    781802        filewidget.add_file(filecontents, 'text/plain', 'students.csv')
    782803        self.browser.getControl(name='SUBMIT').click()
    783         self.browser.getLink('Batch processing').click()
     804        self.browser.getLink('Process data').click()
    784805        button = lookup_submit_value(
    785806            'select', 'students_zope.mgr.csv', self.browser)
     
    802823""")
    803824        self.browser.open(datacenter_path)
    804         self.browser.getLink('Upload CSV file').click()
     825        self.browser.getLink('Upload data').click()
    805826        filecontents = StringIO(open('studycourses.csv', 'rb').read())
    806827        filewidget = self.browser.getControl(name='uploadfile:file')
    807828        filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
    808829        self.browser.getControl(name='SUBMIT').click()
    809         self.browser.getLink('Batch processing').click()
     830        self.browser.getLink('Process data').click()
    810831        button = lookup_submit_value(
    811832            'select', 'studycourses_zope.mgr.csv', self.browser)
     
    917938        self.assertTrue('clearance started' in self.browser.contents)
    918939        self.browser.open(self.history_path)
    919         self.assertTrue("Reset to 'clearance' by My Public Name" in
     940        self.assertTrue("Reset to 'clearance started' by My Public Name" in
    920941            self.browser.contents)
    921942        IWorkflowInfo(self.student).fireTransition('request_clearance')
     
    946967        self.app['users'].addUser('mrsadvise', 'mrsadvisesecret')
    947968        self.app['users']['mrsadvise'].email = 'mradvise@foo.ng'
    948         self.app['users']['mrsadvise'].title = 'Helen Procter'
     969        self.app['users']['mrsadvise'].title = u'Helen Procter'
    949970        # Assign local CourseAdviser100 role for a certificate
    950971        cert = self.app['faculties']['fac1']['dep1'].certificates['CERT1']
     
    9971018        # the 100L CA does see the 'Validate' button
    9981019        self.browser.open(L110_student_path)
    999         self.assertFalse('Validate' in self.browser.contents)
     1020        self.assertFalse('Validate courses' in self.browser.contents)
    10001021        IWorkflowInfo(self.student).fireTransition('register_courses')
    10011022        self.browser.open(L110_student_path)
    1002         self.assertFalse('Validate' in self.browser.contents)
     1023        self.assertFalse('Validate courses' in self.browser.contents)
    10031024        self.student['studycourse'].current_level = 110
    10041025        self.browser.open(L110_student_path)
    1005         self.assertTrue('Validate' in self.browser.contents)
     1026        self.assertTrue('Validate courses' in self.browser.contents)
    10061027        # ... but a 100L CA does not see the button on other levels
    10071028        studylevel2 = StudentStudyLevel()
     
    10111032        L200_student_path = self.studycourse_path + '/200'
    10121033        self.browser.open(L200_student_path)
    1013         self.assertFalse('Validate' in self.browser.contents)
     1034        self.assertFalse('Validate courses' in self.browser.contents)
    10141035        self.browser.open(L110_student_path)
    10151036        self.browser.getLink("Validate courses").click()
    10161037        self.assertTrue('Course list has been validated' in self.browser.contents)
    10171038        self.assertTrue('courses validated' in self.browser.contents)
     1039        self.assertEqual(self.student['studycourse']['110'].validated_by,
     1040            'Helen Procter')
     1041        self.assertMatches(
     1042            '<YYYY-MM-DD hh:mm:ss>',
     1043            self.student['studycourse']['110'].validation_date.strftime(
     1044                "%Y-%m-%d %H:%M:%S"))
    10181045        self.browser.getLink("Reject courses").click()
    10191046        self.assertTrue('Course list request has been annulled.'
     
    10231050            '/contactstudent?subject=%s' % urlmessage)
    10241051        self.assertTrue('school fee paid' in self.browser.contents)
     1052        self.assertTrue(self.student['studycourse']['110'].validated_by is None)
     1053        self.assertTrue(self.student['studycourse']['110'].validation_date is None)
    10251054        IWorkflowInfo(self.student).fireTransition('register_courses')
    10261055        self.browser.open(L110_student_path)
     
    11421171        # and can perform actions
    11431172        IWorkflowInfo(self.student).fireTransition('admit')
     1173        # Students can't login if their account is suspended/deactivated
     1174        self.student.suspended = True
    11441175        self.browser.open(self.login_path)
    11451176        self.browser.getControl(name="form.login").value = self.student_id
    11461177        self.browser.getControl(name="form.password").value = 'spwd'
    11471178        self.browser.getControl("Login").click()
     1179        self.assertTrue(
     1180            'Your account has been deactivated.' in self.browser.contents)
     1181        self.student.suspended = False
     1182        self.browser.getControl("Login").click()
     1183        self.assertTrue(
     1184            'You logged in.' in self.browser.contents)
    11481185        # Student can upload a passport picture
    11491186        self.browser.open(self.student_path + '/change_portrait')
     
    11701207        self.assertMatches('...Not all required fields filled...',
    11711208                           self.browser.contents)
    1172         self.student.email = 'aa@aa.ng'
     1209        self.browser.open(self.student_path + '/edit_base')
     1210        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
     1211        self.browser.getControl("Save").click()
    11731212        self.browser.open(self.student_path + '/start_clearance')
    11741213        self.browser.getControl(name="ac_series").value = '3'
     
    11851224        # Set the correct owner
    11861225        self.existing_clrac.owner = self.student_id
     1226        # clr_code might be set (and thus returns None) due importing
     1227        # an empty clr_code column.
     1228        self.student.clr_code = None
    11871229        self.browser.getControl("Start clearance now").click()
    11881230        self.assertMatches('...Clearance process has been started...',
     
    12351277        self.browser.getControl("Create course list now").click()
    12361278        self.browser.getLink("100").click()
    1237         self.browser.getLink("Add and remove courses").click()
     1279        self.browser.getLink("Edit course list").click()
    12381280        self.browser.getControl("Add course ticket").click()
    12391281        self.browser.getControl(name="form.course").value = ['COURSE1']
     
    12481290        self.browser.getControl("Create course list now").click()
    12491291        self.browser.getLink("200").click()
    1250         self.browser.getLink("Add and remove courses").click()
     1292        self.browser.getLink("Edit course list").click()
    12511293        self.browser.getControl("Add course ticket").click()
    12521294        self.browser.getControl(name="form.course").value = ['COURSE1']
     
    12861328        self.assertEqual(self.student.state, 'courses registered')
    12871329        return
     1330
     1331    def test_student_clearance_wo_clrcode(self):
     1332        IWorkflowState(self.student).setState('clearance started')
     1333        self.browser.open(self.login_path)
     1334        self.browser.getControl(name="form.login").value = self.student_id
     1335        self.browser.getControl(name="form.password").value = 'spwd'
     1336        self.browser.getControl("Login").click()
     1337        self.student.clearance_locked = False
     1338        self.browser.open(self.edit_clearance_path)
     1339        self.browser.getControl(name="form.date_of_birth").value = '09/10/1961'
     1340        self.browser.getControl("Save and request clearance").click()
     1341        self.assertMatches('...Clearance has been requested...',
     1342                           self.browser.contents)
    12881343
    12891344    def test_manage_payments(self):
     
    15161571        # The new SFE-0 pin can be used for starting new session
    15171572        self.browser.open(self.studycourse_path)
    1518         self.browser.getLink('Start session').click()
     1573        self.browser.getLink('Start new session').click()
    15191574        pin = self.app['accesscodes']['SFE-0'].keys()[0]
    15201575        parts = pin.split('-')[1:]
     
    15281583        return
    15291584
    1530     def test_postgraduate_payments(self):
     1585    def test_student_previous_payments(self):
     1586        # Login
     1587        self.browser.open(self.login_path)
     1588        self.browser.getControl(name="form.login").value = self.student_id
     1589        self.browser.getControl(name="form.password").value = 'spwd'
     1590        self.browser.getControl("Login").click()
     1591
     1592        # Students can add previous school fee payment tickets in any state.
     1593        IWorkflowState(self.student).setState('courses registered')
     1594        self.browser.open(self.payments_path)
     1595        self.browser.getControl("Add online payment ticket").click()
     1596        self.browser.getControl(name="form.p_category").value = ['schoolfee']
     1597        self.browser.getControl("Create ticket").click()
     1598
     1599        # Amount cannot be determined since the state is not
     1600        # 'cleared' or 'returning'
     1601        self.assertMatches('...Amount could not be determined...',
     1602                           self.browser.contents)
     1603        self.assertMatches('...Would you like to pay for a previous session?...',
     1604                           self.browser.contents)
     1605
     1606        # Previous session payment form is provided
     1607        self.browser.getControl(name="form.p_category").value = ['schoolfee']
     1608        self.browser.getControl(name="form.p_session").value = ['2004']
     1609        self.browser.getControl(name="form.p_level").value = ['300']
     1610        self.browser.getControl("Create ticket").click()
     1611        self.assertMatches('...ticket created...',
     1612                           self.browser.contents)
     1613        ctrl = self.browser.getControl(name='val_id')
     1614        value = ctrl.options[0]
     1615        self.browser.getLink(value).click()
     1616        self.assertMatches('...Amount Authorized...',
     1617                           self.browser.contents)
     1618        self.assertEqual(self.student['payments'][value].amount_auth, 20000.0)
     1619
     1620        # Payment session is properly set
     1621        self.assertEqual(self.student['payments'][value].p_session, 2004)
     1622        self.assertEqual(self.student['payments'][value].p_level, 300)
     1623
     1624        # We simulate the approval
     1625        self.browser.open(self.browser.url + '/fake_approve')
     1626        self.assertMatches('...Payment approved...',
     1627                          self.browser.contents)
     1628
     1629        # No AC has been created
     1630        self.assertEqual(len(self.app['accesscodes']['SFE-0'].keys()), 0)
     1631        self.assertTrue(self.student['payments'][value].ac is None)
     1632
     1633        # Current payment flag is set False
     1634        self.assertFalse(self.student['payments'][value].p_current)
     1635        return
     1636
     1637    def test_student_postgraduate_payments(self):
    15311638        self.certificate.study_mode = 'pg_ft'
    15321639        self.certificate.start_level = 999
     
    15521659                           self.browser.contents)
    15531660        # Payment session and level are current ones.
    1554         # Postgrads have to school_fee_1.
     1661        # Postgrads have to pay school_fee_1.
    15551662        self.assertEqual(self.student['payments'][value].amount_auth, 40000.0)
    15561663        self.assertEqual(self.student['payments'][value].p_session, 2004)
     
    15651672        # The new SFE-0 pin can be used for starting session
    15661673        self.browser.open(self.studycourse_path)
    1567         self.browser.getLink('Start session').click()
     1674        self.browser.getLink('Start new session').click()
    15681675        pin = self.app['accesscodes']['SFE-0'].keys()[0]
    15691676        parts = pin.split('-')[1:]
     
    16051712        # The new SFE-1 pin can be used for starting new session
    16061713        self.browser.open(self.studycourse_path)
    1607         self.browser.getLink('Start session').click()
     1714        self.browser.getLink('Start new session').click()
    16081715        self.browser.getControl(name="ac_series").value = sfeseries
    16091716        self.browser.getControl(name="ac_number").value = sfenumber
     
    18501957        self.assertTrue('An email with' in self.browser.contents)
    18511958
    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)
    1868 
    18691959    def test_change_current_mode(self):
    18701960        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     
    18881978        self.assertTrue('Employer' in self.browser.contents)
    18891979
     1980    def test_activate_deactivate_buttons(self):
     1981        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     1982        self.browser.open(self.student_path)
     1983        self.browser.getLink("Deactivate").click()
     1984        self.assertTrue(
     1985            'Student account has been deactivated.' in self.browser.contents)
     1986        self.assertTrue(
     1987            'Base Data (account deactivated)' in self.browser.contents)
     1988        self.assertTrue(self.student.suspended)
     1989        self.browser.getLink("Activate").click()
     1990        self.assertTrue(
     1991            'Student account has been activated.' in self.browser.contents)
     1992        self.assertFalse(
     1993            'Base Data (account deactivated)' in self.browser.contents)
     1994        self.assertFalse(self.student.suspended)
     1995        # History messages have been added ...
     1996        self.browser.getLink("History").click()
     1997        self.assertTrue(
     1998            'Student account deactivated by Manager<br />' in self.browser.contents)
     1999        self.assertTrue(
     2000            'Student account activated by Manager<br />' in self.browser.contents)
     2001        # ... and actions have been logged.
     2002        logfile = os.path.join(
     2003            self.app['datacenter'].storage, 'logs', 'students.log')
     2004        logcontent = open(logfile).read()
     2005        self.assertTrue('zope.mgr - students.browser.StudentDeactivatePage - '
     2006                        'K1000000 - account deactivated' in logcontent)
     2007        self.assertTrue('zope.mgr - students.browser.StudentActivatePage - '
     2008                        'K1000000 - account activated' in logcontent)
     2009
     2010    def test_student_transfer(self):
     2011        # Add second certificate
     2012        self.certificate2 = createObject('waeup.Certificate')
     2013        self.certificate2.code = u'CERT2'
     2014        self.certificate2.study_mode = 'ug_ft'
     2015        self.certificate2.start_level = 999
     2016        self.certificate2.end_level = 999
     2017        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
     2018            self.certificate2)
     2019
     2020        # Add study level to old study course
     2021        studylevel = createObject(u'waeup.StudentStudyLevel')
     2022        studylevel.level = 200
     2023        self.student['studycourse'].addStudentStudyLevel(
     2024            self.certificate, studylevel)
     2025
     2026        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
     2027        self.browser.open(self.student_path)
     2028        self.browser.getLink("Transfer").click()
     2029        self.browser.getControl(name="form.certificate").value = ['CERT2']
     2030        self.browser.getControl(name="form.current_session").value = ['2011']
     2031        self.browser.getControl(name="form.current_level").value = ['200']
     2032        self.browser.getControl("Transfer").click()
     2033        self.assertTrue(
     2034            'Current level does not match certificate levels'
     2035            in self.browser.contents)
     2036        self.browser.getControl(name="form.current_level").value = ['999']
     2037        self.browser.getControl("Transfer").click()
     2038        self.assertTrue('Successfully transferred' in self.browser.contents)
     2039
     2040        # Add study level to new study course
     2041        studylevel = createObject(u'waeup.StudentStudyLevel')
     2042        studylevel.level = 200
     2043        self.student['studycourse'].addStudentStudyLevel(
     2044            self.certificate, studylevel)
     2045
     2046        # Edit and add pages are locked for old study courses
     2047        self.browser.open(self.student_path + '/studycourse/manage')
     2048        self.assertFalse('The requested form is locked' in self.browser.contents)
     2049        self.browser.open(self.student_path + '/studycourse_1/manage')
     2050        self.assertTrue('The requested form is locked' in self.browser.contents)
     2051
     2052        self.browser.open(self.student_path + '/studycourse/start_session')
     2053        self.assertFalse('The requested form is locked' in self.browser.contents)
     2054        self.browser.open(self.student_path + '/studycourse_1/start_session')
     2055        self.assertTrue('The requested form is locked' in self.browser.contents)
     2056
     2057        IWorkflowState(self.student).setState('school fee paid')
     2058        self.browser.open(self.student_path + '/studycourse/add')
     2059        self.assertFalse('The requested form is locked' in self.browser.contents)
     2060        self.browser.open(self.student_path + '/studycourse_1/add')
     2061        self.assertTrue('The requested form is locked' in self.browser.contents)
     2062
     2063        self.browser.open(self.student_path + '/studycourse/200/manage')
     2064        self.assertFalse('The requested form is locked' in self.browser.contents)
     2065        self.browser.open(self.student_path + '/studycourse_1/200/manage')
     2066        self.assertTrue('The requested form is locked' in self.browser.contents)
     2067
     2068        self.browser.open(self.student_path + '/studycourse/200/validate_courses')
     2069        self.assertFalse('The requested form is locked' in self.browser.contents)
     2070        self.browser.open(self.student_path + '/studycourse_1/200/validate_courses')
     2071        self.assertTrue('The requested form is locked' in self.browser.contents)
     2072
     2073        self.browser.open(self.student_path + '/studycourse/200/reject_courses')
     2074        self.assertFalse('The requested form is locked' in self.browser.contents)
     2075        self.browser.open(self.student_path + '/studycourse_1/200/reject_courses')
     2076        self.assertTrue('The requested form is locked' in self.browser.contents)
     2077
     2078        self.browser.open(self.student_path + '/studycourse/200/add')
     2079        self.assertFalse('The requested form is locked' in self.browser.contents)
     2080        self.browser.open(self.student_path + '/studycourse_1/200/add')
     2081        self.assertTrue('The requested form is locked' in self.browser.contents)
     2082
     2083        self.browser.open(self.student_path + '/studycourse/200/edit')
     2084        self.assertFalse('The requested form is locked' in self.browser.contents)
     2085        self.browser.open(self.student_path + '/studycourse_1/200/edit')
     2086        self.assertTrue('The requested form is locked' in self.browser.contents)
     2087
    18902088class StudentRequestPWTests(StudentsFullSetup):
    18912089    # Tests for student registration
     
    18942092
    18952093    def test_request_pw(self):
    1896         # Student with wrong reg_number can't be found.
     2094        # Student with wrong number can't be found.
    18972095        self.browser.open('http://localhost/app/requestpw')
    18982096        self.browser.getControl(name="form.firstname").value = 'Anna'
    1899         self.browser.getControl(name="form.reg_number").value = 'anynumber'
     2097        self.browser.getControl(name="form.number").value = 'anynumber'
    19002098        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
    1901         self.browser.getControl("Get login credentials").click()
     2099        self.browser.getControl("Send login credentials").click()
    19022100        self.assertTrue('No student record found.'
    19032101            in self.browser.contents)
     
    19062104        self.browser.open('http://localhost/app/requestpw')
    19072105        self.browser.getControl(name="form.firstname").value = 'Johnny'
    1908         self.browser.getControl(name="form.reg_number").value = '123'
     2106        self.browser.getControl(name="form.number").value = '123'
    19092107        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
    1910         self.browser.getControl("Get login credentials").click()
     2108        self.browser.getControl("Send login credentials").click()
    19112109        self.assertTrue('No student record found.'
    19122110            in self.browser.contents)
     
    19142112        # password has been set and used.
    19152113        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()
     2114        self.browser.getControl(name="form.number").value = '123'
     2115        self.browser.getControl("Send login credentials").click()
    19182116        self.assertTrue('Your password has already been set and used.'
    19192117            in self.browser.contents)
     
    19222120        # The firstname field, used for verification, is not case-sensitive.
    19232121        self.browser.getControl(name="form.firstname").value = 'aNNa'
    1924         self.browser.getControl(name="form.reg_number").value = '123'
     2122        self.browser.getControl(name="form.number").value = '123'
    19252123        self.browser.getControl(name="form.email").value = 'new@yy.zz'
    1926         self.browser.getControl("Get login credentials").click()
     2124        self.browser.getControl("Send login credentials").click()
    19272125        # Yeah, we succeded ...
     2126        self.assertTrue('Your password request was successful.'
     2127            in self.browser.contents)
     2128        # We can also use the matric_number instead.
     2129        self.browser.open('http://localhost/app/requestpw')
     2130        self.browser.getControl(name="form.firstname").value = 'aNNa'
     2131        self.browser.getControl(name="form.number").value = '234'
     2132        self.browser.getControl(name="form.email").value = 'new@yy.zz'
     2133        self.browser.getControl("Send login credentials").click()
    19282134        self.assertTrue('Your password request was successful.'
    19292135            in self.browser.contents)
     
    19342140            email=('new@yy.zz', 'new@yy.zz')))
    19352141        self.assertEqual(self.student,results[0])
     2142        logfile = os.path.join(
     2143            self.app['datacenter'].storage, 'logs', 'main.log')
     2144        logcontent = open(logfile).read()
     2145        self.assertTrue('zope.anybody - students.browser.StudentRequestPasswordPage - '
     2146                        '234 (K1000000) - new@yy.zz' in logcontent)
    19362147        return
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/tests/test_dynamicroles.py

    r7811 r9169  
    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-async-update/src/waeup/kofa/students/tests/test_export.py

    r8621 r9169  
    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-async-update/src/waeup/kofa/students/tests/test_student.py

    r8735 r9169  
    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-async-update/src/waeup/kofa/students/utils.py

    r8708 r9169  
    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(studentview.context.certcode, size=12)
     141        f_text = Paragraph(f_text, style["Normal"])
     142        data_right.append([f_label,f_text])
     143
    139144    table_left = Table(data_left,style=SLIP_STYLE)
    140145    table_right = Table(data_right,style=SLIP_STYLE, colWidths=[5*cm, 6*cm])
     
    165170    return table
    166171
     172def get_signature_table(signatures, lang='en'):
     173    """Return a reportlab table containing signature fields (with date).
     174    """
     175    style = getSampleStyleSheet()
     176    space_width = 0.4  # width in cm of space between signatures
     177    table_width = 16.0 # supposed width of signature table in cms
     178    # width of signature cells in cm...
     179    sig_col_width = table_width - ((len(signatures) - 1) * space_width)
     180    sig_col_width = sig_col_width / len(signatures)
     181    data = []
     182    col_widths = [] # widths of columns
     183
     184    sig_style = [
     185        ('VALIGN',(0,-1),(-1,-1),'TOP'),
     186        ('FONT', (0,0), (-1,-1), 'Helvetica-BoldOblique', 12),
     187        ('BOTTOMPADDING', (0,0), (-1,0), 36),
     188        ('TOPPADDING', (0,-1), (-1,-1), 0),
     189        ]
     190    for num, elem in enumerate(signatures):
     191        # draw a line above each signature cell (not: empty cells in between)
     192        sig_style.append(
     193            ('LINEABOVE', (num*2,-1), (num*2, -1), 1, colors.black))
     194
     195    row = []
     196    for signature in signatures:
     197        row.append(trans(_('Date:'), lang))
     198        row.append('')
     199        if len(signatures) > 1:
     200            col_widths.extend([sig_col_width*cm, space_width*cm])
     201        else:
     202            col_widths.extend([sig_col_width/2*cm, sig_col_width/2*cm])
     203            row.append('') # empty spaceholder on right
     204    data.append(row[:-1])
     205    data.extend(([''],)*3) # insert 3 empty rows...
     206    row = []
     207    for signature in signatures:
     208        row.append(Paragraph(trans(signature, lang), style["Normal"]))
     209        row.append('')
     210    data.append(row[:-1])
     211    table = Table(data, style=sig_style, repeatRows=len(data),
     212                  colWidths=col_widths)
     213    return table
     214
    167215def docs_as_flowables(view, lang='en'):
    168216    """Create reportlab flowables out of scanned docs.
     
    190238            if img_path is None:
    191239                pass
    192             elif not img_path.endswith('.jpg'):
     240            elif not img_path[-4:] in ('.jpg', '.JPG'):
    193241                # reportlab requires jpg images, I think.
    194                 f_text = Paragraph('%s (Not displayable)' % (
     242                f_text = Paragraph('%s (not displayable)' % (
    195243                    viewlet.title,), ENTRY1_STYLE)
    196244            else:
     
    202250    return data
    203251
    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 
    223252class StudentsUtils(grok.GlobalUtility):
    224253    """A collection of methods subject to customization.
     
    227256
    228257    def getReturningData(self, student):
    229         """ This method defines what happens after school fee payment
     258        """ Define what happens after school fee payment
    230259        depending on the student's senate verdict.
    231260
     
    238267
    239268    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.
     269        """ Define what happens after school fee payment
     270        depending on the student's senate verdict.
     271
     272        This method folllows the same algorithm as getReturningData but
     273        it also sets the new values.
    247274        """
    248275        new_session, new_level = self.getReturningData(student)
     
    250277        student['studycourse'].current_session = new_session
    251278        verdict = student['studycourse'].current_verdict
    252         student['studycourse'].current_verdict = 'NY'
     279        student['studycourse'].current_verdict = '0'
    253280        student['studycourse'].previous_verdict = verdict
    254281        return
    255282
    256     def setPaymentDetails(self, category, student):
     283    def setPaymentDetails(self, category, student,
     284            previous_session, previous_level):
    257285        """Create Payment object and set the payment data of a student for
    258286        the payment category specified.
    259287
    260288        """
    261         details = {}
    262289        p_item = u''
    263290        amount = 0.0
    264         error = u''
    265         p_session = student['studycourse'].current_session
    266         p_level = student['studycourse'].current_level
     291        if previous_session:
     292            p_session = previous_session
     293            p_level = previous_level
     294            p_current = False
     295        else:
     296            p_session = student['studycourse'].current_session
     297            p_level = student['studycourse'].current_level
     298            p_current = True
    267299        session = str(p_session)
    268300        try:
     
    276308            except (AttributeError, TypeError):
    277309                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)
     310            if previous_session:
     311                if previous_level == 100:
     312                    amount = getattr(certificate, 'school_fee_1', 0.0)
     313                else:
     314                    amount = getattr(certificate, 'school_fee_2', 0.0)
     315            else:
     316                if student.state == CLEARED:
     317                    amount = getattr(certificate, 'school_fee_1', 0.0)
     318                elif student.state == RETURNING:
     319                    # In case of returning school fee payment the payment session
     320                    # and level contain the values of the session the student
     321                    # has paid for.
     322                    p_session, p_level = self.getReturningData(student)
     323                    amount = getattr(certificate, 'school_fee_2', 0.0)
     324                elif student.is_postgrad and student.state == PAID:
     325                    # Returning postgraduate students also pay for the next session
     326                    # but their level always remains the same.
     327                    p_session += 1
     328                    amount = getattr(certificate, 'school_fee_2', 0.0)
    291329        elif category == 'clearance':
    292330            p_item = student['studycourse'].certificate.code
     
    296334            amount = academic_session.booking_fee
    297335        if amount in (0.0, None):
    298             return _(u'Amount could not be determined.'), None
     336            return _('Amount could not be determined.' +
     337                     ' Would you like to pay for a previous session?'), None
    299338        for key in student['payments'].keys():
    300339            ticket = student['payments'][key]
     
    303342               ticket.p_item == p_item and \
    304343               ticket.p_session == p_session:
    305                   return _('This type of payment has already been made.'), None
     344                  return _('This type of payment has already been made.' +
     345                           ' Would you like to pay for a previous session?'), None
    306346        payment = createObject(u'waeup.StudentOnlinePayment')
    307         timestamp = "%d" % int(time()*1000)
     347        timestamp = ("%d" % int(time()*10000))[1:]
    308348        payment.p_id = "p%s" % timestamp
    309349        payment.p_category = category
     
    311351        payment.p_session = p_session
    312352        payment.p_level = p_level
     353        payment.p_current = p_current
    313354        payment.amount_auth = amount
    314355        return None, payment
     
    333374            return
    334375        end_level = certificate.end_level
    335         if entry_session == grok.getSite()['hostels'].accommodation_session:
     376        if current_level == 10:
     377            bt = 'pr'
     378        elif entry_session == grok.getSite()['hostels'].accommodation_session:
    336379            bt = 'fr'
    337380        elif current_level >= end_level:
     
    357400    def renderPDF(self, view, filename='slip.pdf', student=None,
    358401                  studentview=None, tableheader=None, tabledata=None,
    359                   note=None):
     402                  note=None, signatures=None):
    360403        """Render pdf slips for various pages.
    361404        """
     
    373416            footer_text = "%s - %s - " % (student.student_id, footer_text)
    374417
     418        # Insert history
     419        if not filename.startswith('payment'):
     420            data.extend(creator.fromStringList(student.history.messages))
     421
    375422        # Insert student data table
    376423        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
     
    399446            contenttable = render_table_data(tableheader,tabledata)
    400447            data.append(contenttable)
     448
     449        # Insert signatures
     450        if signatures:
     451            data.append(Spacer(1, 20))
     452            signaturetable = get_signature_table(signatures)
     453            data.append(signaturetable)
    401454
    402455        view.response.setHeader(
     
    412465
    413466    VERDICTS_DICT = {
    414         'NY': _('(not yet)'),
     467        '0': _('(not yet)'),
    415468        'A': 'Successful student',
    416469        'B': 'Student with carryover courses',
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/viewlets.py

    r8736 r9169  
    1 ## $Id$
     13## $Id$
    22##
    33## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
     
    166166    target = 'manage_base'
    167167
     168class StudentTransfernButton(ManageActionButton):
     169    grok.order(5)
     170    grok.context(IStudent)
     171    grok.view(StudentBaseDisplayFormPage)
     172    grok.require('waeup.manageStudent')
     173    text = _('Transfer student')
     174    target = 'transfer'
     175    icon = 'actionicon_redo.png'
     176
     177class StudentDeactivateActionButton(ManageActionButton):
     178    grok.order(6)
     179    grok.context(IStudent)
     180    grok.view(StudentBaseDisplayFormPage)
     181    grok.require('waeup.manageStudent')
     182    text = _('Deactivate account')
     183    target = 'deactivate'
     184    icon = 'actionicon_traffic_lights_red.png'
     185
     186    @property
     187    def target_url(self):
     188        if self.context.suspended:
     189            return ''
     190        return self.view.url(self.view.context, self.target)
     191
     192    @property
     193    def onclick(self):
     194        return "return window.confirm(%s);" % _(
     195            "'A history message will be added. Are you sure?'")
     196
     197class StudentActivateActionButton(ManageActionButton):
     198    grok.order(6)
     199    grok.context(IStudent)
     200    grok.view(StudentBaseDisplayFormPage)
     201    grok.require('waeup.manageStudent')
     202    text = _('Activate account')
     203    target = 'activate'
     204    icon = 'actionicon_traffic_lights_green.png'
     205
     206    @property
     207    def target_url(self):
     208        if not self.context.suspended:
     209            return ''
     210        return self.view.url(self.view.context, self.target)
     211
     212    @property
     213    def onclick(self):
     214        return "return window.confirm(%s);" % _(
     215            "'A history message will be added. Are you sure?'")
     216
    168217class StudentClearanceManageActionButton(ManageActionButton):
    169218    grok.order(1)
     
    231280    target = 'view_personal'
    232281
     282class StudentPersonalManageActionButton(ManageActionButton):
     283    grok.order(1)
     284    grok.context(IStudent)
     285    grok.view(StudentPersonalDisplayFormPage)
     286    grok.require('waeup.manageStudent')
     287    text = _('Manage')
     288    target = 'manage_personal'
     289
    233290class StudentPersonalEditActionButton(ManageActionButton):
    234     grok.order(1)
     291    grok.order(2)
    235292    grok.context(IStudent)
    236293    grok.view(StudentPersonalDisplayFormPage)
    237     grok.require('waeup.viewStudent')
     294    grok.require('waeup.handleStudent')
    238295    text = _('Edit')
    239296    target = 'edit_personal'
     
    247304    target = 'manage'
    248305
     306    @property
     307    def target_url(self):
     308        if self.context.is_current:
     309            return self.view.url(self.view.context, self.target)
     310        return False
     311
    249312class StudyLevelManageActionButton(ManageActionButton):
    250313    grok.order(1)
     
    254317    text = _('Manage')
    255318    target = 'manage'
     319
     320    @property
     321    def target_url(self):
     322        is_current = self.context.__parent__.is_current
     323        if not is_current:
     324            return ''
     325        return self.view.url(self.view.context, self.target)
    256326
    257327class StudentValidateCoursesActionButton(ManageActionButton):
     
    266336    @property
    267337    def target_url(self):
     338        is_current = self.context.__parent__.is_current
    268339        if self.context.student.state != REGISTERED or \
    269             str(self.context.__parent__.current_level) != self.context.__name__:
     340            str(self.context.__parent__.current_level) != self.context.__name__ or\
     341            not is_current:
    270342            return ''
    271343        return self.view.url(self.view.context, self.target)
     
    282354    @property
    283355    def target_url(self):
     356        is_current = self.context.__parent__.is_current
    284357        if self.context.student.state not in (VALIDATED, REGISTERED) or \
    285             str(self.context.__parent__.current_level) != self.context.__name__:
     358            str(self.context.__parent__.current_level) != self.context.__name__ or\
     359            not is_current:
    286360            return ''
    287361        return self.view.url(self.view.context, self.target)
     
    296370    target = 'course_registration.pdf'
    297371
     372    @property
     373    def target_url(self):
     374        is_current = self.context.__parent__.is_current
     375        if not is_current:
     376            return ''
     377        return self.view.url(self.view.context, self.target)
     378
    298379class CourseTicketManageActionButton(ManageActionButton):
    299380    grok.order(1)
     
    313394
    314395class PaymentReceiptActionButton(ManageActionButton):
    315     grok.order(1)
     396    grok.order(9) # This button should always be the last one.
    316397    grok.context(IStudentOnlinePayment)
    317398    grok.view(OnlinePaymentDisplayFormPage)
     
    327408        return self.view.url(self.view.context, self.target)
    328409
    329 
    330410class ApprovePaymentActionButton(ManageActionButton):
    331     grok.order(2)
     411    grok.order(8)
    332412    grok.context(IStudentOnlinePayment)
    333413    grok.view(OnlinePaymentDisplayFormPage)
     
    437517    grok.require('waeup.handleStudent')
    438518    icon = 'actionicon_start.gif'
    439     text = _('Start session')
     519    text = _('Start new session')
    440520    target = 'start_session'
    441521
    442522    @property
    443523    def target_url(self):
    444         if self.context.next_session_allowed:
     524        if self.context.next_session_allowed and self.context.is_current:
    445525            return self.view.url(self.view.context, self.target)
    446526        return False
     
    460540        condition2 = str(student['studycourse'].current_level) in \
    461541            self.view.context.keys()
    462         if condition1 or condition2:
     542        condition3 = not self.context.is_current
     543        if condition1 or condition2 or condition3:
    463544            return ''
    464545        return self.view.url(self.view.context, self.target)
     
    469550    grok.view(StudyLevelDisplayFormPage)
    470551    grok.require('waeup.handleStudent')
    471     text = _('Add and remove courses')
     552    text = _('Edit course list')
    472553    target = 'edit'
    473554
     
    478559        condition2 = student[
    479560            'studycourse'].current_level != self.view.context.level
    480         if condition1 or condition2:
     561        is_current = self.context.__parent__.is_current
     562        if condition1 or condition2 or not is_current:
    481563            return ''
    482564        return self.view.url(self.view.context, self.target)
  • main/waeup.kofa/branches/uli-async-update/src/waeup/kofa/students/vocabularies.py

    r8766 r9169  
    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-async-update/src/waeup/kofa/students/workflow.py

    r8471 r9169  
    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.