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

Last change on this file since 11584 was 11451, checked in by Henrik Bettermann, 11 years ago

Provide customizable method for disabling student payments.

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