Student Interfaces

The Student class is a container class which means, there are not only attributes describing the student entity but also content. Each student container contains three subcontainers: studycourse (instance of StudentStudyCourse), payments (instance of StudentPaymentsContainer) and accommodation (instance of StudentAccommodation). The purposes of them are described further below. The student container with all its subcontainers and subobjects is called student record.

Let’s talk about the attributes and methods belonging to the Student class, the so-called ‘external behaviour’ of student objects specified in Zope ‘interfaces’. The data stored with each student object are subdivided into three parts: base data, personal data and clearance data. Each part has its own interface.

Note

Interfaces are one of the pillars of Zope’s Component Architecture (ZCA, see also prerequisites). They document the ‘external behaviour’ of objects. In Kofa interfaces are used to specify the attributes, methods and schema fields of objects. The first two are well-known Python concepts. A Zope schema field is a bit more complicated. It’s a detailed description of a field to be submitted via forms to the server, or which is processed by a batch processor. In both cases the input data are being validated against the schema field requirements. In Kofa, schema fields (see also note below) in interfaces are also used to automtically add FieldProperty attributes of the same name to most content classes. This is done by a function attrs_to_fields which is called once during startup of Kofa. These class attributes ensure that corresponding attributes of instances of this class - when being added or changed - always meet the requirements of the schema field. Another big advantage of such class attributes is, that attributes with a FieldProperty do not need to be set in __init__ methods.

The Student class implements several interfaces which we will quote to unveil the student’s ‘external behaviour’.

IStudentBase

waeup.kofa.students.interfaces.IStudentBase defines a fundamental set of attributes, schema fields and methods which are being used throughout the portal. This set is immutable. No class statement must be removed or added in customized versions of Kofa.

class IStudentBase(IKofaObject):
    """Representation of student base data.
    """
    history = Attribute('Object history, a list of messages')
    state = Attribute('Registration state')
    translated_state = Attribute('Real name of the registration state')
    certcode = Attribute('Certificate code of any chosen study course')
    depcode = Attribute('Department code of any chosen study course')
    faccode = Attribute('Faculty code of any chosen study course')
    entry_session = Attribute('Entry session')
    current_session = Attribute('Current session')
    current_level = Attribute('Current level')
    current_mode = Attribute('Current mode')
    current_verdict = Attribute('Current verdict')
    fullname = Attribute('All name parts separated by hyphens')
    display_fullname = Attribute('Fullname as displayed on pages')
    is_postgrad = Attribute('True if postgraduate student')
    is_special_postgrad = Attribute('True if special postgraduate student')
    is_fresh = Attribute('True if fresh student')
    before_payment = Attribute('True if no previous payment has to be made')
    personal_data_expired = Attribute('True if personal data expired')
    transcript_enabled = Attribute('True if transcript processing is enabled')
    clearance_locked = Attribute('True if clearance form is locked')
    studycourse_locked = Attribute(
        'True if nobody is allowed to change studycourse, studylecel or '
        'course ticket data, neither through the UI nor via import')

    password = Attribute('Encrypted password')
    temp_password = Attribute('Dictionary with user name, timestamp and encrypted password')
    parents_password = Attribute('Dictionary with student_id, timestamp and encrypted password')

    suspended = schema.Bool(
        title = _(u'Account suspended'),
        default = False,
        required = False,
        )

    suspended_comment = schema.Text(
        title = _(u"Reasons for Deactivation"),
        required = False,
        description = _(
            u'This message will be shown if and only if deactivated '
            'students try to login.'),
        )

    flash_notice = schema.TextLine(
        title = _(u'Flash Notice'),
        required = False,
        readonly = False,
        description = _(
            u'This single-line message will be shown in a flash box.'),
        )

    student_id = schema.TextLine(
        title = _(u'Student Id'),
        required = False,
        )

    firstname = schema.TextLine(
        title = _(u'First Name'),
        required = True,
        )

    middlename = schema.TextLine(
        title = _(u'Middle Name'),
        required = False,
        )

    lastname = schema.TextLine(
        title = _(u'Last Name (Surname)'),
        required = True,
        )

    sex = schema.Choice(
        title = _(u'Gender'),
        source = GenderSource(),
        required = True,
        )

    reg_number = TextLineChoice(
        title = _(u'Registration Number'),
        required = True,
        readonly = False,
        source = contextual_reg_num_source,
        )

    matric_number = TextLineChoice(
        title = _(u'Matriculation Number'),
        required = False,
        readonly = False,
        source = contextual_mat_num_source,
        )

    adm_code = schema.TextLine(
        title = _(u'PWD Activation Code'),
        required = False,
        readonly = False,
        )

    email = schema.ASCIILine(
        title = _(u'Email'),
        required = False,
        constraint=validate_email,
        )

    phone = PhoneNumber(
        title = _(u'Phone'),
        required = False,
        )

    parents_email = schema.ASCIILine(
        title = _(u"Parents' Email"),
        required = False,
        constraint=validate_email,
        )

    def setTempPassword(user, password):
        """Set a temporary password (LDAP-compatible) SSHA encoded for
        officers.
        """

    def getTempPassword():
        """Check if a temporary password has been set and if it
        is not expired. Return the temporary password if valid,
        None otherwise. Unset the temporary password if expired.
        """

    def setParentsPassword(password):
        """Set a parents password (LDAP-compatible) SSHA encoded for
        parents.
        """

    def getParentsPassword():
        """Check if a parents password has been set and if it
        is not expired.

        Return the parents password if valid,
        None otherwise. Unset the parents password if expired.
        """

    def transfer(certificate, current_session,
        current_level, current_verdict):
        """ Creates a new studycourse and backups the old one.
        """

    def revert_transfer():
        """ Revert previous transfer.
        """

The first part of the interface lists attributes. Except for the last two attributes (password and temp_password) they are all read-only property attributes, i.e. attributes with a getter method only. These properties are computed dynamically and can’t be set. Most of them return data derived from the studycourse subobject.

The second part of the interface specifies the schema fields.

Note

Schema fields are instances of schema field classes with title, description, default, required, readonly attributes. Class names and class attributes are self-explanatory and described in the Zope Schema documentation.

A very powerful Zope Schema field is Choice. Choice fields are constrained to values drawn from a specified set, which can be static or dynamic. This set can be a simple list of choices (= values), a vocabulary or a source which produce dynamic choices.

Vocabularies and sources are very similar. The latter is a newer concept of producing choices and considered to replace vocabularies. In Kofa we use both. Common to both is, that a set of values is accompanied by a user-friendly title, which is displayed on pages (e.g. in select boxes) instead of a less informative value token or even a cryptic representation of a persistent value object. In most cases we have a token-title pair which perfectly describes the values of a source or a vocabulary. The tokens are being sent in forms or imported by batch processors.

See token-title pairs of most (non-database-dependent) sources and vocabularies in the Kofa base package.

The third part of the interface lists the methods which can be applied to student objects.

IUGStudentClearance

Much like all the following student interfaces, waeup.kofa.students.interfaces.IUGStudentClearance describes an additional set of schema fields which are exclusively used on form pages. They do not play any role beyond that purpose. Any kind of schema field can be safely added in customized versions of these interfaces to meet the conditions of the university’s matriculation process. Also date_of_birth and nationality are not used in the base package for further data processing which means, there is no dependency on these two parameters. Attention: This might be different in customized versions of Kofa. Some views might use these parameters to restrict access or make actions (e.g. payment of items) conditional on the student’s nationality or current age.

class IUGStudentClearance(IKofaObject):
    """Representation of undergraduate student clearance data.
    """
    officer_comment = schema.Text(
        title = _(u"Officer's Comment"),
        required = False,
        )

    clr_code = schema.TextLine(
        title = _(u'CLR Activation Code'),
        required = False,
        readonly = False,
        )

    date_of_birth = FormattedDate(
        title = _(u'Date of Birth'),
        required = True,
        show_year = True,
        )

    nationality = schema.Choice(
        vocabulary = nats_vocab,
        title = _(u'Nationality'),
        required = False,
        )

clearance_locked controls the access to the edit form page of clearance data. The corresponding attribute is automatically set by workflow transitions to lock or unlock the edit form page respectively. The attribute can also be set by officers via the manage form page to lock or unlock the page manually.

officer_comment is usually edited by clearance officers when rejecting clearance previously requested by the student. The attribute contains the message which informs the student about the reasons of rejection. The attribute is cleared after final clearance. The message can be also found in students.log.

Both clearance_locked and officer_comment are controlled by workflow transitions not by states, see below.

IPGStudentClearance

Most universities acquire different data from undergraduate (UG) and postgraduate (PG) students. The forms vary widely. Therefore Kofa offers a second interface for acquiring PG clearance data. In the base package IPGStudentClearance inherits from the IUGStudentClearance interface. The additional employer field in the base package only serves as an example.

class IPGStudentClearance(IUGStudentClearance):
    """Representation of postgraduate student clearance data.
    """
    employer = schema.TextLine(
        title = _(u'Employer'),
        required = False,
        readonly = False,
        )

IStudentPersonal

waeup.kofa.students.interfaces.IUGStudentPersonal defines an additional set of personal student data which are permantly editable by students. The set usually contains further contact data and information about the student’s relatives.

class IStudentPersonal(IKofaObject):
    """Representation of student personal data.
    """
    personal_updated = schema.Datetime(
        title = _(u'Updated'),
        required = False,
        readonly = False,
        )

    perm_address = schema.Text(
        title = _(u'Permanent Address'),
        required = False,
        )

Kofa provides a mechanism which ensures that personal data are being kept up to date. Students are regularly requested to update the personal data directly after login. The personal data expire 180 days after saving the data for the last time which is stored in the personal_updated attribute. Then the personal_data_expired property returns True and the student is redirected to the personal data edit page when logging in.

IStudentNavigation

See docstring.

class IStudentNavigation(IStudentNavigationBase):
    """Interface needed for navigation and logging. This interface is
    implemented by all content classes in the students section.
    """
    student = Attribute('Student object of context')

    def writeLogMessage(view, message):
        """Add an INFO message to students.log.
        """

Student Study Course Interfaces

All data related to an individual course of study are stored with the studycourse object. This container object is an instance of the StudentStudyCourse class which implements waeup.kofa.students.interfaces.IStudentStudyCourse, waeup.kofa.students.interfaces.IStudentNavigation and waeup.kofa.students.interfaces.IStudentStudyCourseTranscript. The latter does not add further behaviour to our classes but is needed for transcript pages only. It is not described in the user handbook.

IStudentStudyCourse

class IStudentStudyCourse(IKofaObject):
    """Representation of student study course data.
    """
    next_session_allowed = Attribute('True if the student can proceed to next session')
    is_postgrad = Attribute('True if student is postgraduate student')
    is_current = Attribute('True if the study course is the current course of studies')
    is_previous = Attribute('True if the study course is the previous course of studies')

    certificate = schema.Choice(
        title = _(u'Certificate'),
        source = CertificateSource(),
        required = False,
        )

    entry_mode = schema.Choice(
        title = _(u'Entry Mode'),
        source = StudyModeSource(),
        required = True,
        readonly = False,
        )

    entry_session = schema.Choice(
        title = _(u'Entry Session'),
        source = academic_sessions_vocab,
        #default = datetime.now().year,
        required = True,
        readonly = False,
        )

    current_session = schema.Choice(
        title = _(u'Current Session'),
        source = academic_sessions_vocab,
        required = True,
        readonly = False,
        )

    current_level = schema.Choice(
        title = _(u'Current Level'),
        source = StudyLevelSource(),
        required = False,
        readonly = False,
        )

    current_verdict = schema.Choice(
        title = _(u'Current Verdict'),
        source = VerdictSource(),
        default = '0',
        required = False,
        )

    previous_verdict = schema.Choice(
        title = _(u'Previous Verdict'),
        source = VerdictSource(),
        default = '0',
        required = False,
        )

    def addStudentStudyLevel(cert, studylevel):
        """Add a study level object.
        """

    def getTranscriptData():
        """Get a sorted list of dicts with level and course ticket data.
        This method is used for transcripts.
        """

All attributes are read-only property attributes which are computed dynamically.

The first schema field is a Choice field which allows values from the CertificateSource. The source provides all certificates stored in the portal. This way, StudentStudyCourse objects point to exactly one Certificate object in the portal. If the certificate is removed, an event handler takes care of clearing also all referring certificate attribute. The tokens of the CertificateSource are the certicate codes which means, that in forms and import files the unique code must be given and the desired Certificate object will be stored.

The interface has two entry parameter fields. entry_mode stores the study mode of the programme and entry_session the academic year when the study course was started. Usually these parameters are fixed and must not be changed during course of study.

Very important - or even most important - are the parameters which describe the current state of the student’s study course. current_session, current_level, current_verdict, previous_verdict and the student’s workflow state (see below) declare precisely in which academic session and how successfully the student has reached the current study level and state in the portal. All these attributes are set automatically by the portal und must (normally) not be changed via manage form pages or import.

What is a verdict? A verdict is the individual judgement of a jury (senate in Nigeria) at the end of each academic session. The jury’s verdict tells the portal, if the student has successfully completed an academic session or not. Depending on the verdict, the student will be either allowed to proceed to the next study level or forced to repeat the current level. Without a verdict, the student gets stuck and cannot even pay school fees for the next academic year. This will be further exlplained in the workflow section below.

StudentStudyCourse is a container class. Study courses contain StudentStudyLevel instances which implement waeup.kofa.students.interfaces.IStudentStudyLevel and also waeup.kofa.students.interfaces.IStudentNavigation, see above.

IStudentStudyLevel

StudentStudyLevel instances contain information about the study progress achieved at that level. In Kofa study levels range from 100 to 900. There are two additional levels: a pre-studies level (10) and a special postgraduate level (999). There are also two probation levels per regular study level. Level 120, for instance, means level 100 on second probation. See complete numbering of study levels in the base package.

StudentStudyLevel instances are container objects which contain course tickets. We therefore sometimes speak of a level course list instead of a study level. The study level stores and provides the information when (validation_date) and by whom (validated_by) the course list was validated, in which session the level was taken (level_session) and which verdict the student finally obtained.

class IStudentStudyLevel(IKofaObject):
    """A representation of student study level data. 
    """
    certcode = Attribute('The certificate code of the study course')
    is_current_level = Attribute('True if level is current level of the student')
    level_title = Attribute('Level title from source')
    getSessionString = Attribute('Session title from source')
    number_of_tickets = Attribute('Number of tickets contained in this level')
    passed_params = Attribute('Information about passed and failed courses')
    gpa_params_rectified = Attribute('Corrected sessional GPA parameters')
    gpa_params = Attribute('GPA parameters for this level.')
    cumulative_params = Attribute(
        'Cumulative GPA and other cumulative parameters for this level')
    course_registration_forbidden = Attribute(
        'Return error message if course registration is forbidden')

    level = schema.Choice(
        title = _(u'Level'),
        source = StudyLevelSource(),
        required = True,
        readonly = False,
        )

    level_session = schema.Choice(
        title = _(u'Session'),
        source = academic_sessions_vocab,
        required = True,
        )

    level_verdict = schema.Choice(
        title = _(u'Verdict'),
        source = VerdictSource(),
        default = '0',
        required = False,
        )

    validated_by = schema.TextLine(
        title = _(u'Validated by'),
        default = None,
        required = False,
        )

    validation_date = schema.Datetime(
        title = _(u'Validation Date'),
        required = False,
        readonly = False,
        )

    total_credits = schema.Int(
        title = _(u'Total Credits'),
        required = False,
        readonly = True,
        )

    gpa = schema.TextLine(
        title = _(u'Unrectified GPA'),
        required = False,
        readonly = True,
        )

    transcript_remark = schema.Text(
        title = _(u'Transcript Remark'),
        required = False,
        readonly = False,
        )

    def addCourseTicket(ticket, course):
        """Add a course ticket object.
        """

    def addCertCourseTickets(cert):
        """Collect all certificate courses and create course
        tickets automatically.
        """

    def updateCourseTicket(ticket, course):
        """Updates a course ticket object and return code
        if ticket has been invalidated.
        """

StudentStudyLevel instances also compute some statistics on the fly to give an overview of the courses taken. These read-only property attributes are: number_of_tickets, passed_params, gpa_params, gpa_params_rectified, cumulative_params, total_credits and gpa. The attentive reader may wonder why the latter two are not listed in the attributes section of the interface, but as read-only schema fields further below. This is a trick which we used to properly display these fields on some display pages and pdf slips. However, the attrs_to_fields function explicitly excludes them when starting the portal, so that these fields are not used for persistent storage of same-named attributes in the database.

The property attribute course_registration_allowed determines whether course registration has ended. In the base package students can proceed if they have paid late registration fee. The conditions for course registration can be configured in custom packages.

ICourseTicket

Course tickets allow students to attend a course. Lecturers can enter scores at the end of the term. A CourseTicket instance contains a copy of the original Course and CertificateCourse data. If the courses and/or the referring certificate courses are removed, the corresponding tickets remain unchanged. So we do not need any event triggered actions on course tickets.

The CourseTicket implements waeup.kofa.students.interfaces.ICourseTicket and also waeup.kofa.students.interfaces.IStudentNavigation, see above.

class ICourseTicket(IKofaObject):
    """A representation of course ticket data.
    """
    certcode = Attribute('Certificate code of the study course')
    level_session = Attribute('Session of the study level the ticket has been added to')
    level = Attribute('Level value of the study level the ticket has been added to')
    total_score = Attribute('Score')
    grade = Attribute('Grade calculated from total score')
    weight = Attribute('Weight calculated from total score')
    removable_by_student = Attribute('True if student is allowed to remove the ticket')
    editable_by_lecturer = Attribute('True if lecturer is allowed to edit the ticket')

    code = Attribute('Code of the original course')

    title = schema.TextLine(
        title = _(u'Title'),
        required = False,
        )

    fcode = schema.TextLine(
        title = _(u'Faculty Code'),
        required = False,
        )

    dcode = schema.TextLine(
        title = _(u'Department Code'),
        required = False,
        )

    semester = schema.Choice(
        title = _(u'Semester/Term'),
        source = SemesterSource(),
        required = False,
        )

    ticket_session = schema.Choice(
        title = _(u'Imported Session'),
        source = academic_sessions_vocab,
        required = False,
        )

    passmark = schema.Int(
        title = _(u'Passmark'),
        required = False,
        )

    credits = schema.Int(
        title = _(u'Credits'),
        required = False,
        )

    mandatory = schema.Bool(
        title = _(u'Required'),
        default = False,
        required = False,
        )

    outstanding = schema.Bool(
        title = _(u'Outstanding Course'),
        default = False,
        required = False,
        )

    course_category = schema.Choice(
        title = _(u'Course Category'),
        source = CourseCategorySource(),
        required = False,
        )

    score = schema.Int(
        title = _(u'Score'),
        default = None,
        required = False,
        missing_value = None,
        )

    carry_over = schema.Bool(
        title = _(u'Carry-over Course'),
        default = False,
        required = False,
        )

    automatic = schema.Bool(
        title = _(u'Automatical Creation'),
        default = False,
        required = False,
        )

The quite long list of schema fields pretends that form pages may provide these fields for editing. This is not the case. Except for score, mandatory, outstanding and course_category (not available in base package) all fields are ‘for display’ only. They cannot be changed through the UI. They are solely meant for backing up the orginal course data. However, some of them can be overwritten by batch processing (import), see Course Ticket Processor.

ticket_session can but must not be used for storing the session in which the course was taken. Usually, level and session are inherited from the parent study level container. Storing the same information in course tickets is not necessary, unless the session really differs or ‘orphaned’ course tickets (tickets without level assignment) have to be stored in the Level Zero container. ticket_session can’t be edited through the UI, they only changed by import.

Note

It may happen that a course has been accidentally misconfigured, for example the number of credits was set too high. The course object can be corrected, but, course tickets, which refer to this course, can not. They are not adjusted automatically. If you want to correct course tickets you have to replace them by new tickets or to repair the data by batch processing.

Student Payment Interfaces

IStudentOnlinePayment

IStudentOnlinePayment instances are called student payment tickets or sometimes only payments. They contain the data which confirm that the student has paid a specific fee. waeup.kofa.students.interfaces.IStudentOnlinePayment inherits from waeup.kofa.payments.interfaces.IOnlinePayment and has only two additional schema fields: p_current and p_level which are student-specific. It also promises three additional methods which process student data after successful or approved payment.

class IStudentOnlinePayment(IOnlinePayment):
    """A student payment via payment gateways.
    """

    certificate = Attribute('Certificate to determine the correct p_level value')
    student = Attribute('Student')

    p_current = schema.Bool(
        title = _(u'Current Session Payment'),
        default = True,
        required = False,
        )

    p_level = schema.Choice(
        title = _(u'Payment Level'),
        source = StudyLevelSource(),
        required = False,
        )

    def redeemTicket():
        """Either create an appropriate access code or trigger an action
        directly.
        """

    def doAfterStudentPayment():
        """Process student after payment was made.
        """

    def doAfterStudentPaymentApproval():
        """Process student after payment was approved.
        """

    def approveStudentPayment():
        """Approve payment and process student.
        """

Student Accommodation Interfaces

IBedTicket

A bed ticket confirms that a bed space in a hostel has been allocated to the student for the period of an academic session (booking_session). The bed_coordinates specify which bed space has been booked. Sometimes this value is too cryptic and inappropriate to guide the student to the bed space. The display_coordinates property can be used to ‘translate’ the coordinates and display a customary description of the bed space. Much like some study level attributes, also display_coordinates is defined as a read-only schema field in order to automatically display the field on form pages. It is excluded by the attrs_to_fields function and thus not stored in the database.

class IBedTicket(IKofaObject):
    """A representation of accommodation booking data.
    """
    bed = Attribute('The bed object')
    maint_payment_made = Attribute('True if maintenance payment is made')

    display_coordinates = schema.TextLine(
        title = _(u'Allocated Bed'),
        required = False,
        readonly = True,
        )

    bed_coordinates = schema.TextLine(
        title = u'',
        required = True,
        readonly = False,
        )

    bed_type = schema.TextLine(
        title = _(u'Requested Bed Type'),
        required = True,
        readonly = False,
        )

    booking_session = schema.Choice(
        title = _(u'Session'),
        source = academic_sessions_vocab,
        required = True,
        readonly = False
        )

    booking_date = schema.Datetime(
        title = _(u'Booking Date'),
        required = False,
        readonly = False,
        )

    booking_code = schema.TextLine(
        title = _(u'Booking Activation Code'),
        required = False,
        readonly = False,
        )

    def getSessionString():
        """Returns the title of academic_sessions_vocab term of the session
        when the bed was booked.
        """