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

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

Move VERDICTS_DICT to KofaUtils?.

  • Property svn:keywords set to Id
File size: 26.0 KB
Line 
1## $Id: interfaces.py 13125 2015-07-01 18:35: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, IKofaUtils)
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(IKofaUtils).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(IKofaUtils).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 students 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    translated_state = Attribute('Real name of the registration state')
195    certcode = Attribute('Certificate code of any chosen study course')
196    depcode = Attribute('Department code of any chosen study course')
197    faccode = Attribute('Faculty code of any chosen study course')
198    entry_session = Attribute('Entry session')
199    current_session = Attribute('Current session')
200    current_level = Attribute('Current level')
201    current_mode = Attribute('Current mode')
202    current_verdict = Attribute('Current verdict')
203    fullname = Attribute('All name parts separated by hyphens')
204    display_fullname = Attribute('Fullname as displayed on pages')
205    is_postgrad = Attribute('True if postgraduate student')
206    is_special_postgrad = Attribute('True if special postgraduate student')
207    is_fresh = Attribute('True if fresh student')
208    before_payment = Attribute('True if no previous payment has to be made')
209    personal_data_expired = Attribute('True if personal data expired')
210    transcript_enabled = Attribute('True if transcript processing is enabled')
211    clearance_locked = Attribute('True if clearance form is locked')
212
213    password = Attribute('Encrypted password')
214    temp_password = Attribute('Dictionary with user name, timestamp and encrypted password')
215
216    suspended = schema.Bool(
217        title = _(u'Account suspended'),
218        default = False,
219        required = False,
220        )
221
222    suspended_comment = schema.Text(
223        title = _(u"Reasons for Deactivation"),
224        required = False,
225        description = _(
226            u'This message will be shown if and only if deactivated '
227            'students try to login.'),
228        )
229
230    student_id = schema.TextLine(
231        title = _(u'Student Id'),
232        required = False,
233        )
234
235    firstname = schema.TextLine(
236        title = _(u'First Name'),
237        required = True,
238        )
239
240    middlename = schema.TextLine(
241        title = _(u'Middle Name'),
242        required = False,
243        )
244
245    lastname = schema.TextLine(
246        title = _(u'Last Name (Surname)'),
247        required = True,
248        )
249
250    sex = schema.Choice(
251        title = _(u'Sex'),
252        source = GenderSource(),
253        required = True,
254        )
255
256    reg_number = TextLineChoice(
257        title = _(u'Registration Number'),
258        required = True,
259        readonly = False,
260        source = contextual_reg_num_source,
261        )
262
263    matric_number = TextLineChoice(
264        title = _(u'Matriculation Number'),
265        required = False,
266        readonly = False,
267        source = contextual_mat_num_source,
268        )
269
270    adm_code = schema.TextLine(
271        title = _(u'PWD Activation Code'),
272        required = False,
273        readonly = False,
274        )
275
276    email = schema.ASCIILine(
277        title = _(u'Email'),
278        required = True,
279        constraint=validate_email,
280        )
281    phone = PhoneNumber(
282        title = _(u'Phone'),
283        description = u'',
284        required = False,
285        )
286
287    def setTempPassword(user, password):
288        """Set a temporary password (LDAP-compatible) SSHA encoded for
289        officers.
290        """
291
292    def getTempPassword():
293        """Check if a temporary password has been set and if it
294        is not expired. Return the temporary password if valid,
295        None otherwise. Unset the temporary password if expired.
296        """
297
298    def transfer(certificate, current_session,
299        current_level, current_verdict):
300        """ Creates a new studycourse and backups the old one.
301        """
302
303    def revert_transfer():
304        """ Revert previous transfer.
305        """
306
307class IUGStudentClearance(IKofaObject):
308    """Representation of undergraduate student clearance data.
309    """
310    officer_comment = schema.Text(
311        title = _(u"Officer's Comment"),
312        required = False,
313        )
314
315    clr_code = schema.TextLine(
316        title = _(u'CLR Activation Code'),
317        required = False,
318        readonly = False,
319        )
320
321    date_of_birth = FormattedDate(
322        title = _(u'Date of Birth'),
323        required = True,
324        show_year = True,
325        )
326
327    nationality = schema.Choice(
328        vocabulary = nats_vocab,
329        title = _(u'Nationality'),
330        required = False,
331        )
332
333class IPGStudentClearance(IUGStudentClearance):
334    """Representation of postgraduate student clearance data.
335    """
336    employer = schema.TextLine(
337        title = _(u'Employer'),
338        required = False,
339        readonly = False,
340        )
341
342class IStudentPersonal(IKofaObject):
343    """Representation of student personal data.
344    """
345    personal_updated = schema.Datetime(
346        title = _(u'Updated'),
347        required = False,
348        readonly = False,
349        )
350
351    perm_address = schema.Text(
352        title = _(u'Permanent Address'),
353        required = False,
354        )
355
356class IStudentTranscript(IKofaObject):
357    """Representation of student transcript data.
358    """
359
360    transcript_comment = schema.Text(
361        title = _(u'Comment'),
362        required = False,
363        )
364
365
366class IStudent(IStudentBase,IUGStudentClearance,IPGStudentClearance,
367    IStudentPersonal, IStudentTranscript):
368    """Representation of a student.
369    """
370
371class IStudentPersonalEdit(IStudentPersonal):
372    """Interface for editing personal data by students.
373    Here we can repeat the fields from IStudentPersonal and set the
374    `required` if necessary.
375    """
376
377    perm_address = schema.Text(
378        title = _(u'Permanent Address'),
379        required = True,
380        )
381
382class IStudentUpdateByRegNo(IStudent):
383    """Representation of a student. Skip regular reg_number validation.
384    """
385    reg_number = schema.TextLine(
386        title = _(u'Registration Number'),
387        required = False,
388        )
389
390class IStudentUpdateByMatricNo(IStudent):
391    """Representation of a student. Skip regular matric_number validation.
392    """
393    matric_number = schema.TextLine(
394        title = _(u'Matriculation Number'),
395        required = False,
396        )
397
398class IStudentRequestPW(IStudent):
399    """Representation of a student for first-time password request.
400    This interface is used when students use the requestpw page to
401    login for the the first time.
402    """
403    number = schema.TextLine(
404        title = _(u'Registr. or Matric. Number'),
405        required = True,
406        )
407
408    firstname = schema.TextLine(
409        title = _(u'First Name'),
410        required = True,
411        )
412
413    email = schema.ASCIILine(
414        title = _(u'Email Address'),
415        required = True,
416        constraint=validate_email,
417        )
418
419class IStudentStudyCourse(IKofaObject):
420    """Representation of student study course data.
421    """
422    next_session_allowed = Attribute('True if the student can proceed to next session')
423    is_postgrad = Attribute('True if student is postgraduate student')
424    is_current = Attribute('True if the study course is the current course of studies')
425    is_previous = Attribute('True if the study course is the previous course of studies')
426
427    certificate = schema.Choice(
428        title = _(u'Certificate'),
429        source = CertificateSource(),
430        required = False,
431        )
432
433    entry_mode = schema.Choice(
434        title = _(u'Entry Mode'),
435        source = StudyModeSource(),
436        required = True,
437        readonly = False,
438        )
439
440    entry_session = schema.Choice(
441        title = _(u'Entry Session'),
442        source = academic_sessions_vocab,
443        #default = datetime.now().year,
444        required = True,
445        readonly = False,
446        )
447
448    current_session = schema.Choice(
449        title = _(u'Current Session'),
450        source = academic_sessions_vocab,
451        required = True,
452        readonly = False,
453        )
454
455    current_level = schema.Choice(
456        title = _(u'Current Level'),
457        source = StudyLevelSource(),
458        required = False,
459        readonly = False,
460        )
461
462    current_verdict = schema.Choice(
463        title = _(u'Current Verdict'),
464        source = VerdictSource(),
465        default = '0',
466        required = False,
467        )
468
469    previous_verdict = schema.Choice(
470        title = _(u'Previous Verdict'),
471        source = VerdictSource(),
472        default = '0',
473        required = False,
474        )
475
476    def addStudentStudyLevel(cert, studylevel):
477        """Add a study level object.
478        """
479
480    def getTranscriptData():
481        """Get a sorted list of dicts with level and course ticket data.
482        This method is used for transcripts.
483        """
484
485class IStudentStudyCourseTransfer(IStudentStudyCourse):
486    """An interface used for student transfers.
487    """
488    certificate = schema.Choice(
489        title = _(u'Certificate'),
490        source = CertificateSource(),
491        required = True,
492        )
493
494    current_level = schema.Choice(
495        title = _(u'Current Level'),
496        source = StudyLevelSource(),
497        required = True,
498        readonly = False,
499        )
500
501    entry_session = schema.Choice(
502        title = _(u'Entry Session'),
503        source = academic_sessions_vocab,
504        #default = datetime.now().year,
505        required = False,
506        readonly = False,
507        )
508
509
510IStudentStudyCourseTransfer['certificate'].order = IStudentStudyCourse[
511    'certificate'].order
512IStudentStudyCourseTransfer['current_level'].order = IStudentStudyCourse[
513    'current_level'].order
514
515class IStudentStudyCourseTranscript(IKofaObject):
516    """An interface for student transcripts.
517    """
518    entry_mode = schema.Choice(
519        title = _(u'Entry Mode'),
520        source = StudyModeSource(),
521        required = True,
522        readonly = False,
523        )
524
525    entry_session = schema.Choice(
526        title = _(u'Entry Session'),
527        source = academic_sessions_vocab,
528        #default = datetime.now().year,
529        required = True,
530        readonly = False,
531        )
532
533class IStudentVerdictUpdate(IKofaObject):
534    """A interface for verdict imports.
535    """
536    current_verdict = schema.Choice(
537        title = _(u'Current Verdict'),
538        source = VerdictSource(),
539        required = True,
540        )
541
542    current_session = schema.Choice(
543        title = _(u'Current Session'),
544        source = academic_sessions_vocab,
545        required = True,
546        )
547
548    current_level = schema.Choice(
549        title = _(u'Current Level'),
550        source = StudyLevelSource(),
551        required = True,
552        )
553
554    bypass_validation = schema.Bool(
555        title = _(u'Bypass validation'),
556        required = False,
557        )
558
559    validated_by = schema.TextLine(
560        title = _(u'Validated by'),
561        required = False,
562        )
563
564class IStudentStudyLevel(IKofaObject):
565    """A representation of student study level data.
566    """
567    certcode = Attribute('The certificate code of the study course')
568    is_current_level = Attribute('True if level is current level of the student')
569    level_title = Attribute('Level title from source')
570    getSessionString = Attribute('Session title from source')
571    number_of_tickets = Attribute('Number of tickets contained in this level')
572    passed_params = Attribute('Information about passed and failed courses')
573    gpa_params_rectified = Attribute('Corrected sessional GPA parameters')
574    gpa_params = Attribute('GPA parameters for this level.')
575    cumulative_params = Attribute('Cumulative GPA and other cumulative parameters for this level')
576    course_registration_allowed = Attribute('True if registerin courses is allowed')
577
578    level = schema.Choice(
579        title = _(u'Level'),
580        source = StudyLevelSource(),
581        required = True,
582        readonly = False,
583        )
584
585    level_session = schema.Choice(
586        title = _(u'Session'),
587        source = academic_sessions_vocab,
588        required = True,
589        )
590
591    level_verdict = schema.Choice(
592        title = _(u'Verdict'),
593        source = VerdictSource(),
594        default = '0',
595        required = False,
596        )
597
598    validated_by = schema.TextLine(
599        title = _(u'Validated by'),
600        default = None,
601        required = False,
602        )
603
604    validation_date = schema.Datetime(
605        title = _(u'Validation Date'),
606        required = False,
607        readonly = False,
608        )
609
610    total_credits = schema.Int(
611        title = _(u'Total Credits'),
612        required = False,
613        readonly = True,
614        )
615
616    gpa = schema.Int(
617        title = _(u'Unrectified GPA'),
618        required = False,
619        readonly = True,
620        )
621
622    def addCourseTicket(ticket, course):
623        """Add a course ticket object.
624        """
625
626    def addCertCourseTickets(cert):
627        """Collect all certificate courses and create course
628        tickets automatically.
629        """
630
631class ICourseTicket(IKofaObject):
632    """A representation of course ticket data.
633    """
634    certcode = Attribute('Certificate code of the study course')
635    level_session = Attribute('Session of the study level the ticket has been added to')
636    level = Attribute('Level value of the study level the ticket has been added to')
637    grade = Attribute('Grade calculated from score')
638    weight = Attribute('Weight calculated from score')
639    removable_by_student = Attribute('True if student is allowed to remove the ticket')
640    editable_by_lecturer = Attribute('True if lecturer is allowed to edit the ticket')
641
642    code = Attribute('Code of the original course')
643
644    title = schema.TextLine(
645        title = _(u'Title'),
646        required = False,
647        )
648
649    fcode = schema.TextLine(
650        title = _(u'Faculty Code'),
651        required = False,
652        )
653
654    dcode = schema.TextLine(
655        title = _(u'Department Code'),
656        required = False,
657        )
658
659    semester = schema.Choice(
660        title = _(u'Semester/Term'),
661        source = SemesterSource(),
662        required = False,
663        )
664
665    passmark = schema.Int(
666        title = _(u'Passmark'),
667        required = False,
668        )
669
670    credits = schema.Int(
671        title = _(u'Credits'),
672        required = False,
673        )
674
675    mandatory = schema.Bool(
676        title = _(u'Required'),
677        default = False,
678        required = False,
679        )
680
681    score = schema.Int(
682        title = _(u'Score'),
683        default = None,
684        required = False,
685        missing_value = None,
686        )
687
688    carry_over = schema.Bool(
689        title = _(u'Carry-over Course'),
690        default = False,
691        required = False,
692        )
693
694    automatic = schema.Bool(
695        title = _(u'Automatical Creation'),
696        default = False,
697        required = False,
698        )
699
700class ICourseTicketAdd(IKofaObject):
701    """An interface for adding course tickets.
702    """
703    course = schema.Choice(
704        title = _(u'Course'),
705        source = CourseSource(),
706        readonly = False,
707        )
708
709class ICourseTicketImport(ICourseTicket):
710    """An interface for importing course results and nothing more.
711    """
712    score = schema.Int(
713        title = _(u'Score'),
714        required = False,
715        readonly = False,
716        )
717
718    level_session = schema.Choice(
719        title = _(u'Level Session'),
720        source = academic_sessions_vocab,
721        required = False,
722        readonly = False,
723        )
724
725class IStudentAccommodation(IKofaObject):
726    """A container for student accommodation objects.
727    """
728
729    def addBedTicket(bedticket):
730        """Add a bed ticket object.
731        """
732
733
734class IBedTicket(IKofaObject):
735    """A representation of accommodation booking data.
736    """
737    bed = Attribute('The bed object.')
738    booking_date = Attribute('Date of booking the bed')
739
740    display_coordinates = schema.TextLine(
741        title = _(u'Allocated Bed'),
742        required = False,
743        readonly = True,
744        )
745
746    bed_coordinates = schema.TextLine(
747        title = u'',
748        required = True,
749        readonly = False,
750        )
751
752    bed_type = schema.TextLine(
753        title = _(u'Requested Bed Type'),
754        required = True,
755        readonly = False,
756        )
757
758    booking_session = schema.Choice(
759        title = _(u'Session'),
760        source = academic_sessions_vocab,
761        required = True,
762        readonly = True,
763        )
764
765    booking_date = schema.Datetime(
766        title = _(u'Booking Date'),
767        required = False,
768        readonly = True,
769        )
770
771    booking_code = schema.TextLine(
772        title = _(u'Booking Activation Code'),
773        required = False,
774        readonly = True,
775        )
776
777    def getSessionString():
778        """Returns the title of academic_sessions_vocab term of the session
779        when the bed was booked.
780        """
781
782class IStudentPaymentsContainer(IPaymentsContainer):
783    """A container for student payment objects.
784    """
785
786class IStudentOnlinePayment(IOnlinePayment):
787    """A student payment via payment gateways.
788    """
789
790    p_current = schema.Bool(
791        title = _(u'Current Session Payment'),
792        default = True,
793        required = False,
794        )
795
796    p_level = schema.Int(
797        title = _(u'Payment Level'),
798        required = False,
799        )
800
801    def doAfterStudentPayment():
802        """Process student after payment was made.
803        """
804
805    def doAfterStudentPaymentApproval():
806        """Process student after payment was approved.
807        """
808
809    def approveStudentPayment():
810        """Approve payment and process student.
811        """
812
813IStudentOnlinePayment['p_level'].order = IStudentOnlinePayment[
814    'p_session'].order
815
816class IStudentPreviousPayment(IKofaObject):
817    """An interface for adding previous session payments.
818    """
819
820    p_category = schema.Choice(
821        title = _(u'Payment Category'),
822        default = u'schoolfee',
823        source = PreviousPaymentCategorySource(),
824        required = True,
825        )
826
827    p_session = schema.Choice(
828        title = _(u'Payment Session'),
829        source = academic_sessions_vocab,
830        required = True,
831        )
832
833    p_level = schema.Choice(
834        title = _(u'Payment Level'),
835        source = StudyLevelSource(),
836        required = True,
837        )
838
839class IStudentBalancePayment(IKofaObject):
840    """An interface for adding balances.
841    """
842
843    p_category = schema.Choice(
844        title = _(u'Payment Category'),
845        default = u'schoolfee',
846        required = True,
847        source = BalancePaymentCategorySource(),
848        )
849
850    balance_session = schema.Choice(
851        title = _(u'Payment Session'),
852        source = academic_sessions_vocab,
853        required = True,
854        )
855
856    balance_level = schema.Choice(
857        title = _(u'Payment Level'),
858        source = StudyLevelSource(),
859        required = True,
860        )
861
862    balance_amount = schema.Float(
863        title = _(u'Balance Amount'),
864        default = None,
865        required = True,
866        readonly = False,
867        description = _(
868            u'Balance in Naira '),
869        )
870
871class ICSVStudentExporter(ICSVExporter):
872    """A regular ICSVExporter that additionally supports exporting
873      data from a given student object.
874    """
875    def get_filtered(site, **kw):
876        """Get a filtered set of students.
877        """
878
879    def get_selected(site, selected):
880        """Get set of selected students.
881        """
882
883    def export_student(student, filepath=None):
884        """Export data for a given student.
885        """
886
887    def export_filtered(site, filepath=None, **kw):
888        """Export data for filtered set of students.
889        """
890
891    def export_selected(site, filepath=None, **kw):
892        """Export data for selected set of students.
893        """
Note: See TracBrowser for help on using the repository browser.