source: main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py @ 13031

Last change on this file since 13031 was 13031, checked in by Henrik Bettermann, 9 years ago

Implement session-specific course registration deadline and
late registration payment.

  • Property svn:keywords set to Id
File size: 26.0 KB
Line 
1## $Id: interfaces.py 13031 2015-06-04 14:21:23Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18#from datetime import datetime
19from zope.component import getUtility
20from zope.interface import Attribute, Interface
21from zope import schema
22from zc.sourcefactory.contextual import BasicContextualSourceFactory
23from waeup.kofa.browser.interfaces import IStudentNavigationBase
24from waeup.kofa.interfaces import (
25    IKofaObject, academic_sessions_vocab, validate_email, ICSVExporter,
26    ContextualDictSourceFactoryBase)
27from waeup.kofa.interfaces import MessageFactory as _
28from waeup.kofa.schema import TextLineChoice, FormattedDate, PhoneNumber
29from waeup.kofa.students.vocabularies import (
30    StudyLevelSource, contextual_reg_num_source, contextual_mat_num_source,
31    GenderSource, nats_vocab
32    )
33from waeup.kofa.payments.interfaces import (
34    IPaymentsContainer, IOnlinePayment)
35from waeup.kofa.university.vocabularies import (
36    CourseSource, StudyModeSource, CertificateSource, SemesterSource,
37    )
38
39class PreviousPaymentCategorySource(ContextualDictSourceFactoryBase):
40    """A source that delivers all selectable categories of previous session
41    payments.
42    """
43    #: name of dict to deliver from kofa utils.
44    DICT_NAME = 'PREVIOUS_PAYMENT_CATEGORIES'
45
46class BalancePaymentCategorySource(ContextualDictSourceFactoryBase):
47    """A source that delivers all selectable items of balance payments.
48    """
49    #: name of dict to deliver from kofa utils.
50    DICT_NAME = 'BALANCE_PAYMENT_CATEGORIES'
51
52# VerdictSource can't be placed into the vocabularies module because it
53# requires importing IStudentsUtils which then leads to circular imports.
54class VerdictSource(BasicContextualSourceFactory):
55    """A verdicts source delivers all verdicts provided
56    in the portal.
57    """
58    def getValues(self, context):
59        verdicts_dict = getUtility(IStudentsUtils).VERDICTS_DICT
60        return sorted(verdicts_dict.keys())
61
62    def getToken(self, context, value):
63        return value
64
65    def getTitle(self, context, value):
66        verdicts_dict = getUtility(IStudentsUtils).VERDICTS_DICT
67        if value != '0':
68            return verdicts_dict[value] + ' (%s)' % value
69        return verdicts_dict[value]
70
71
72class IStudentsUtils(Interface):
73    """A collection of methods which are subject to customization.
74    """
75    def setReturningData(student):
76        """ This method defines what happens after school fee payment
77        depending on the student's senate verdict.
78
79        In the base configuration current level is always increased
80        by 100 no matter which verdict has been assigned.
81        """
82
83    def setPaymentDetails(category, student, previous_session=None,
84            previous_level=None,):
85        """Create Payment object and set the payment data of a student for
86        the payment category specified.
87        """
88
89    def increaseMatricInteger(student):
90        """Increase counter for matric numbers.
91
92        This counter can be a centrally stored attribute or an attribute of
93        faculties, departments or certificates. In the base package the counter
94        is as an attribute of the site configuration object.
95        """
96
97    def constructMatricNumber(student):
98        """Fetch the matric number counter which fits the student and
99        construct the new matric number of the student.
100
101        In the base package the counter is returned which is as an attribute
102        of the site configuration object.
103        """
104
105    def setMatricNumber(student):
106        """Set matriculation number of student.
107
108        If the student's matric number is unset a new matric number is
109        constructed according to the matriculation number construction rules
110        defined in the constructMatricNumber method. The new matric number is
111        set, the students catalog updated. The corresponding matric number
112        counter is increased by one.
113
114        This method is tested but not used in the base package. It can
115        be used in custom packages by adding respective views
116        and by customizing increaseMatricInteger and constructMatricNumber
117        according to the university's matriculation number construction rules.
118
119        The method can be disabled by setting the counter to zero.
120        """
121
122    def getAccommodation_details(student):
123        """Determine the accommodation dates of a student.
124        """
125
126    def selectBed(available_beds):
127        """Select a bed from a list of available beds.
128        In the standard configuration we select the first bed found,
129        but can also randomize the selection if we like.
130        """
131
132    def getPDFCreator(context):
133        """Get some IPDFCreator instance suitable for use with `context`.
134        """
135
136    def renderPDF(view, subject='', filename='slip.pdf',):
137        """Render pdf slips for various pages.
138        """
139
140class IStudentsContainer(IKofaObject):
141    """A students container contains university students.
142    """
143    def addStudent(student):
144        """Add an IStudent object and subcontainers.
145        """
146
147    def archive(id=None):
148        """Create on-dist archive of students.
149
150        If id is `None`, all students are archived.
151
152        If id contains a single id string, only the respective
153        students are archived.
154
155        If id contains a list of id strings all of the respective
156        students types are saved to disk.
157        """
158
159    def clear(id=None, archive=True):
160        """Remove students of type given by 'id'.
161
162        Optionally archive the students.
163
164        If id is `None`, all students are archived.
165
166        If id contains a single id string, only the respective
167        students are archived.
168
169        If id contains a list of id strings all of the respective
170        student types are saved to disk.
171
172        If `archive` is ``False`` none of the archive-handling is done
173        and respective students are simply removed from the
174        database.
175        """
176
177    unique_student_id = Attribute("""A unique student id.""")
178
179class IStudentNavigation(IStudentNavigationBase):
180    """Interface needed for navigation and logging. This interface is
181    implemented by all content classes in the student section.
182    """
183    student = Attribute('Student object of context')
184
185    def writeLogMessage(view, message):
186        """Write a view specific log message into students.log.
187        """
188
189class IStudentBase(IKofaObject):
190    """Representation of student base data.
191    """
192    history = Attribute('Object history, a list of messages')
193    state = Attribute('Registration state')
194    certcode = Attribute('Certificate code of any chosen study course')
195    depcode = Attribute('Department code of any chosen study course')
196    faccode = Attribute('Faculty code of any chosen study course')
197    entry_session = Attribute('Entry session')
198    current_session = Attribute('Current session')
199    current_level = Attribute('Current level')
200    current_mode = Attribute('Current mode')
201    current_verdict = Attribute('Current verdict')
202    fullname = Attribute('All name parts separated by hyphens')
203    display_fullname = Attribute('Fullname as displayed on pages')
204    is_postgrad = Attribute('True if postgraduate student')
205    is_special_postgrad = Attribute('True if special postgraduate student')
206    is_fresh = Attribute('True if fresh student')
207    before_payment = Attribute('True if no previous payment has to be made')
208    personal_data_expired = Attribute('True if personal data expired')
209    transcript_enabled = Attribute('True if transcript processing is enabled')
210
211    password = Attribute('Encrypted password')
212    temp_password = Attribute('Dictionary with user name, timestamp and encrypted password')
213
214    suspended = schema.Bool(
215        title = _(u'Account suspended'),
216        default = False,
217        required = False,
218        )
219
220    suspended_comment = schema.Text(
221        title = _(u"Reasons for Deactivation"),
222        required = False,
223        description = _(
224            u'This message will be shown if and only if deactivated '
225            'students try to login.'),
226        )
227
228    student_id = schema.TextLine(
229        title = _(u'Student Id'),
230        required = False,
231        )
232
233    firstname = schema.TextLine(
234        title = _(u'First Name'),
235        required = True,
236        )
237
238    middlename = schema.TextLine(
239        title = _(u'Middle Name'),
240        required = False,
241        )
242
243    lastname = schema.TextLine(
244        title = _(u'Last Name (Surname)'),
245        required = True,
246        )
247
248    sex = schema.Choice(
249        title = _(u'Sex'),
250        source = GenderSource(),
251        required = True,
252        )
253
254    reg_number = TextLineChoice(
255        title = _(u'Registration Number'),
256        required = True,
257        readonly = False,
258        source = contextual_reg_num_source,
259        )
260
261    matric_number = TextLineChoice(
262        title = _(u'Matriculation Number'),
263        required = False,
264        readonly = False,
265        source = contextual_mat_num_source,
266        )
267
268    adm_code = schema.TextLine(
269        title = _(u'PWD Activation Code'),
270        required = False,
271        readonly = False,
272        )
273
274    email = schema.ASCIILine(
275        title = _(u'Email'),
276        required = False,
277        constraint=validate_email,
278        )
279    phone = PhoneNumber(
280        title = _(u'Phone'),
281        description = u'',
282        required = False,
283        )
284
285    def setTempPassword(user, password):
286        """Set a temporary password (LDAP-compatible) SSHA encoded for
287        officers.
288        """
289
290    def getTempPassword():
291        """Check if a temporary password has been set and if it
292        is not expired. Return the temporary password if valid,
293        None otherwise. Unset the temporary password if expired.
294        """
295
296    def transfer(certificate, current_session,
297        current_level, current_verdict):
298        """ Creates a new studycourse and backups the old one.
299        """
300
301    def revert_transfer():
302        """ Revert previous transfer.
303        """
304
305class IUGStudentClearance(IKofaObject):
306    """Representation of undergraduate student clearance data.
307    """
308    officer_comment = schema.Text(
309        title = _(u"Officer's Comment"),
310        required = False,
311        )
312
313    clearance_locked = schema.Bool(
314        title = _(u'Clearance form locked'),
315        default = False,
316        required = False,
317        )
318
319    clr_code = schema.TextLine(
320        title = _(u'CLR Activation Code'),
321        required = False,
322        readonly = False,
323        )
324
325    date_of_birth = FormattedDate(
326        title = _(u'Date of Birth'),
327        required = True,
328        show_year = True,
329        )
330
331    nationality = schema.Choice(
332        vocabulary = nats_vocab,
333        title = _(u'Nationality'),
334        required = False,
335        )
336
337class IPGStudentClearance(IUGStudentClearance):
338    """Representation of postgraduate student clearance data.
339    """
340    employer = schema.TextLine(
341        title = _(u'Employer'),
342        required = False,
343        readonly = False,
344        )
345
346class IStudentPersonal(IKofaObject):
347    """Representation of student personal data.
348    """
349    personal_updated = schema.Datetime(
350        title = _(u'Updated'),
351        required = False,
352        readonly = False,
353        )
354
355    perm_address = schema.Text(
356        title = _(u'Permanent Address'),
357        required = False,
358        )
359
360class IStudentTranscript(IKofaObject):
361    """Representation of student transcript data.
362    """
363
364    transcript_comment = schema.Text(
365        title = _(u'Comment'),
366        required = False,
367        )
368
369
370class IStudent(IStudentBase,IUGStudentClearance,IPGStudentClearance,
371    IStudentPersonal, IStudentTranscript):
372    """Representation of a student.
373    """
374
375class IStudentPersonalEdit(IStudentPersonal):
376    """Interface for editing personal data by students.
377    Here we can repeat the fields from IStudentPersonal and set the
378    `required` if necessary.
379    """
380
381    perm_address = schema.Text(
382        title = _(u'Permanent Address'),
383        required = True,
384        )
385
386class IStudentUpdateByRegNo(IStudent):
387    """Representation of a student. Skip regular reg_number validation.
388    """
389    reg_number = schema.TextLine(
390        title = _(u'Registration Number'),
391        required = False,
392        )
393
394class IStudentUpdateByMatricNo(IStudent):
395    """Representation of a student. Skip regular matric_number validation.
396    """
397    matric_number = schema.TextLine(
398        title = _(u'Matriculation Number'),
399        required = False,
400        )
401
402class IStudentRequestPW(IStudent):
403    """Representation of a student for first-time password request.
404    This interface is used when students use the requestpw page to
405    login for the the first time.
406    """
407    number = schema.TextLine(
408        title = _(u'Registr. or Matric. Number'),
409        required = True,
410        )
411
412    firstname = schema.TextLine(
413        title = _(u'First Name'),
414        required = True,
415        )
416
417    email = schema.ASCIILine(
418        title = _(u'Email Address'),
419        required = True,
420        constraint=validate_email,
421        )
422
423class IStudentStudyCourse(IKofaObject):
424    """Representation of student study course data.
425    """
426    next_session_allowed = Attribute('True if the student can proceed to next session')
427    is_postgrad = Attribute('True if student is postgraduate student')
428    is_current = Attribute('True if the study course is the current course of studies')
429    is_previous = Attribute('True if the study course is the previous course of studies')
430
431    certificate = schema.Choice(
432        title = _(u'Certificate'),
433        source = CertificateSource(),
434        required = False,
435        )
436
437    entry_mode = schema.Choice(
438        title = _(u'Entry Mode'),
439        source = StudyModeSource(),
440        required = True,
441        readonly = False,
442        )
443
444    entry_session = schema.Choice(
445        title = _(u'Entry Session'),
446        source = academic_sessions_vocab,
447        #default = datetime.now().year,
448        required = True,
449        readonly = False,
450        )
451
452    current_session = schema.Choice(
453        title = _(u'Current Session'),
454        source = academic_sessions_vocab,
455        required = True,
456        readonly = False,
457        )
458
459    current_level = schema.Choice(
460        title = _(u'Current Level'),
461        source = StudyLevelSource(),
462        required = False,
463        readonly = False,
464        )
465
466    current_verdict = schema.Choice(
467        title = _(u'Current Verdict'),
468        source = VerdictSource(),
469        default = '0',
470        required = False,
471        )
472
473    previous_verdict = schema.Choice(
474        title = _(u'Previous Verdict'),
475        source = VerdictSource(),
476        default = '0',
477        required = False,
478        )
479
480    def addStudentStudyLevel(cert, studylevel):
481        """Add a study level object.
482        """
483
484    def getTranscriptData():
485        """Get a sorted list of dicts with level and course ticket data.
486        This method is used for transcripts.
487        """
488
489class IStudentStudyCourseTransfer(IStudentStudyCourse):
490    """An interface used for student transfers.
491    """
492    certificate = schema.Choice(
493        title = _(u'Certificate'),
494        source = CertificateSource(),
495        required = True,
496        )
497
498    current_level = schema.Choice(
499        title = _(u'Current Level'),
500        source = StudyLevelSource(),
501        required = True,
502        readonly = False,
503        )
504
505    entry_session = schema.Choice(
506        title = _(u'Entry Session'),
507        source = academic_sessions_vocab,
508        #default = datetime.now().year,
509        required = False,
510        readonly = False,
511        )
512
513
514IStudentStudyCourseTransfer['certificate'].order = IStudentStudyCourse[
515    'certificate'].order
516IStudentStudyCourseTransfer['current_level'].order = IStudentStudyCourse[
517    'current_level'].order
518
519class IStudentStudyCourseTranscript(IKofaObject):
520    """An interface for student transcripts.
521    """
522    entry_mode = schema.Choice(
523        title = _(u'Entry Mode'),
524        source = StudyModeSource(),
525        required = True,
526        readonly = False,
527        )
528
529    entry_session = schema.Choice(
530        title = _(u'Entry Session'),
531        source = academic_sessions_vocab,
532        #default = datetime.now().year,
533        required = True,
534        readonly = False,
535        )
536
537class IStudentVerdictUpdate(IKofaObject):
538    """A interface for verdict imports.
539    """
540    current_verdict = schema.Choice(
541        title = _(u'Current Verdict'),
542        source = VerdictSource(),
543        required = True,
544        )
545
546    current_session = schema.Choice(
547        title = _(u'Current Session'),
548        source = academic_sessions_vocab,
549        required = True,
550        )
551
552    current_level = schema.Choice(
553        title = _(u'Current Level'),
554        source = StudyLevelSource(),
555        required = True,
556        )
557
558    bypass_validation = schema.Bool(
559        title = _(u'Bypass validation'),
560        required = False,
561        )
562
563    validated_by = schema.TextLine(
564        title = _(u'Validated by'),
565        required = False,
566        )
567
568class IStudentStudyLevel(IKofaObject):
569    """A representation of student study level data.
570    """
571    certcode = Attribute('The certificate code of the study course')
572    is_current_level = Attribute('True if level is current level of the student')
573    level_title = Attribute('Level title from source')
574    getSessionString = Attribute('Session title from source')
575    number_of_tickets = Attribute('Number of tickets contained in this level')
576    passed_params = Attribute('Information about passed and failed courses')
577    gpa_params_rectified = Attribute('Corrected sessional GPA parameters')
578    gpa_params = Attribute('GPA parameters for this level.')
579    cumulative_params = Attribute('Cumulative GPA and other cumulative parameters for this level')
580    course_registration_allowed = Attribute('True if registerin courses is allowed')
581
582    level = schema.Choice(
583        title = _(u'Level'),
584        source = StudyLevelSource(),
585        required = True,
586        readonly = False,
587        )
588
589    level_session = schema.Choice(
590        title = _(u'Session'),
591        source = academic_sessions_vocab,
592        required = True,
593        )
594
595    level_verdict = schema.Choice(
596        title = _(u'Verdict'),
597        source = VerdictSource(),
598        default = '0',
599        required = False,
600        )
601
602    validated_by = schema.TextLine(
603        title = _(u'Validated by'),
604        default = None,
605        required = False,
606        )
607
608    validation_date = schema.Datetime(
609        title = _(u'Validation Date'),
610        required = False,
611        readonly = False,
612        )
613
614    total_credits = schema.Int(
615        title = _(u'Total Credits'),
616        required = False,
617        readonly = True,
618        )
619
620    gpa = schema.Int(
621        title = _(u'Unrectified GPA'),
622        required = False,
623        readonly = True,
624        )
625
626    def addCourseTicket(ticket, course):
627        """Add a course ticket object.
628        """
629
630    def addCertCourseTickets(cert):
631        """Collect all certificate courses and create course
632        tickets automatically.
633        """
634
635class ICourseTicket(IKofaObject):
636    """A representation of course ticket data.
637    """
638    certcode = Attribute('Certificate code of the study course')
639    level_session = Attribute('Session of the study level the ticket has been added to')
640    level = Attribute('Level value of the study level the ticket has been added to')
641    grade = Attribute('Grade calculated from score')
642    weight = Attribute('Weight calculated from score')
643    removable_by_student = Attribute('True if student is allowed to remove the ticket')
644    editable_by_lecturer = Attribute('True if lecturer is allowed to edit the ticket')
645
646    code = Attribute('Code of the original course')
647
648    title = schema.TextLine(
649        title = _(u'Title'),
650        required = False,
651        )
652
653    fcode = schema.TextLine(
654        title = _(u'Faculty Code'),
655        required = False,
656        )
657
658    dcode = schema.TextLine(
659        title = _(u'Department Code'),
660        required = False,
661        )
662
663    semester = schema.Choice(
664        title = _(u'Semester/Term'),
665        source = SemesterSource(),
666        required = False,
667        )
668
669    passmark = schema.Int(
670        title = _(u'Passmark'),
671        required = False,
672        )
673
674    credits = schema.Int(
675        title = _(u'Credits'),
676        required = False,
677        )
678
679    mandatory = schema.Bool(
680        title = _(u'Required'),
681        default = False,
682        required = False,
683        )
684
685    score = schema.Int(
686        title = _(u'Score'),
687        default = None,
688        required = False,
689        missing_value = None,
690        )
691
692    carry_over = schema.Bool(
693        title = _(u'Carry-over Course'),
694        default = False,
695        required = False,
696        )
697
698    automatic = schema.Bool(
699        title = _(u'Automatical Creation'),
700        default = False,
701        required = False,
702        )
703
704class ICourseTicketAdd(IKofaObject):
705    """An interface for adding course tickets.
706    """
707    course = schema.Choice(
708        title = _(u'Course'),
709        source = CourseSource(),
710        readonly = False,
711        )
712
713class ICourseTicketImport(ICourseTicket):
714    """An interface for importing course results and nothing more.
715    """
716    score = schema.Int(
717        title = _(u'Score'),
718        required = False,
719        readonly = False,
720        )
721
722    level_session = schema.Choice(
723        title = _(u'Level Session'),
724        source = academic_sessions_vocab,
725        required = False,
726        readonly = False,
727        )
728
729class IStudentAccommodation(IKofaObject):
730    """A container for student accommodation objects.
731    """
732
733    def addBedTicket(bedticket):
734        """Add a bed ticket object.
735        """
736
737
738class IBedTicket(IKofaObject):
739    """A representation of accommodation booking data.
740    """
741    bed = Attribute('The bed object.')
742    booking_date = Attribute('Date of booking the bed')
743
744    display_coordinates = schema.TextLine(
745        title = _(u'Allocated Bed'),
746        required = False,
747        readonly = True,
748        )
749
750    bed_coordinates = schema.TextLine(
751        title = u'',
752        required = True,
753        readonly = False,
754        )
755
756    bed_type = schema.TextLine(
757        title = _(u'Requested Bed Type'),
758        required = True,
759        readonly = False,
760        )
761
762    booking_session = schema.Choice(
763        title = _(u'Session'),
764        source = academic_sessions_vocab,
765        required = True,
766        readonly = True,
767        )
768
769    booking_date = schema.Datetime(
770        title = _(u'Booking Date'),
771        required = False,
772        readonly = True,
773        )
774
775    booking_code = schema.TextLine(
776        title = _(u'Booking Activation Code'),
777        required = False,
778        readonly = True,
779        )
780
781    def getSessionString():
782        """Returns the title of academic_sessions_vocab term of the session
783        when the bed was booked.
784        """
785
786class IStudentPaymentsContainer(IPaymentsContainer):
787    """A container for student payment objects.
788    """
789
790class IStudentOnlinePayment(IOnlinePayment):
791    """A student payment via payment gateways.
792    """
793
794    p_current = schema.Bool(
795        title = _(u'Current Session Payment'),
796        default = True,
797        required = False,
798        )
799
800    p_level = schema.Int(
801        title = _(u'Payment Level'),
802        required = False,
803        )
804
805    def doAfterStudentPayment():
806        """Process student after payment was made.
807        """
808
809    def doAfterStudentPaymentApproval():
810        """Process student after payment was approved.
811        """
812
813    def approveStudentPayment():
814        """Approve payment and process student.
815        """
816
817IStudentOnlinePayment['p_level'].order = IStudentOnlinePayment[
818    'p_session'].order
819
820class IStudentPreviousPayment(IKofaObject):
821    """An interface for adding previous session payments.
822    """
823
824    p_category = schema.Choice(
825        title = _(u'Payment Category'),
826        default = u'schoolfee',
827        source = PreviousPaymentCategorySource(),
828        required = True,
829        )
830
831    p_session = schema.Choice(
832        title = _(u'Payment Session'),
833        source = academic_sessions_vocab,
834        required = True,
835        )
836
837    p_level = schema.Choice(
838        title = _(u'Payment Level'),
839        source = StudyLevelSource(),
840        required = True,
841        )
842
843class IStudentBalancePayment(IKofaObject):
844    """An interface for adding balances.
845    """
846
847    p_category = schema.Choice(
848        title = _(u'Payment Category'),
849        default = u'schoolfee',
850        required = True,
851        source = BalancePaymentCategorySource(),
852        )
853
854    balance_session = schema.Choice(
855        title = _(u'Payment Session'),
856        source = academic_sessions_vocab,
857        required = True,
858        )
859
860    balance_level = schema.Choice(
861        title = _(u'Payment Level'),
862        source = StudyLevelSource(),
863        required = True,
864        )
865
866    balance_amount = schema.Float(
867        title = _(u'Balance Amount'),
868        default = None,
869        required = True,
870        readonly = False,
871        description = _(
872            u'Balance in Naira '),
873        )
874
875class ICSVStudentExporter(ICSVExporter):
876    """A regular ICSVExporter that additionally supports exporting
877      data from a given student object.
878    """
879    def get_filtered(site, **kw):
880        """Get a filtered set of students.
881        """
882
883    def get_selected(site, selected):
884        """Get set of selected students.
885        """
886
887    def export_student(student, filepath=None):
888        """Export data for a given student.
889        """
890
891    def export_filtered(site, filepath=None, **kw):
892        """Export data for filtered set of students.
893        """
894
895    def export_selected(site, filepath=None, **kw):
896        """Export data for selected set of students.
897        """
Note: See TracBrowser for help on using the repository browser.