source: main/waeup.kofa/branches/uli-fix-zc-async/src/waeup/kofa/interfaces.py @ 17688

Last change on this file since 17688 was 17214, checked in by Henrik Bettermann, 2 years ago

Sort correctly.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 46.6 KB
Line 
1## $Id: interfaces.py 17214 2022-12-09 10:20:52Z 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##
18import os
19import re
20import codecs
21import zc.async.interfaces
22import zope.i18nmessageid
23from datetime import datetime
24from hurry.file.interfaces import IFileRetrieval
25from hurry.workflow.interfaces import IWorkflowInfo
26from zc.sourcefactory.basic import BasicSourceFactory
27from zope import schema
28from zope.pluggableauth.interfaces import IPrincipalInfo
29from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
30from zope.component import getUtility
31from zope.component.interfaces import IObjectEvent
32from zope.configuration.fields import Path
33from zope.container.interfaces import INameChooser, IContainer
34from zope.interface import Interface, Attribute
35from zope.schema.interfaces import IObject
36from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
37from waeup.kofa.schema import PhoneNumber
38from waeup.kofa.sourcefactory import SmartBasicContextualSourceFactory
39
40_ = MessageFactory = zope.i18nmessageid.MessageFactory('waeup.kofa')
41
42DELETION_MARKER = 'XXX'
43IGNORE_MARKER = '<IGNORE>'
44WAEUP_KEY = 'waeup.kofa'
45VIRT_JOBS_CONTAINER_NAME = 'jobs'
46DOCLINK = 'http://kofa-doc.waeup.org/userdocs'
47
48CREATED = 'created'
49ADMITTED = 'admitted'
50CLEARANCE = 'clearance started'
51REQUESTED = 'clearance requested'
52CLEARED = 'cleared'
53PAID = 'school fee paid'
54RETURNING = 'returning'
55REGISTERED = 'courses registered'
56VALIDATED = 'courses validated'
57GRADUATED = 'graduated'
58TRANSREQ = 'transcript requested'
59TRANSVAL = 'transcript validated'
60TRANSREL = 'transcript released'
61
62
63#: A dict giving job status as tuple (<STRING>, <TRANSLATED_STRING>),
64#: the latter for UI purposes.
65JOB_STATUS_MAP = {
66    zc.async.interfaces.NEW: ('new', _('new')),
67    zc.async.interfaces.COMPLETED: ('completed', _('completed')),
68    zc.async.interfaces.PENDING: ('pending', _('pending')),
69    zc.async.interfaces.ACTIVE: ('active', _('active')),
70    zc.async.interfaces.ASSIGNED: ('assigned', _('assigned')),
71    zc.async.interfaces.CALLBACKS: ('callbacks', _('callbacks')),
72    }
73
74#default_rest_frontpage = u'' + codecs.open(os.path.join(
75#        os.path.dirname(__file__), 'frontpage.rst'),
76#        encoding='utf-8', mode='rb').read()
77
78default_html_frontpage = u'' + codecs.open(os.path.join(
79        os.path.dirname(__file__), 'frontpage.html'),
80        encoding='utf-8', mode='rb').read()
81
82def SimpleKofaVocabulary(*terms):
83    """A well-buildt vocabulary provides terms with a value, token and
84       title for each term
85    """
86    return SimpleVocabulary([
87            SimpleTerm(value, value, title) for title, value in terms])
88
89def academic_sessions():
90    curr_year = datetime.now().year
91    year_range = range(1970, curr_year + 2)
92    return [('%s/%s' % (year,year+1), year) for year in year_range]
93
94academic_sessions_vocab = SimpleKofaVocabulary(*academic_sessions())
95
96registration_states_vocab = SimpleKofaVocabulary(
97    (_('created'), CREATED),
98    (_('admitted'), ADMITTED),
99    (_('clearance started'), CLEARANCE),
100    (_('clearance requested'), REQUESTED),
101    (_('cleared'), CLEARED),
102    (_('school fee paid'), PAID),
103    (_('courses registered'), REGISTERED),
104    (_('courses validated'), VALIDATED),
105    (_('returning'), RETURNING),
106    (_('graduated'), GRADUATED),
107    (_('transcript requested'), TRANSREQ),
108    (_('transcript validated'), TRANSVAL),
109    (_('transcript released'), TRANSREL),
110    )
111
112class ContextualDictSourceFactoryBase(SmartBasicContextualSourceFactory):
113    """A base for contextual sources based on KofaUtils dicts.
114
115    To create a real source, you have to set the `DICT_NAME` attribute
116    which should be the name of a dictionary in KofaUtils.
117    """
118    def getValues(self, context):
119        utils = getUtility(IKofaUtils)
120        sorted_items = sorted(getattr(utils, self.DICT_NAME).items(),
121                              key=lambda item: item[1])
122        return [item[0] for item in sorted_items]
123
124    def getToken(self, context, value):
125        return str(value)
126
127    def getTitle(self, context, value):
128        utils = getUtility(IKofaUtils)
129        return getattr(utils, self.DICT_NAME)[value]
130
131class SubjectSource(BasicSourceFactory):
132    """A source for school subjects used in exam documentation.
133    """
134    def getValues(self):
135        subjects_dict = getUtility(IKofaUtils).EXAM_SUBJECTS_DICT
136        return sorted(subjects_dict.keys())
137
138    def getTitle(self, value):
139        subjects_dict = getUtility(IKofaUtils).EXAM_SUBJECTS_DICT
140        return "%s:" % subjects_dict[value]
141
142class GradeSource(BasicSourceFactory):
143    """A source for exam grades.
144    """
145    def getValues(self):
146        for entry in getUtility(IKofaUtils).EXAM_GRADES:
147            yield entry[0]
148
149    def getTitle(self, value):
150        return dict(getUtility(IKofaUtils).EXAM_GRADES)[value]
151
152class DisablePaymentGroupSource(ContextualDictSourceFactoryBase):
153    """A source for filtering groups of students
154    """
155    #: name of dict to deliver from kofa utils.
156    DICT_NAME = 'DISABLE_PAYMENT_GROUP_DICT'
157
158class MonthSource(BasicSourceFactory):
159    """A months source delivers all months of the year.
160    """
161    def getValues(self):
162        months_dict = getUtility(IKofaUtils).MONTHS_DICT
163        return sorted(months_dict.keys(), key=lambda x: int(x))
164
165    def getToken(self, value):
166        return value
167
168    def getTitle(self, value):
169        months_dict = getUtility(IKofaUtils).MONTHS_DICT
170        return months_dict[value]
171
172# Define a validation method for email addresses
173class NotAnEmailAddress(schema.ValidationError):
174    __doc__ = u"Invalid email address"
175
176#: Regular expression to check email-address formats. As these can
177#: become rather complex (nearly everything is allowed by RFCs), we only
178#: forbid whitespaces, commas and dots following onto each other.
179check_email = re.compile(
180    r"^[^@\s,]+@[^@\.\s,]+(\.[^@\.\s,]+)*$").match
181
182def validate_email(value):
183    if not check_email(value):
184        raise NotAnEmailAddress(value)
185    return True
186
187# Define a validation method for ids
188class NotIdValue(schema.ValidationError):
189    __doc__ = u"Invalid id"
190
191#: Regular expressions to check id formats.
192check_id = re.compile(r"^[a-zA-Z0-9_-]{2,11}$").match
193
194def validate_id(value):
195    if not check_id(value):
196        raise NotIdValue(value)
197    return True
198
199# Define a validation method for HTML fields
200class NotHTMLValue(schema.ValidationError):
201    __doc__ = u"Style or script elements not allowed"
202
203def validate_html(value):
204    if '<style' in value or '<script' in value:
205        raise NotHTMLValue(value)
206    return True
207
208# Define a validation method for international phone numbers
209class InvalidPhoneNumber(schema.ValidationError):
210    __doc__ = u"Invalid phone number"
211
212# represent format +NNN-NNNN-NNNN
213RE_INT_PHONE = re.compile(r"^\+?\d+\-\d+\-[\d\-]+$")
214
215def validate_phone(value):
216    if not RE_INT_PHONE.match(value):
217        raise InvalidPhoneNumber(value)
218    return True
219
220class FatalCSVError(Exception):
221    """Some row could not be processed.
222    """
223    pass
224
225class DuplicationError(Exception):
226    """An exception that can be raised when duplicates are found.
227
228    When raising :exc:`DuplicationError` you can, beside the usual
229    message, specify a list of objects which are duplicates. These
230    values can be used by catching code to print something helpful or
231    similar.
232    """
233    def __init__(self, msg, entries=[]):
234        self.msg = msg
235        self.entries = entries
236
237    def __str__(self):
238        return '%r' % self.msg
239
240class RoleSource(BasicSourceFactory):
241    """A source for site roles.
242    """
243    def getValues(self):
244        # late import: in interfaces we should not import local modules
245        from waeup.kofa.permissions import get_waeup_role_names
246        return get_waeup_role_names()
247
248    def getTitle(self, value):
249        # late import: in interfaces we should not import local modules
250        from waeup.kofa.permissions import get_all_roles
251        roles = dict(get_all_roles())
252        if value in roles.keys():
253            title = roles[value].title
254            if '.' in title:
255                title = title.split('.', 2)[1]
256        return title
257
258class CaptchaSource(BasicSourceFactory):
259    """A source for captchas.
260    """
261    def getValues(self):
262        captchas = ['No captcha', 'Testing captcha', 'ReCaptcha']
263        try:
264            # we have to 'try' because IConfiguration can only handle
265            # interfaces from w.k.interface.
266            from waeup.kofa.browser.interfaces import ICaptchaManager
267        except:
268            return captchas
269        return sorted(getUtility(ICaptchaManager).getAvailCaptchas().keys())
270
271    def getTitle(self, value):
272        return value
273
274class IResultEntry(Interface):
275    """A school grade entry.
276    """
277
278    subject = schema.Choice(
279        title = _(u'Subject'),
280        source = SubjectSource(),
281        )
282
283    grade = schema.Choice(
284        title = _(u'Grade'),
285        source = GradeSource(),
286        )
287
288class IResultEntryField(IObject):
289    """A zope.schema-like field for usage in interfaces.
290
291    Marker interface to distuingish result entries from ordinary
292    object fields. Needed for registration of widgets.
293    """
294
295class IRefereeEntry(Interface):
296    """A referee entry.
297    """
298    email_sent = Attribute('True if email has been sent')
299
300    name = schema.TextLine(
301        title = _(u'Name'),
302        required = True,
303        description = _(u'Name'),
304        )
305
306    email = schema.ASCIILine(
307        title = _(u'Email Address'),
308        default = None,
309        required = True,
310        constraint=validate_email,
311        description = _(
312            u'Email Address'),
313        )
314
315class IRefereeEntryField(IObject):
316    """A zope.schema-like field for usage in interfaces.
317
318    Marker interface to distuingish referee entries from ordinary
319    object fields. Needed for registration of widgets.
320    """
321
322class IKofaUtils(Interface):
323    """A collection of methods which are subject to customization.
324    """
325
326    PORTAL_LANGUAGE = Attribute("Dict of global language setting")
327    MONTHS_DICT = Attribute("Dict of months of the year")
328    PREFERRED_LANGUAGES_DICT = Attribute("Dict of preferred languages")
329    EXAM_SUBJECTS_DICT = Attribute("Dict of examination subjects")
330    EXAM_GRADES = Attribute("Dict of examination grades")
331    INST_TYPES_DICT = Attribute("Dict if institution types")
332    STUDY_MODES_DICT = Attribute("Dict of study modes")
333    APP_CATS_DICT = Attribute("Dict of application categories")
334    SEMESTER_DICT = Attribute("Dict of semesters or trimesters")
335    SYSTEM_MAX_LOAD = Attribute("Dict of maximum system loads.")
336    TEMP_PASSWORD_MINUTES = Attribute(
337        "Temporary passwords and parents password validity period")
338
339    def sendContactForm(
340          from_name,from_addr,rcpt_name,rcpt_addr,
341          from_username,usertype,portal,body,subject):
342        """Send an email with data provided by forms.
343        """
344
345    def fullname(firstname,lastname,middlename):
346        """Full name constructor.
347        """
348
349    def sendCredentials(user, password, url_info, msg):
350        """Send credentials as email.
351
352        Input is the applicant for which credentials are sent and the
353        password.
354
355        Returns True or False to indicate successful operation.
356        """
357
358    def genPassword(length, chars):
359        """Generate a random password.
360        """
361
362class IKofaObject(Interface):
363    """A Kofa object.
364
365    This is merely a marker interface.
366    """
367
368class IUniversity(IKofaObject):
369    """Representation of a university.
370    """
371
372
373class IKofaContainer(IKofaObject):
374    """A container for Kofa objects.
375    """
376
377class IKofaContained(IKofaObject):
378    """An item contained in an IKofaContainer.
379    """
380
381class ICSVExporter(Interface):
382    """A CSV file exporter for objects.
383    """
384    fields = Attribute("""List of fieldnames in resulting CSV""")
385
386    title = schema.TextLine(
387        title = u'Title',
388        description = u'Description to be displayed in selections.',
389        )
390    def mangle_value(value, name, obj):
391        """Mangle `value` extracted from `obj` or suobjects thereof.
392
393        This is called by export before actually writing to the result
394        file.
395        """
396
397    def get_filtered(site, **kw):
398        """Get datasets in `site` to be exported.
399
400        The set of data is specified by keywords, which might be
401        different for any implementaion of exporter.
402
403        Returns an iterable.
404        """
405
406    def get_selected(site, selected):
407        """Get datasets in `site` to be exported.
408
409        The set of data is specified by a list of identifiers.
410
411        Returns an iterable.
412        """
413
414    def export(iterable, filepath=None):
415        """Export iterables as rows in a CSV file.
416
417        If `filepath` is not given, a string with the data should be
418        returned.
419
420        What kind of iterables are acceptable depends on the specific
421        exporter implementation.
422        """
423
424    def export_all(site, filepath=None):
425        """Export all items in `site` as CSV file.
426
427        if `filepath` is not given, a string with the data should be
428        returned.
429        """
430
431    def export_filtered(site, filepath=None, **kw):
432        """Export those items in `site` specified by `args` and `kw`.
433
434        If `filepath` is not given, a string with the data should be
435        returned.
436
437        Which special keywords are supported is up to the respective
438        exporter.
439        """
440
441    def export_selected(site, filepath=None, **kw):
442        """Export items in `site` specified by a list of identifiers
443        called `selected`.
444
445        If `filepath` is not given, a string with the data should be
446        returned.
447        """
448
449class IKofaExporter(Interface):
450    """An exporter for objects.
451    """
452    def export(obj, filepath=None):
453        """Export by pickling.
454
455        Returns a file-like object containing a representation of `obj`.
456
457        This is done using `pickle`. If `filepath` is ``None``, a
458        `cStringIO` object is returned, that contains the saved data.
459        """
460
461class IKofaXMLExporter(Interface):
462    """An XML exporter for objects.
463    """
464    def export(obj, filepath=None):
465        """Export as XML.
466
467        Returns an XML representation of `obj`.
468
469        If `filepath` is ``None``, a StringIO` object is returned,
470        that contains the transformed data.
471        """
472
473class IKofaXMLImporter(Interface):
474    """An XML import for objects.
475    """
476    def doImport(filepath):
477        """Create Python object from XML.
478
479        Returns a Python object.
480        """
481
482class IBatchProcessor(Interface):
483    """A batch processor that handles mass-operations.
484    """
485    name = schema.TextLine(
486        title = _(u'Processor name')
487        )
488
489    def doImport(path, headerfields, mode='create', user='Unknown',
490                 logger=None, ignore_empty=True):
491        """Read data from ``path`` and update connected object.
492
493        `headerfields` is a list of headerfields as read from the file
494        to import.
495
496        `mode` gives the import mode to use (``'create'``,
497        ``'update'``, or ``'remove'``.
498
499        `user` is a string describing the user performing the
500        import. Normally fetched from current principal.
501
502        `logger` is the logger to use during import.
503
504        `ignore_emtpy` in update mode ignores empty fields if true.
505        """
506
507class IContactForm(IKofaObject):
508    """A contact form.
509    """
510
511    email_from = schema.ASCIILine(
512        title = _(u'Email Address:'),
513        default = None,
514        required = True,
515        constraint=validate_email,
516        )
517
518    email_to = schema.ASCIILine(
519        title = _(u'Email to:'),
520        default = None,
521        required = True,
522        constraint=validate_email,
523        )
524
525    bcc_to = schema.Text(
526        title = _(u'Bcc to:'),
527        required = True,
528        #readonly = True,
529        )
530
531    subject = schema.TextLine(
532        title = _(u'Subject:'),
533        required = True,)
534
535    fullname = schema.TextLine(
536        title = _(u'Full Name:'),
537        required = True,)
538
539    body = schema.Text(
540        title = _(u'Text:'),
541        required = True,)
542
543
544class IKofaPrincipalInfo(IPrincipalInfo):
545    """Infos about principals that are users of Kofa Kofa.
546    """
547    email = Attribute("The email address of a user")
548    phone = Attribute("The phone number of a user")
549    public_name = Attribute("The public name of a user")
550    user_type = Attribute("The type of a user")
551
552
553class IKofaPrincipal(IPrincipal):
554    """A principle for Kofa Kofa.
555
556    This interface extends zope.security.interfaces.IPrincipal and
557    requires also an `id` and other attributes defined there.
558    """
559
560    email = schema.TextLine(
561        title = _(u'Email Address'),
562        description = u'',
563        required=False,)
564
565    phone = PhoneNumber(
566        title = _(u'Phone'),
567        description = u'',
568        required=False,)
569
570    public_name = schema.TextLine(
571        title = _(u'Public Name'),
572        required = False,)
573
574    user_type = Attribute('The user type of the principal')
575
576class IFailedLoginInfo(IKofaObject):
577    """Info about failed logins.
578
579    Timestamps are supposed to be stored as floats using time.time()
580    or similar.
581    """
582    num = schema.Int(
583        title = _(u'Number of failed logins'),
584        description = _(u'Number of failed logins'),
585        required = True,
586        default = 0,
587        )
588
589    last = schema.Float(
590        title = _(u'Timestamp'),
591        description = _(u'Timestamp of last failed login or `None`'),
592        required = False,
593        default = None,
594        )
595
596    def as_tuple():
597        """Get login info as tuple ``<NUM>, <TIMESTAMP>``.
598        """
599
600    def set_values(num=0, last=None):
601        """Set number of failed logins and timestamp of last one.
602        """
603
604    def increase():
605        """Increase the current number of failed logins and set timestamp.
606        """
607
608    def reset():
609        """Set failed login counters back to zero.
610        """
611
612
613class IUserAccount(IKofaObject):
614    """A user account.
615    """
616
617    failed_logins = Attribute('FailedLoginInfo for this account')
618
619    name = schema.TextLine(
620        title = _(u'User Id'),
621        description = _(u'Login name of user'),
622        required = True,)
623
624    title = schema.TextLine(
625        title = _(u'Full Name'),
626        required = True,)
627
628    public_name = schema.TextLine(
629        title = _(u'Public Name'),
630        description = _(u"Substitute for officer's real name "
631                       "in student object histories "
632                       "and in local roles tables."),
633        required = False,)
634
635    description = schema.Text(
636        title = _(u'Description/Notice'),
637        required = False,)
638
639    email = schema.ASCIILine(
640        title = _(u'Email Address'),
641        default = None,
642        required = True,
643        constraint=validate_email,
644        )
645
646    phone = PhoneNumber(
647        title = _(u'Phone'),
648        default = None,
649        required = False,
650        )
651
652    roles = schema.List(
653        title = _(u'Portal Roles'),
654        value_type = schema.Choice(source=RoleSource()),
655        required = False,
656        )
657
658    suspended = schema.Bool(
659        title = _(u'Account suspended'),
660        description = _(u'If set, the account is immediately blocked.'),
661        default = False,
662        required = False,
663        )
664
665
666class IPasswordValidator(Interface):
667    """A password validator utility.
668    """
669
670    def validate_password(password, password_repeat):
671        """Validates a password by comparing it with
672        control password and checking some other requirements.
673        """
674
675    def validate_secure_password(self, pw, pw_repeat):
676        """ Validates a password by comparing it with
677        control password and checks password strength by
678        matching with the regular expression:
679
680        ^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$
681
682        ^              Start anchor
683        (?=.*[A-Z])    Ensure password has one uppercase letters.
684        (?=.*[0-9])    Ensure password has one digit.
685        (?=.*[a-z])    Ensure password has one lowercase letter.
686        .{8,}          Ensure password is of length 8.
687        $              End anchor.
688        """
689
690
691class IUsersContainer(IKofaObject):
692    """A container for officers.
693    """
694
695    def addUser(name, password, title=None, description=None):
696        """Add a user.
697        """
698
699    def delUser(name):
700        """Delete a user if it exists.
701        """
702
703class ILocalRolesAssignable(Interface):
704    """The local roles assignable to an object.
705    """
706    def __call__():
707        """Returns a list of dicts.
708
709        Each dict contains a ``name`` referring to the role assignable
710        for the specified object and a `title` to describe the range
711        of users to which this role can be assigned.
712        """
713
714class IConfigurationContainer(IKofaObject):
715    """A container for session configuration objects.
716    """
717
718    name = schema.TextLine(
719        title = _(u'Name of University'),
720        default = _(u'Sample University'),
721        required = True,
722        )
723
724    acronym = schema.TextLine(
725        title = _(u'Abbreviated Title of University'),
726        default = u'WAeUP.Kofa',
727        required = True,
728        )
729
730    frontpage = schema.Text(
731        title = _(u'Content in HTML format'),
732        required = False,
733        default = default_html_frontpage,
734        constraint=validate_html,
735        )
736
737    frontpage_dict = schema.Dict(
738        title = u'Content as language dictionary with values in html format',
739        required = False,
740        default = {},
741        )
742
743    name_admin = schema.TextLine(
744        title = _(u'Name of Administrator'),
745        default = u'Administrator',
746        required = True,
747        )
748
749    email_admin = schema.ASCIILine(
750        title = _(u'Email Address of Administrator'),
751        default = 'contact@waeup.org',
752        required = True,
753        #constraint=validate_email,
754        )
755
756    email_subject = schema.TextLine(
757        title = _(u'Subject of Email to Administrator'),
758        default = _(u'Kofa Contact'),
759        required = True,
760        )
761
762    smtp_mailer = schema.Choice(
763        title = _(u'SMTP mailer to use when sending mail'),
764        vocabulary = 'Mail Delivery Names',
765        default = 'No email service',
766        required = True,
767        )
768
769    captcha = schema.Choice(
770        title = _(u'Captcha used for public registration pages'),
771        source = CaptchaSource(),
772        default = u'No captcha',
773        required = True,
774        )
775
776    carry_over = schema.Bool(
777        title = _(u'Carry-over Course Registration'),
778        default = False,
779        )
780
781    current_academic_session = schema.Choice(
782        title = _(u'Current Academic Session'),
783        description = _(u'Session for which score editing is allowed'),
784        source = academic_sessions_vocab,
785        default = None,
786        required = False,
787        readonly = False,
788        )
789
790    next_matric_integer = schema.Int(
791        title = _(u'Next Matriculation Number Integer'),
792        description = _(u'Integer used for constructing the next '
793                         'matriculation number'),
794        default = 0,
795        readonly = False,
796        required = False,
797        )
798
799    next_matric_integer_2 = schema.Int(
800        title = _(u'Next Matriculation Number Integer 2'),
801        description = _(u'2nd integer used for constructing the next '
802                         'matriculation number'),
803        default = 0,
804        readonly = False,
805        required = False,
806        )
807
808    next_matric_integer_3 = schema.Int(
809        title = _(u'Next Matriculation Number Integer 3'),
810        description = _(u'3rd integer used for constructing the next '
811                         'matriculation number'),
812        default = 0,
813        readonly = False,
814        required = False,
815        )
816
817    next_matric_integer_4 = schema.Int(
818        title = _(u'Next Matriculation Number Integer 4'),
819        description = _(u'4th integer used for constructing the next '
820                         'matriculation number'),
821        default = 0,
822        readonly = False,
823        required = False,
824        )
825
826    export_disabled_message = schema.Text(
827        title = _(u'Export-disabled message'),
828        description = _(u'Message which will show up if an officer tries '
829                         'to export data. All exporters are automatcally '
830                         'disabled if this field is set.'),
831        required = False,
832        )
833
834    maintmode_enabled_by = schema.TextLine(
835        title = _(u'Maintenance Mode enabled by'),
836        default = None,
837        required = False,
838        )
839
840    def addSessionConfiguration(sessionconfiguration):
841        """Add a session configuration object.
842        """
843
844class ISessionConfiguration(IKofaObject):
845    """A session configuration object.
846    """
847
848    academic_session = schema.Choice(
849        title = _(u'Academic Session'),
850        source = academic_sessions_vocab,
851        default = None,
852        required = True,
853        readonly = True,
854        )
855
856    clearance_enabled = schema.Bool(
857        title = _(u'Clearance enabled'),
858        default = False,
859        )
860
861    payment_disabled = schema.List(
862        title = _(u'Payment disabled'),
863        value_type = schema.Choice(
864            source = DisablePaymentGroupSource(),
865            ),
866        required = False,
867        defaultFactory=list,
868        )
869
870    coursereg_deadline = schema.Datetime(
871        title = _(u'Course Reg. Deadline'),
872        required = False,
873        description = _('Example: ') + u'2011-12-31 23:59:59+01:00',
874        )
875
876    clearance_fee = schema.Float(
877        title = _(u'Acceptance Fee'),
878        default = 0.0,
879        required = False,
880        )
881
882    booking_fee = schema.Float(
883        title = _(u'Bed Booking Fee'),
884        default = 0.0,
885        required = False,
886        )
887
888    maint_fee = schema.Float(
889        title = _(u'Rent (fallback)'),
890        default = 0.0,
891        required = False,
892        )
893
894    late_registration_fee = schema.Float(
895        title = _(u'Late Course Reg. Fee'),
896        default = 0.0,
897        required = False,
898        )
899
900    transcript_fee = schema.Float(
901        title = _(u'Transcript Fee'),
902        default = 0.0,
903        required = False,
904        )
905
906    transfer_fee = schema.Float(
907        title = _(u'Transfer Fee'),
908        default = 0.0,
909        required = False,
910        )
911
912    def getSessionString():
913        """Return the session string from the vocabulary.
914        """
915
916
917class ISessionConfigurationAdd(ISessionConfiguration):
918    """A session configuration object in add mode.
919    """
920
921    academic_session = schema.Choice(
922        title = _(u'Academic Session'),
923        source = academic_sessions_vocab,
924        default = None,
925        required = True,
926        readonly = False,
927        )
928
929ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
930    'academic_session'].order
931
932class IDataCenter(IKofaObject):
933    """A data center.
934
935    A data center manages files (uploads, downloads, etc.).
936
937    Beside providing the bare paths needed to keep files, it also
938    provides some helpers to put results of batch processing into
939    well-defined final locations (with well-defined filenames).
940
941    The main use-case is managing of site-related files, i.e. files
942    for import, export etc.
943
944    DataCenters are _not_ meant as storages for object-specific files
945    like passport photographs and similar.
946
947    It is up to the datacenter implementation how to organize data
948    (paths) inside its storage path.
949    """
950    storage = schema.Bytes(
951        title = u'Path to directory where everything is kept.'
952        )
953
954    deleted_path = schema.Bytes(
955        title = u'Path for deleted student data.'
956        )
957
958    graduated_path = schema.Bytes(
959        title = u'Path for deleted graduated student data.'
960        )
961
962    def getPendingFiles(sort='name'):
963        """Get a list of files stored in `storage` sorted by basename.
964        """
965
966    def getFinishedFiles():
967        """Get a list of files stored in `finished` subfolder of `storage`.
968        """
969
970    def setStoragePath(path, move=False, overwrite=False):
971        """Set the path where to store files.
972
973        If `move` is True, move over files from the current location
974        to the new one.
975
976        If `overwrite` is also True, overwrite any already existing
977        files of same name in target location.
978
979        Triggers a DataCenterStorageMovedEvent.
980        """
981
982    def distProcessedFiles(successful, source_path, finished_file,
983                           pending_file, mode='create', move_orig=True):
984        """Distribute processed files over final locations.
985        """
986
987
988class IDataCenterFile(Interface):
989    """A data center file.
990    """
991
992    name = schema.TextLine(
993        title = u'Filename')
994
995    size = schema.TextLine(
996        title = u'Human readable file size')
997
998    uploaddate = schema.TextLine(
999        title = u'Human readable upload datetime')
1000
1001    lines = schema.Int(
1002        title = u'Number of lines in file')
1003
1004    def getDate():
1005        """Get creation timestamp from file in human readable form.
1006        """
1007
1008    def getSize():
1009        """Get human readable size of file.
1010        """
1011
1012    def getLinesNumber():
1013        """Get number of lines of file.
1014        """
1015
1016class IDataCenterStorageMovedEvent(IObjectEvent):
1017    """Emitted, when the storage of a datacenter changes.
1018    """
1019
1020class IObjectUpgradeEvent(IObjectEvent):
1021    """Can be fired, when an object shall be upgraded.
1022    """
1023
1024class ILocalRoleSetEvent(IObjectEvent):
1025    """A local role was granted/revoked for a principal on an object.
1026    """
1027    role_id = Attribute(
1028        "The role id that was set.")
1029    principal_id = Attribute(
1030        "The principal id for which the role was granted/revoked.")
1031    granted = Attribute(
1032        "Boolean. If false, then the role was revoked.")
1033
1034class IQueryResultItem(Interface):
1035    """An item in a search result.
1036    """
1037    url = schema.TextLine(
1038        title = u'URL that links to the found item')
1039    title = schema.TextLine(
1040        title = u'Title displayed in search results.')
1041    description = schema.Text(
1042        title = u'Longer description of the item found.')
1043
1044class IKofaPluggable(Interface):
1045    """A component that might be plugged into a Kofa Kofa app.
1046
1047    Components implementing this interface are referred to as
1048    'plugins'. They are normally called when a new
1049    :class:`waeup.kofa.app.University` instance is created.
1050
1051    Plugins can setup and update parts of the central site without the
1052    site object (normally a :class:`waeup.kofa.app.University` object)
1053    needing to know about that parts. The site simply collects all
1054    available plugins, calls them and the plugins care for their
1055    respective subarea like the applicants area or the datacenter
1056    area.
1057
1058    Currently we have no mechanism to define an order of plugins. A
1059    plugin should therefore make no assumptions about the state of the
1060    site or other plugins being run before and instead do appropriate
1061    checks if necessary.
1062
1063    Updates can be triggered for instance by the respective form in
1064    the site configuration. You normally do updates when the
1065    underlying software changed.
1066    """
1067    def setup(site, name, logger):
1068        """Create an instance of the plugin.
1069
1070        The method is meant to be called by the central app (site)
1071        when it is created.
1072
1073        `site`:
1074           The site that requests a setup.
1075
1076        `name`:
1077           The name under which the plugin was registered (utility name).
1078
1079        `logger`:
1080           A standard Python logger for the plugins use.
1081        """
1082
1083    def update(site, name, logger):
1084        """Method to update an already existing plugin.
1085
1086        This might be called by a site when something serious
1087        changes. It is a poor-man replacement for Zope generations
1088        (but probably more comprehensive and better understandable).
1089
1090        `site`:
1091           The site that requests an update.
1092
1093        `name`:
1094           The name under which the plugin was registered (utility name).
1095
1096        `logger`:
1097           A standard Python logger for the plugins use.
1098        """
1099
1100class IAuthPluginUtility(Interface):
1101    """A component that cares for authentication setup at site creation.
1102
1103    Utilities providing this interface are looked up when a Pluggable
1104    Authentication Utility (PAU) for any
1105    :class:`waeup.kofa.app.University` instance is created and put
1106    into ZODB.
1107
1108    The setup-code then calls the `register` method of the utility and
1109    expects a modified (or unmodified) version of the PAU back.
1110
1111    This allows to define any authentication setup modifications by
1112    submodules or third-party modules/packages.
1113    """
1114
1115    def register(pau):
1116        """Register any plugins wanted to be in the PAU.
1117        """
1118
1119    def unregister(pau):
1120        """Unregister any plugins not wanted to be in the PAU.
1121        """
1122
1123class IObjectConverter(Interface):
1124    """Object converters are available as simple adapters, adapting
1125       interfaces (not regular instances).
1126
1127    """
1128
1129    def fromStringDict(self, data_dict, context, form_fields=None):
1130        """Convert values in `data_dict`.
1131
1132        Converts data in `data_dict` into real values based on
1133        `context` and `form_fields`.
1134
1135        `data_dict` is a mapping (dict) from field names to values
1136        represented as strings.
1137
1138        The fields (keys) to convert can be given in optional
1139        `form_fields`. If given, form_fields should be an instance of
1140        :class:`zope.formlib.form.Fields`. Suitable instances are for
1141        example created by :class:`grok.AutoFields`.
1142
1143        If no `form_fields` are given, a default is computed from the
1144        associated interface.
1145
1146        The `context` can be an existing object (implementing the
1147        associated interface) or a factory name. If it is a string, we
1148        try to create an object using
1149        :func:`zope.component.createObject`.
1150
1151        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
1152        <DATA_DICT>)`` where
1153
1154        ``<FIELD_ERRORS>``
1155           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
1156           error that happened when validating the input data in
1157           `data_dict`
1158
1159        ``<INVARIANT_ERRORS>``
1160           is a list of invariant errors concerning several fields
1161
1162        ``<DATA_DICT>``
1163           is a dict with the values from input dict converted.
1164
1165        If errors happen, i.e. the error lists are not empty, always
1166        an empty ``<DATA_DICT>`` is returned.
1167
1168        If ``<DATA_DICT>`` is non-empty, there were no errors.
1169        """
1170
1171class IFieldConverter(Interface):
1172    def request_data(name, value, schema_field, prefix='', mode='create'):
1173        """Create a dict with key-value mapping as created by a request.
1174
1175        `name` and `value` are expected to be parsed from CSV or a
1176        similar input and represent an attribute to be set to a
1177        representation of value.
1178
1179        `mode` gives the mode of import.
1180
1181        :meth:`update_request_data` is then requested to turn this
1182        name and value into vars as they would be sent by a regular
1183        form submit. This means we do not create the real values to be
1184        set but we only define the values that would be sent in a
1185        browser request to request the creation of those values.
1186
1187        The returned dict should contain names and values of a faked
1188        browser request for the given `schema_field`.
1189
1190        Field converters are normally registered as adapters to some
1191        specific zope.schema field.
1192        """
1193
1194class IObjectHistory(Interface):
1195
1196    messages = schema.List(
1197        title = u'List of messages stored',
1198        required = True,
1199        )
1200
1201    def addMessage(message):
1202        """Add a message.
1203        """
1204
1205class IKofaWorkflowInfo(IWorkflowInfo):
1206    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
1207       methods for convenience.
1208    """
1209    def getManualTransitions():
1210        """Get allowed manual transitions.
1211
1212        Get a sorted list of tuples containing the `transition_id` and
1213        `title` of each allowed transition.
1214        """
1215
1216class ISiteLoggers(Interface):
1217
1218    loggers = Attribute("A list or generator of registered KofaLoggers")
1219
1220    def register(name, filename=None, site=None, **options):
1221        """Register a logger `name` which logs to `filename`.
1222
1223        If `filename` is not given, logfile will be `name` with
1224        ``.log`` as filename extension.
1225        """
1226
1227    def unregister(name):
1228        """Unregister a once registered logger.
1229        """
1230
1231class ILogger(Interface):
1232    """A logger cares for setup, update and restarting of a Python logger.
1233    """
1234
1235    logger = Attribute("""A :class:`logging.Logger` instance""")
1236
1237
1238    def __init__(name, filename=None, site=None, **options):
1239        """Create a Kofa logger instance.
1240        """
1241
1242    def setup():
1243        """Create a Python :class:`logging.Logger` instance.
1244
1245        The created logger is based on the params given by constructor.
1246        """
1247
1248    def update(**options):
1249        """Update the logger.
1250
1251        Updates the logger respecting modified `options` and changed
1252        paths.
1253        """
1254
1255class ILoggerCollector(Interface):
1256
1257    def getLoggers(site):
1258        """Return all loggers registered for `site`.
1259        """
1260
1261    def registerLogger(site, logging_component):
1262        """Register a logging component residing in `site`.
1263        """
1264
1265    def unregisterLogger(site, logging_component):
1266        """Unregister a logger.
1267        """
1268
1269#
1270# External File Storage and relatives
1271#
1272class IFileStoreNameChooser(INameChooser):
1273    """See zope.container.interfaces.INameChooser for base methods.
1274    """
1275    def checkName(name, attr=None):
1276        """Check whether an object name is valid.
1277
1278        Raises a user error if the name is not valid.
1279        """
1280
1281    def chooseName(name, attr=None):
1282        """Choose a unique valid file id for the object.
1283
1284        The given name may be taken into account when choosing the
1285        name (file id).
1286
1287        chooseName is expected to always choose a valid file id (that
1288        would pass the checkName test) and never raise an error.
1289
1290        If `attr` is not ``None`` it might been taken into account as
1291        well when generating the file id. Usual behaviour is to
1292        interpret `attr` as a hint for what type of file for a given
1293        context should be stored if there are several types
1294        possible. For instance for a certain student some file could
1295        be the connected passport photograph or some certificate scan
1296        or whatever. Each of them has to be stored in a different
1297        location so setting `attr` to a sensible value should give
1298        different file ids returned.
1299        """
1300
1301class IExtFileStore(IFileRetrieval):
1302    """A file storage that stores files in filesystem (not as blobs).
1303    """
1304    root = schema.TextLine(
1305        title = u'Root path of file store.',
1306        )
1307
1308    def getFile(file_id):
1309        """Get raw file data stored under file with `file_id`.
1310
1311        Returns a file descriptor open for reading or ``None`` if the
1312        file cannot be found.
1313        """
1314
1315    def getFileByContext(context, attr=None):
1316        """Get raw file data stored for the given context.
1317
1318        Returns a file descriptor open for reading or ``None`` if no
1319        such file can be found.
1320
1321        Both, `context` and `attr` might be used to find (`context`)
1322        and feed (`attr`) an appropriate file name chooser.
1323
1324        This is a convenience method.
1325        """
1326
1327    def deleteFile(file_id):
1328        """Delete file stored under `file_id`.
1329
1330        Remove file from filestore so, that it is not available
1331        anymore on next call to getFile for the same file_id.
1332
1333        Should not complain if no such file exists.
1334        """
1335
1336    def deleteFileByContext(context, attr=None):
1337        """Delete file for given `context` and `attr`.
1338
1339        Both, `context` and `attr` might be used to find (`context`)
1340        and feed (`attr`) an appropriate file name chooser.
1341
1342        This is a convenience method.
1343        """
1344
1345    def createFile(filename, f):
1346        """Create file given by f with filename `filename`
1347
1348        Returns a hurry.file.File-based object.
1349        """
1350
1351class IFileStoreHandler(Interface):
1352    """Filestore handlers handle specific files for file stores.
1353
1354    If a file to store/get provides a specific filename, a file store
1355    looks up special handlers for that type of file.
1356
1357    """
1358    def pathFromFileID(store, root, filename):
1359        """Turn file id into path to store.
1360
1361        Returned path should be absolute.
1362        """
1363
1364    def createFile(store, root, filename, file_id, file):
1365        """Return some hurry.file based on `store` and `file_id`.
1366
1367        Some kind of callback method called by file stores to create
1368        file objects from file_id.
1369
1370        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
1371        ``file_like_obj`` should be a HurryFile, a KofaImageFile or
1372        similar. ``raw_file`` is the (maybe changed) input file and
1373        ``path`` the relative internal path to store the file at.
1374
1375        Please make sure the ``raw_file`` is opened for reading and
1376        the file descriptor set at position 0 when returned.
1377
1378        This method also gets the raw input file object that is about
1379        to be stored and is expected to raise any exceptions if some
1380        kind of validation or similar fails.
1381        """
1382
1383class IPDF(Interface):
1384    """A PDF representation of some context.
1385    """
1386
1387    def __call__(view=None, note=None):
1388        """Create a bytestream representing a PDF from context.
1389
1390        If `view` is passed in additional infos might be rendered into
1391        the document.
1392
1393        `note` is optional HTML rendered at bottom of the created
1394        PDF. Please consider the limited reportlab support for HTML,
1395        but using font-tags and friends you certainly can get the
1396        desired look.
1397        """
1398
1399class IMailService(Interface):
1400    """A mail service.
1401    """
1402
1403    def __call__():
1404        """Get the default mail delivery.
1405        """
1406
1407
1408class IDataCenterConfig(Interface):
1409    path = Path(
1410        title = u'Path',
1411        description = u"Directory where the datacenter should store "
1412                      u"files by default (adjustable in web UI).",
1413        required = True,
1414        )
1415
1416#
1417# Asynchronous job handling and related
1418#
1419class IJobManager(IKofaObject):
1420    """A manager for asynchronous running jobs (tasks).
1421    """
1422    def put(job, site=None):
1423        """Put a job into task queue.
1424
1425        If no `site` is given, queue job in context of current local
1426        site.
1427
1428        Returns a job_id to identify the put job. This job_id is
1429        needed for further references to the job.
1430        """
1431
1432    def jobs(site=None):
1433        """Get an iterable of jobs stored.
1434        """
1435
1436    def get(job_id, site=None):
1437        """Get the job with id `job_id`.
1438
1439        For the `site` parameter see :meth:`put`.
1440        """
1441
1442    def remove(job_id, site=None):
1443        """Remove job with `job_id` from stored jobs.
1444        """
1445
1446    def start_test_job(site=None):
1447        """Start a test job.
1448        """
1449
1450class IProgressable(Interface):
1451    """A component that can indicate its progress status.
1452    """
1453    percent = schema.Float(
1454        title = u'Percent of job done already.',
1455        )
1456
1457class IJobContainer(IContainer):
1458    """A job container contains IJob objects.
1459    """
1460
1461class IExportJob(zc.async.interfaces.IJob):
1462    def __init__(site, exporter_name):
1463        pass
1464
1465    finished = schema.Bool(
1466        title = u'`True` if the job finished.`',
1467        default = False,
1468        )
1469
1470    failed = schema.Bool(
1471        title = u"`True` iff the job finished and didn't provide a file.",
1472        default = None,
1473        )
1474
1475class IExportJobContainer(IKofaObject):
1476    """A component that contains (maybe virtually) export jobs.
1477    """
1478    def start_export_job(exporter_name, user_id, *args, **kwargs):
1479        """Start asynchronous export job.
1480
1481        `exporter_name` is the name of an exporter utility to be used.
1482
1483        `user_id` is the ID of the user that triggers the export.
1484
1485        `args` positional arguments passed to the export job created.
1486
1487        `kwargs` keyword arguments passed to the export job.
1488
1489        The job_id is stored along with exporter name and user id in a
1490        persistent list.
1491
1492        Returns the job ID of the job started.
1493        """
1494
1495    def get_running_export_jobs(user_id=None):
1496        """Get export jobs for user with `user_id` as list of tuples.
1497
1498        Each tuples holds ``<job_id>, <exporter_name>, <user_id>`` in
1499        that order. The ``<exporter_name>`` is the utility name of the
1500        used exporter.
1501
1502        If `user_id` is ``None``, all running jobs are returned.
1503        """
1504
1505    def get_export_jobs_status(user_id=None):
1506        """Get running/completed export jobs for `user_id` as list of tuples.
1507
1508        Each tuple holds ``<raw status>, <status translated>,
1509        <exporter title>`` in that order, where ``<status
1510        translated>`` and ``<exporter title>`` are translated strings
1511        representing the status of the job and the human readable
1512        title of the exporter used.
1513        """
1514
1515    def delete_export_entry(entry):
1516        """Delete the export denoted by `entry`.
1517
1518        Removes `entry` from the local `running_exports` list and also
1519        removes the regarding job via the local job manager.
1520
1521        `entry` is a tuple ``(<job id>, <exporter name>, <user id>)``
1522        as created by :meth:`start_export_job` or returned by
1523        :meth:`get_running_export_jobs`.
1524        """
1525
1526    def entry_from_job_id(job_id):
1527        """Get entry tuple for `job_id`.
1528
1529        Returns ``None`` if no such entry can be found.
1530        """
1531
1532class IExportContainerFinder(Interface):
1533    """A finder for the central export container.
1534    """
1535    def __call__():
1536        """Return the currently used global or site-wide IExportContainer.
1537        """
1538
1539class IFilteredQuery(IKofaObject):
1540    """A query for objects.
1541    """
1542
1543    defaults = schema.Dict(
1544        title = u'Default Parameters',
1545        required = True,
1546        )
1547
1548    def __init__(**parameters):
1549        """Instantiate a filtered query by passing in parameters.
1550        """
1551
1552    def query():
1553        """Get an iterable of objects denoted by the set parameters.
1554
1555        The search should be applied to objects inside current
1556        site. It's the caller's duty to set the correct site before.
1557
1558        Result can be any iterable like a catalog result set, a list,
1559        or similar.
1560        """
1561
1562class IFilteredCatalogQuery(IFilteredQuery):
1563    """A catalog-based query for objects.
1564    """
1565
1566    cat_name = schema.TextLine(
1567        title = u'Registered name of the catalog to search.',
1568        required = True,
1569        )
1570
1571    def query_catalog(catalog):
1572        """Query catalog with the parameters passed to constructor.
1573        """
Note: See TracBrowser for help on using the repository browser.