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

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

Add next_matric_integer_3 field.

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