source: main/kofacustom.iuokada/trunk/src/kofacustom/iuokada/applicants/interfaces.py @ 16410

Last change on this file since 16410 was 16287, checked in by Henrik Bettermann, 4 years ago

Add fields (#81)

  • Property svn:keywords set to Id
File size: 23.5 KB
Line 
1# -*- coding: utf-8 -*-
2## $Id: interfaces.py 16287 2020-10-28 20:34:03Z henrik $
3##
4## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
5## This program is free software; you can redistribute it and/or modify
6## it under the terms of the GNU General Public License as published by
7## the Free Software Foundation; either version 2 of the License, or
8## (at your option) any later version.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15## You should have received a copy of the GNU General Public License
16## along with this program; if not, write to the Free Software
17## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18##
19"""Customized interfaces of the university application package.
20"""
21
22from zope import schema
23from zope.component import getUtility
24from zope.catalog.interfaces import ICatalog
25from zc.sourcefactory.basic import BasicSourceFactory
26from waeup.kofa.applicants.interfaces import (
27    IApplicantBaseData,
28    AppCatCertificateSource, CertificateSource)
29from waeup.kofa.schoolgrades import ResultEntryField
30from waeup.kofa.university.vocabularies import StudyModeSource
31from waeup.kofa.interfaces import (
32    SimpleKofaVocabulary, academic_sessions_vocab, validate_email,
33    IKofaObject)
34from waeup.kofa.schema import FormattedDate, TextLineChoice, PhoneNumber
35from waeup.kofa.students.vocabularies import (
36    nats_vocab, GenderSource, StudyLevelSource)
37from waeup.kofa.applicants.interfaces import (
38    contextual_reg_num_source, IApplicantBaseData, IApplicantRefereeReport,
39    contextual_email_source, IApplicantRegisterUpdate)
40from waeup.kofa.refereeentries import RefereeEntryField
41from kofacustom.nigeria.applicants.interfaces import (
42    LGASource, high_qual, high_grade, exam_types, DisabilitiesSource,
43    programme_types_vocab, jambsubjects, validate_jamb_reg_number,
44    INigeriaUGApplicant, INigeriaPGApplicant,
45    INigeriaApplicantOnlinePayment,
46    INigeriaUGApplicantEdit, INigeriaPGApplicantEdit,
47    INigeriaApplicantUpdateByRegNo,
48    IPUTMEApplicantEdit,
49    )
50from kofacustom.iuokada.interfaces import MessageFactory as _
51from kofacustom.iuokada.payments.interfaces import ICustomOnlinePayment
52
53sponsors_vocab = SimpleKofaVocabulary(
54    (_('Bauchi Government'), 'bauchi'),
55    (_('Company'), 'company'),
56    (_('Federal Government of Nigeria'), 'federalgov'),
57    #(_('Dangote Group'), 'dangote'),
58    (_('Kano Government'), 'kano'),
59    (_('Parent/Guardian'), 'parent'),
60    (_('Rosula Organization'), 'rosula'),
61    (_('Self'), 'self'),
62    #(_('Social Impact Project'), 'social'),
63    )
64
65heard_about_types_vocab = SimpleKofaVocabulary(
66    (_('Facebook/Twitter/Other Social Media'), 'socmedia'),
67    (_('From a Friend'), 'friend'),
68    (_('Newspaper Advertisement'), 'newspaper'),
69    (_('Radio Advertisement'), 'radio'),
70    (_('Television Advertisement'), 'television'),
71    (_('SMS'), 'sms'),
72    )
73
74subtype_vocab = SimpleKofaVocabulary(
75    (_('UTME'), 'utme'),
76    (_('Direct Entry'), 'de'),
77    (_('Inter Uni Transfer'), 'transfer'),
78    )
79
80rating_vocab = SimpleKofaVocabulary(
81    (_('Excellent'), 'e'),
82    (_('Very good'), 'vg'),
83    (_('Good'), 'g'),
84    (_('Slightly above average'), 'saa'),
85    (_('Average'), 'a'),
86    (_('Below average'), 'ba'),
87    (_('Unable to assess'), 'unable'),
88    )
89
90
91overallpromise_vocab = SimpleKofaVocabulary(
92    (_('Very good (highest 10%)'), 'vg'),
93    (_('Above average (next 15%)'), 'aa'),
94    (_('Average (middle 20%)'), 'a'),
95    (_('Below average (middle 40%)'), 'ba'),
96    )
97
98DESTINATION_COST = {
99    'local': ('Local (Nigeria)', 15000.0, 1),
100    'overseas': ('Overseas', 25000.0, 2),
101    }
102
103class DestinationCostSource(BasicSourceFactory):
104    """A source that delivers continents and shipment costs.
105    """
106    def getValues(self):
107        sorted_items = sorted(DESTINATION_COST.items(),
108                              key=lambda element: element[1][2])
109        return [item[0] for item in sorted_items]
110
111    def getTitle(self, value):
112        return u"%s (₦ %s)" % (
113            DESTINATION_COST[value][0],
114            DESTINATION_COST[value][1])
115
116class TranscriptCertificateSource(CertificateSource):
117    """Include Department and Faculty in Title.
118    """
119    def getValues(self, context):
120        catalog = getUtility(ICatalog, name='certificates_catalog')
121        resultset = catalog.searchResults(code=(None, None))
122        resultlist = sorted(resultset, key=lambda
123            value: value.__parent__.__parent__.__parent__.code +
124            value.__parent__.__parent__.code +
125            value.code)
126        return resultlist
127
128    def getTitle(self, context, value):
129        """
130        """
131        try: title = "%s / %s / %s (%s)" % (
132            value.__parent__.__parent__.__parent__.title,
133            value.__parent__.__parent__.title,
134            value.title, value.code)
135        except AttributeError:
136            title = "NA / %s (%s)" % (value.title, value.code)
137        return title
138
139class ICustomUGApplicant(IApplicantBaseData):
140    """An undergraduate applicant.
141
142    This interface defines the least common multiple of all fields
143    in ug application forms. In customized forms, fields can be excluded by
144    adding them to the UG_OMIT* tuples.
145    """
146
147    email = TextLineChoice(
148        title = _(u'Email Address'),
149        required = True,
150        constraint=validate_email,
151        source = contextual_email_source,
152        )
153
154    subtype = schema.Choice(
155        title = _(u'Application Subtype'),
156        vocabulary = subtype_vocab,
157        required = False,
158        )
159    disabilities = schema.Choice(
160        title = _(u'Disability'),
161        source = DisabilitiesSource(),
162        required = False,
163        )
164    nationality = schema.Choice(
165        source = nats_vocab,
166        title = _(u'Nationality'),
167        required = False,
168        )
169    lga = schema.Choice(
170        source = LGASource(),
171        title = _(u'State/LGA (Nigerians only)'),
172        required = False,
173        )
174    sponsor = schema.Choice(
175        title = _(u'Sponsor'),
176        vocabulary = sponsors_vocab,
177        required = False,
178        )
179    heard_about = schema.Choice(
180        title = _(u'How did you hear about IUO?'),
181        vocabulary = heard_about_types_vocab,
182        required = False,
183        )
184    perm_address = schema.Text(
185        title = _(u'Residential Address'),
186        required = True,
187        )
188    parents_name = schema.TextLine(
189        title = _(u'Full Name'),
190        required = False,
191        readonly = False,
192        )
193    parents_email = schema.ASCIILine(
194        title = _(u'Email Address'),
195        required = False,
196        constraint=validate_email,
197        )
198    parents_phone = PhoneNumber(
199        title = _(u'Phone'),
200        required = False,
201        )
202    course1 = schema.Choice(
203        title = _(u'1st Choice Course of Study'),
204        source = AppCatCertificateSource(),
205        required = True,
206        )
207    course2 = schema.Choice(
208        title = _(u'2nd Choice Course of Study'),
209        source = AppCatCertificateSource(),
210        required = False,
211        )
212    programme_type = schema.Choice(
213        title = _(u'Programme Type'),
214        vocabulary = programme_types_vocab,
215        required = False,
216        )
217    fst_sit_fname = schema.TextLine(
218        title = _(u'Full Name'),
219        required = False,
220        readonly = False,
221        )
222    fst_sit_no = schema.TextLine(
223        title = _(u'Exam Number'),
224        required = False,
225        readonly = False,
226        )
227    fst_sit_date = FormattedDate(
228        title = _(u'Exam Date'),
229        required = False,
230        readonly = False,
231        show_year = True,
232        )
233    fst_sit_type = schema.Choice(
234        title = _(u'Exam Type'),
235        required = False,
236        readonly = False,
237        vocabulary = exam_types,
238        )
239    fst_sit_results = schema.List(
240        title = _(u'Exam Results'),
241        value_type = ResultEntryField(),
242        required = False,
243        readonly = False,
244        defaultFactory=list,
245        )
246    scd_sit_fname = schema.TextLine(
247        title = _(u'Full Name'),
248        required = False,
249        readonly = False,
250        )
251    scd_sit_no = schema.TextLine(
252        title = _(u'Exam Number'),
253        required = False,
254        readonly = False,
255        )
256    scd_sit_date = FormattedDate(
257        title = _(u'Exam Date'),
258        required = False,
259        readonly = False,
260        show_year = True,
261        )
262    scd_sit_type = schema.Choice(
263        title = _(u'Exam Type'),
264        required = False,
265        readonly = False,
266        vocabulary = exam_types,
267        )
268    scd_sit_results = schema.List(
269        title = _(u'Exam Results'),
270        value_type = ResultEntryField(),
271        required = False,
272        readonly = False,
273        defaultFactory=list,
274        )
275    hq_type = schema.Choice(
276        title = _(u'Qualification Obtained'),
277        required = False,
278        readonly = False,
279        vocabulary = high_qual,
280        )
281    hq_matric_no = schema.TextLine(
282        title = _(u'Former Matric Number'),
283        required = False,
284        readonly = False,
285        )
286    hq_degree = schema.Choice(
287        title = _(u'Class of Degree'),
288        required = False,
289        readonly = False,
290        vocabulary = high_grade,
291        )
292    hq_school = schema.TextLine(
293        title = _(u'Institution Attended'),
294        required = False,
295        readonly = False,
296        )
297    hq_session = schema.TextLine(
298        title = _(u'Years Attended'),
299        required = False,
300        readonly = False,
301        )
302    hq_disc = schema.TextLine(
303        title = _(u'Discipline'),
304        required = False,
305        readonly = False,
306        )
307    #jamb_subjects = schema.Text(
308    #    title = _(u'Subjects and Scores'),
309    #    required = False,
310    #    )
311    jamb_subjects_list = schema.List(
312        title = _(u'JAMB Subjects'),
313        required = False,
314        defaultFactory=list,
315        value_type = schema.Choice(
316            vocabulary = jambsubjects
317            #source = JAMBSubjectSource(),
318            ),
319        )
320    jamb_score = schema.Int(
321        title = _(u'Total JAMB Score'),
322        required = False,
323        )
324    #jamb_age = schema.Int(
325    #    title = _(u'Age (provided by JAMB)'),
326    #    required = False,
327    #    )
328    jamb_reg_number = schema.TextLine(
329        title = _(u'JAMB Registration Number'),
330        required = False,
331        constraint=validate_jamb_reg_number,
332        )
333    notice = schema.Text(
334        title = _(u'Notice'),
335        required = False,
336        )
337    screening_venue = schema.TextLine(
338        title = _(u'Screening Venue'),
339        required = False,
340        )
341    screening_date = schema.TextLine(
342        title = _(u'Screening Date'),
343        required = False,
344        )
345    screening_score = schema.Int(
346        title = _(u'Screening Score (%)'),
347        required = False,
348        )
349    aggregate = schema.Int(
350        title = _(u'Aggregate Score (%)'),
351        description = _(u'(average of relative JAMB and PUTME scores)'),
352        required = False,
353        )
354    result_uploaded = schema.Bool(
355        title = _(u'Result uploaded'),
356        default = False,
357        required = False,
358        )
359    student_id = schema.TextLine(
360        title = _(u'Student Id'),
361        required = False,
362        readonly = False,
363        )
364    course_admitted = schema.Choice(
365        title = _(u'Admitted Course of Study'),
366        source = CertificateSource(),
367        required = False,
368        )
369    locked = schema.Bool(
370        title = _(u'Form locked'),
371        default = False,
372        required = False,
373        )
374
375ICustomUGApplicant[
376    'subtype'].order =  ICustomUGApplicant['lga'].order
377ICustomUGApplicant[
378    'locked'].order =  ICustomUGApplicant['suspended'].order
379ICustomUGApplicant[
380    'result_uploaded'].order =  ICustomUGApplicant['suspended'].order
381
382class ICustomPGApplicant(INigeriaPGApplicant):
383    """A postgraduate applicant.
384
385    This interface defines the least common multiple of all fields
386    in pg application forms. In customized forms, fields can be excluded by
387    adding them to the PG_OMIT* tuples.
388    """
389
390    email = TextLineChoice(
391        title = _(u'Email Address'),
392        required = True,
393        constraint=validate_email,
394        source = contextual_email_source,
395        )
396
397    perm_address = schema.Text(
398        title = _(u'Residential Address'),
399        required = True,
400        )
401
402    sponsor = schema.Choice(
403        title = _(u'Sponsor'),
404        vocabulary = sponsors_vocab,
405        required = False,
406        )
407
408    heard_about = schema.Choice(
409        title = _(u'How did you hear about IU?'),
410        vocabulary = heard_about_types_vocab,
411        required = False,
412        )
413
414    nysc_number = schema.Int(
415        title = _(u'Nysc Number'),
416        required = False,
417        readonly = False,
418        )
419
420    referees = schema.List(
421        title = _(u'Referees'),
422        value_type = RefereeEntryField(),
423        required = False,
424        defaultFactory=list,
425        )
426
427ICustomPGApplicant[
428    'referees'].order =  INigeriaPGApplicant['emp2_reason'].order
429ICustomPGApplicant[
430    'sponsor'].order =  ICustomPGApplicant['lga'].order
431ICustomPGApplicant[
432    'heard_about'].order =  ICustomPGApplicant['lga'].order
433ICustomPGApplicant[
434    'sponsor'].order =  ICustomPGApplicant['lga'].order
435ICustomPGApplicant[
436    'lga'].order =  ICustomPGApplicant['nationality'].order
437ICustomPGApplicant[
438    'nysc_number'].order =  ICustomPGApplicant['nysc_year'].order
439ICustomPGApplicant[
440    'perm_address'].order =  ICustomPGApplicant['lga'].order
441ICustomPGApplicant[
442    'email'].order =  ICustomPGApplicant['lga'].order
443
444class ITranscriptApplicant(IKofaObject):
445    """A transcript applicant.
446    """
447
448    suspended = schema.Bool(
449        title = _(u'Account suspended'),
450        default = False,
451        required = False,
452        )
453
454    locked = schema.Bool(
455        title = _(u'Form locked'),
456        default = False,
457        required = False,
458        )
459
460    applicant_id = schema.TextLine(
461        title = _(u'Transcript Application Id'),
462        required = False,
463        readonly = False,
464        )
465
466    matric_number = schema.TextLine(
467        title = _(u'Matriculation Number'),
468        readonly = False,
469        required = True,
470        )
471
472    firstname = schema.TextLine(
473        title = _(u'First Name'),
474        required = True,
475        )
476
477    middlename = schema.TextLine(
478        title = _(u'Middle Name'),
479        required = False,
480        )
481
482    lastname = schema.TextLine(
483        title = _(u'Last Name (Surname)'),
484        required = True,
485        )
486
487    #date_of_birth = FormattedDate(
488    #    title = _(u'Date of Birth'),
489    #    required = False,
490    #    #date_format = u'%d/%m/%Y', # Use grok-instance-wide default
491    #    show_year = True,
492    #    )
493
494    sex = schema.Choice(
495        title = _(u'Gender'),
496        source = GenderSource(),
497        required = True,
498        )
499
500    #nationality = schema.Choice(
501    #    vocabulary = nats_vocab,
502    #    title = _(u'Nationality'),
503    #    required = False,
504    #    )
505
506    email = schema.ASCIILine(
507        title = _(u'Email Address'),
508        required = True,
509        constraint=validate_email,
510        )
511
512    #phone = PhoneNumber(
513    #    title = _(u'Phone'),
514    #    description = u'',
515    #    required = False,
516    #    )
517
518    #perm_address = schema.Text(
519    #    title = _(u'Current Local Address'),
520    #    required = False,
521    #    readonly = False,
522    #    )
523
524    dispatch_address = schema.Text(
525        title = _(u'Dispatch Addresses'),
526        description = u'Addresses to which transcripts should be posted. '
527                       'Addresses must involve same courier charges.',
528        required = False,
529        readonly = False,
530        )
531
532    charge = schema.Choice(
533        title = _(u'Courier Charge'),
534        source = DestinationCostSource(),
535        required = True,
536        )
537
538    #entry_mode = schema.Choice(
539    #    title = _(u'Entry Mode'),
540    #    source = StudyModeSource(),
541    #    required = False,
542    #    readonly = False,
543    #    )
544
545    #entry_session = schema.Choice(
546    #    title = _(u'Entry Session'),
547    #    source = academic_sessions_vocab,
548    #    required = False,
549    #    readonly = False,
550    #    )
551
552    end_session = schema.Choice(
553        title = _(u'Academic Session of Graduation'),
554        source = academic_sessions_vocab,
555        required = False,
556        readonly = False,
557        )
558
559    course_studied = schema.Choice(
560        title = _(u'Course of Study'),
561        source = TranscriptCertificateSource(),
562        description = u'Faculty / Department / Course',
563        required = False,
564        readonly = False,
565        )
566
567    #course_changed = schema.Choice(
568    #    title = _(u'Change of Study Course'),
569    #    description = u'If yes, select previous course of study.',
570    #    source = CertificateSource(),
571    #    readonly = False,
572    #    required = False,
573    #    )
574
575    #change_level = schema.Choice(
576    #    title = _(u'Change Level'),
577    #    description = u'If yes, select level at which you changed course of study.',
578    #    source = StudyLevelSource(),
579    #    required = False,
580    #    readonly = False,
581    #    )
582
583    no_copies = schema.Choice(
584        title = _(u'Number of Copies'),
585        description = u'Must correspond with the number of dispatch addresses above.',
586        values=[1, 2, 3, 4],
587        required = False,
588        readonly = False,
589        default = 1,
590        )
591
592class ICustomApplicant(ICustomUGApplicant, ICustomPGApplicant,
593    ITranscriptApplicant):
594    """An interface for both types of applicants.
595
596    Attention: The ICustomPGApplicant field seetings will be overwritten
597    by ICustomPGApplicant field settings. If a field is defined
598    in both interfaces zope.schema validates only against the
599    constraints in ICustomUGApplicant. This does not affect the forms
600    since they are build on either ICustomUGApplicant or ICustomPGApplicant.
601    """
602
603    def writeLogMessage(view, comment):
604        """Adds an INFO message to the log file
605        """
606
607    def createStudent():
608        """Create a student object from applicant data
609        and copy applicant object.
610        """
611
612class ICustomUGApplicantEdit(ICustomUGApplicant):
613    """An undergraduate applicant interface for edit forms.
614
615    Here we can repeat the fields from base data and set the
616    `required` and `readonly` attributes to True to further restrict
617    the data access. Or we can allow only certain certificates to be
618    selected by choosing the appropriate source.
619
620    We cannot omit fields here. This has to be done in the
621    respective form page.
622    """
623
624    phone = PhoneNumber(
625        title = _(u'Phone'),
626        description = u'',
627        required = True,
628        )
629    email = TextLineChoice(
630        title = _(u'Email Address'),
631        required = False,
632        constraint=validate_email,
633        source = contextual_email_source,
634        )
635    date_of_birth = FormattedDate(
636        title = _(u'Date of Birth'),
637        required = True,
638        show_year = True,
639        )
640
641ICustomUGApplicantEdit[
642    'date_of_birth'].order = ICustomUGApplicant['date_of_birth'].order
643ICustomUGApplicantEdit[
644    'email'].order = ICustomUGApplicant['email'].order
645ICustomUGApplicantEdit[
646    'phone'].order =  ICustomUGApplicantEdit['email'].order
647
648class ICustomPGApplicantEdit(ICustomPGApplicant):
649    """A postgraduate applicant interface for editing.
650
651    Here we can repeat the fields from base data and set the
652    `required` and `readonly` attributes to True to further restrict
653    the data access. Or we can allow only certain certificates to be
654    selected by choosing the appropriate source.
655
656    We cannot omit fields here. This has to be done in the
657    respective form page.
658    """
659
660    phone = PhoneNumber(
661        title = _(u'Phone'),
662        description = u'',
663        required = True,
664        )
665    email = TextLineChoice(
666        title = _(u'Email Address'),
667        required = False,
668        constraint=validate_email,
669        source = contextual_email_source,
670        )
671    date_of_birth = FormattedDate(
672        title = _(u'Date of Birth'),
673        required = True,
674        show_year = True,
675        )
676
677ICustomPGApplicantEdit[
678    'date_of_birth'].order = ICustomPGApplicant['date_of_birth'].order
679ICustomPGApplicantEdit[
680    'email'].order = ICustomPGApplicant['email'].order
681ICustomPGApplicantEdit[
682    'phone'].order =  ICustomPGApplicantEdit['email'].order
683
684class ICustomApplicantRegisterUpdate(IApplicantRegisterUpdate):
685    """This is a representation of an applicant for first-time registration.
686    This interface is used when applicants use the registration page to
687    update their records.
688    """
689
690    email = TextLineChoice(
691        title = _(u'Email Address'),
692        required = False,
693        constraint=validate_email,
694        source = contextual_email_source,
695        )
696
697class ICustomApplicantOnlinePayment(INigeriaApplicantOnlinePayment):
698    """An applicant payment via payment gateways.
699
700    """
701
702class IPUTMEApplicantEdit(IPUTMEApplicantEdit):
703    """An undergraduate applicant interface for editing.
704
705    Here we can repeat the fields from base data and set the
706    `required` and `readonly` attributes to True to further restrict
707    the data access. Or we can allow only certain certificates to be
708    selected by choosing the appropriate source.
709
710    We cannot omit fields here. This has to be done in the
711    respective form page.
712    """
713
714class ICustomApplicantUpdateByRegNo(INigeriaApplicantUpdateByRegNo):
715    """Representation of an applicant.
716
717    Skip regular reg_number validation if reg_number is used for finding
718    the applicant object.
719    """
720
721class ICustomApplicantRefereeReport(IApplicantRefereeReport):
722    """A referee report.
723    """
724
725    duration = schema.Text(
726        title = _(u'How long and in what capacity have you known the candidate?'),
727        required = False,
728        )
729
730    itellectual = schema.Choice(
731        title = _(u'Intellectual Capacity'),
732        required = False,
733        readonly = False,
734        vocabulary = rating_vocab,
735        )
736
737    persistent = schema.Choice(
738        title = _(u'Capacity for Persistent and Independent Academic Study'),
739        required = False,
740        readonly = False,
741        vocabulary = rating_vocab,
742        )
743
744    imaginative = schema.Choice(
745        title = _(u'Ability for Imaginative Thought'),
746        required = False,
747        readonly = False,
748        vocabulary = rating_vocab,
749        )
750
751    productive = schema.Choice(
752        title = _(u'Promise of Productive Scholarship'),
753        required = False,
754        readonly = False,
755        vocabulary = rating_vocab,
756        )
757
758    previous = schema.Choice(
759        title = _(u'Quality of Previous Work'),
760        required = False,
761        readonly = False,
762        vocabulary = rating_vocab,
763        )
764
765    expression = schema.Choice(
766        title = _(u'Oral and Written Expression in English'),
767        required = False,
768        readonly = False,
769        vocabulary = rating_vocab,
770        )
771
772    personality = schema.Text(
773        title = _(u'Please comment on the candidate\'s personality '
774            'with particular reference to his/her moral character, emotional '
775            'and physical stabilty'),
776        required = False,
777        )
778
779    promise = schema.Choice(
780        title = _(u'Candidate\'s overall promise'),
781        required = False,
782        readonly = False,
783        vocabulary = overallpromise_vocab,
784        )
785
786    report = schema.Text(
787        title = _(u'Any other relevant information which would help '
788            'in determining the candidate\'s suitability?'),
789        required = False,
790        )
791
792    objection = schema.Text(
793        title = _(u'Have you any objection to the contents of this '
794            'evaluation being disclosed to any award-given body if '
795            'the need arises?'),
796        required = False,
797        )
Note: See TracBrowser for help on using the repository browser.