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

Last change on this file since 11661 was 11589, checked in by Henrik Bettermann, 11 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 41.1 KB
Line 
1## $Id: interfaces.py 11589 2014-04-22 07:13:16Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18import os
19import re
20import codecs
21import zc.async.interfaces
22import zope.i18nmessageid
23from datetime import datetime
24from hurry.file.interfaces import IFileRetrieval
25from hurry.workflow.interfaces import IWorkflowInfo
26from zc.sourcefactory.basic import BasicSourceFactory
27from zope import schema
28from zope.pluggableauth.interfaces import IPrincipalInfo
29from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
30from zope.component import getUtility
31from zope.component.interfaces import IObjectEvent
32from zope.configuration.fields import Path
33from zope.container.interfaces import INameChooser, IContainer
34from zope.interface import Interface, Attribute
35from zope.schema.interfaces import IObject
36from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
37from waeup.kofa.schema import PhoneNumber
38from waeup.kofa.sourcefactory import SmartBasicContextualSourceFactory
39
40_ = MessageFactory = zope.i18nmessageid.MessageFactory('waeup.kofa')
41
42DELETION_MARKER = 'XXX'
43IGNORE_MARKER = '<IGNORE>'
44WAEUP_KEY = 'waeup.kofa'
45VIRT_JOBS_CONTAINER_NAME = 'jobs'
46
47CREATED = 'created'
48ADMITTED = 'admitted'
49CLEARANCE = 'clearance started'
50REQUESTED = 'clearance requested'
51CLEARED = 'cleared'
52PAID = 'school fee paid'
53RETURNING = 'returning'
54REGISTERED = 'courses registered'
55VALIDATED = 'courses validated'
56GRADUATED = 'graduated'
57TRANSCRIPT = 'transcript requested'
58
59
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
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'),
77        encoding='utf-8', mode='rb').read()
78
79def SimpleKofaVocabulary(*terms):
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
88    year_range = range(1995, curr_year + 2)
89    return [('%s/%s' % (year,year+1), year) for year in year_range]
90
91academic_sessions_vocab = SimpleKofaVocabulary(*academic_sessions())
92
93registration_states_vocab = SimpleKofaVocabulary(
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),
102    (_('returning'), RETURNING),
103    (_('graduated'), GRADUATED),
104    (_('transcript requested'), TRANSCRIPT),
105    )
106
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
124class SubjectSource(BasicSourceFactory):
125    """A source for school subjects used in exam documentation.
126    """
127    def getValues(self):
128        subjects_dict = getUtility(IKofaUtils).EXAM_SUBJECTS_DICT
129        return sorted(subjects_dict.keys())
130
131    def getTitle(self, value):
132        subjects_dict = getUtility(IKofaUtils).EXAM_SUBJECTS_DICT
133        return "%s:" % subjects_dict[value]
134
135class GradeSource(BasicSourceFactory):
136    """A source for exam grades.
137    """
138    def getValues(self):
139        for entry in getUtility(IKofaUtils).EXAM_GRADES:
140            yield entry[0]
141
142    def getTitle(self, value):
143        return dict(getUtility(IKofaUtils).EXAM_GRADES)[value]
144
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
151# Define a validation method for email addresses
152class NotAnEmailAddress(schema.ValidationError):
153    __doc__ = u"Invalid email address"
154
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.
158check_email = re.compile(
159    r"^[^@\s,]+@[^@\.\s,]+(\.[^@\.\s,]+)*$").match
160
161def validate_email(value):
162    if not check_email(value):
163        raise NotAnEmailAddress(value)
164    return True
165
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):
174    if not RE_INT_PHONE.match(value):
175        raise InvalidPhoneNumber(value)
176    return True
177
178class FatalCSVError(Exception):
179    """Some row could not be processed.
180    """
181    pass
182
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
198class RoleSource(BasicSourceFactory):
199    """A source for site roles.
200    """
201    def getValues(self):
202        # late import: in interfaces we should not import local modules
203        from waeup.kofa.permissions import get_waeup_role_names
204        return get_waeup_role_names()
205
206    def getTitle(self, value):
207        # late import: in interfaces we should not import local modules
208        from waeup.kofa.permissions import get_all_roles
209        roles = dict(get_all_roles())
210        if value in roles.keys():
211            title = roles[value].title
212            if '.' in title:
213                title = title.split('.', 2)[1]
214        return title
215
216class CaptchaSource(BasicSourceFactory):
217    """A source for captchas.
218    """
219    def getValues(self):
220        captchas = ['No captcha', 'Testing captcha', 'ReCaptcha']
221        try:
222            # we have to 'try' because IConfiguration can only handle
223            # interfaces from w.k.interface.
224            from waeup.kofa.browser.interfaces import ICaptchaManager
225        except:
226            return captchas
227        return sorted(getUtility(ICaptchaManager).getAvailCaptchas().keys())
228
229    def getTitle(self, value):
230        return value
231
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
251class IKofaUtils(Interface):
252    """A collection of methods which are subject to customization.
253    """
254
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")
263    INT_PHONE_PREFIXES = Attribute(
264        "Dict of international phone number prefixes")
265
266    def sendContactForm(
267          from_name,from_addr,rcpt_name,rcpt_addr,
268          from_username,usertype,portal,body,subject):
269        """Send an email with data provided by forms.
270        """
271
272    def fullname(firstname,lastname,middlename):
273        """Full name constructor.
274        """
275
276    def sendCredentials(user, password, url_info, msg):
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
289class IKofaObject(Interface):
290    """A Kofa object.
291
292    This is merely a marker interface.
293    """
294
295class IUniversity(IKofaObject):
296    """Representation of a university.
297    """
298
299
300class IKofaContainer(IKofaObject):
301    """A container for Kofa objects.
302    """
303
304class IKofaContained(IKofaObject):
305    """An item contained in an IKofaContainer.
306    """
307
308class ICSVExporter(Interface):
309    """A CSV file exporter for objects.
310    """
311    fields = Attribute("""List of fieldnames in resulting CSV""")
312
313    title = schema.TextLine(
314        title = u'Title',
315        description = u'Description to be displayed in selections.',
316        )
317    def mangle_value(value, name, obj):
318        """Mangle `value` extracted from `obj` or suobjects thereof.
319
320        This is called by export before actually writing to the result
321        file.
322        """
323
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
333    def export(iterable, filepath=None):
334        """Export iterables as rows in a CSV file.
335
336        If `filepath` is not given, a string with the data should be
337        returned.
338
339        What kind of iterables are acceptable depends on the specific
340        exporter implementation.
341        """
342
343    def export_all(site, filepath=None):
344        """Export all items in `site` as CSV file.
345
346        if `filepath` is not given, a string with the data should be
347        returned.
348        """
349
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
360class IKofaExporter(Interface):
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
372class IKofaXMLExporter(Interface):
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
384class IKofaXMLImporter(Interface):
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
393class IBatchProcessor(Interface):
394    """A batch processor that handles mass-operations.
395    """
396    name = schema.TextLine(
397        title = _(u'Processor name')
398        )
399
400    def doImport(path, headerfields, mode='create', user='Unknown',
401                 logger=None, ignore_empty=True):
402        """Read data from ``path`` and update connected object.
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.
414
415        `ignore_emtpy` in update mode ignores empty fields if true.
416        """
417
418class IContactForm(IKofaObject):
419    """A contact form.
420    """
421
422    email_from = schema.ASCIILine(
423        title = _(u'Email Address:'),
424        default = None,
425        required = True,
426        constraint=validate_email,
427        )
428
429    email_to = schema.ASCIILine(
430        title = _(u'Email to:'),
431        default = None,
432        required = True,
433        constraint=validate_email,
434        )
435
436    subject = schema.TextLine(
437        title = _(u'Subject:'),
438        required = True,)
439
440    fullname = schema.TextLine(
441        title = _(u'Full Name:'),
442        required = True,)
443
444    body = schema.Text(
445        title = _(u'Text:'),
446        required = True,)
447
448class IKofaPrincipalInfo(IPrincipalInfo):
449    """Infos about principals that are users of Kofa Kofa.
450    """
451    email = Attribute("The email address of a user")
452    phone = Attribute("The phone number of a user")
453    public_name = Attribute("The public name of a user")
454
455
456class IKofaPrincipal(IPrincipal):
457    """A principle for Kofa Kofa.
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(
464        title = _(u'Email Address'),
465        description = u'',
466        required=False,)
467
468    phone = PhoneNumber(
469        title = _(u'Phone'),
470        description = u'',
471        required=False,)
472
473    public_name = schema.TextLine(
474        title = _(u'Public Name'),
475        required = False,)
476
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
514class IUserAccount(IKofaObject):
515    """A user account.
516    """
517
518    failed_logins = Attribute("""FailedLoginInfo for this account""")
519
520    name = schema.TextLine(
521        title = _(u'User Id'),
522        description = u'Login name of user',
523        required = True,)
524
525    title = schema.TextLine(
526        title = _(u'Full Name'),
527        required = True,)
528
529    public_name = schema.TextLine(
530        title = _(u'Public Name'),
531        description = u"Substitute for officer's real name "
532                       "in student object histories.",
533        required = False,)
534
535    description = schema.Text(
536        title = _(u'Description/Notice'),
537        required = False,)
538
539    email = schema.ASCIILine(
540        title = _(u'Email Address'),
541        default = None,
542        required = True,
543        constraint=validate_email,
544        )
545
546    phone = PhoneNumber(
547        title = _(u'Phone'),
548        default = None,
549        required = False,
550        )
551
552    roles = schema.List(
553        title = _(u'Portal Roles'),
554        value_type = schema.Choice(source=RoleSource()),
555        required = False,
556        )
557
558
559
560class IPasswordValidator(Interface):
561    """A password validator utility.
562    """
563
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
570class IUsersContainer(IKofaObject):
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
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
595class IConfigurationContainer(IKofaObject):
596    """A container for session configuration objects.
597    """
598
599    name = schema.TextLine(
600        title = _(u'Name of University'),
601        default = _(u'Sample University'),
602        required = True,
603        )
604
605    acronym = schema.TextLine(
606        title = _(u'Abbreviated Title of University'),
607        default = u'WAeUP.Kofa',
608        required = True,
609        )
610
611    frontpage = schema.Text(
612        title = _(u'Content in HTML format'),
613        required = False,
614        default = default_html_frontpage,
615        )
616
617    frontpage_dict = schema.Dict(
618        title = u'Content as language dictionary with values in html format',
619        required = False,
620        default = {},
621        )
622
623    name_admin = schema.TextLine(
624        title = _(u'Name of Administrator'),
625        default = u'Administrator',
626        required = True,
627        )
628
629    email_admin = schema.ASCIILine(
630        title = _(u'Email Address of Administrator'),
631        default = 'contact@waeup.org',
632        required = True,
633        constraint=validate_email,
634        )
635
636    email_subject = schema.TextLine(
637        title = _(u'Subject of Email to Administrator'),
638        default = _(u'Kofa Contact'),
639        required = True,
640        )
641
642    smtp_mailer = schema.Choice(
643        title = _(u'SMTP mailer to use when sending mail'),
644        vocabulary = 'Mail Delivery Names',
645        default = 'No email service',
646        required = True,
647        )
648
649    captcha = schema.Choice(
650        title = _(u'Captcha used for public registration pages'),
651        source = CaptchaSource(),
652        default = u'No captcha',
653        required = True,
654        )
655
656    carry_over = schema.Bool(
657        title = _(u'Carry-over Course Registration'),
658        default = False,
659        )
660
661    current_academic_session = schema.Choice(
662        title = _(u'Current Academic Session'),
663        description = _(u'Session for which score editing is allowed'),
664        source = academic_sessions_vocab,
665        default = None,
666        required = False,
667        readonly = False,
668        )
669
670    next_matric_integer = schema.Int(
671        title = _(u'Next Matriculation Number Integer'),
672        description = _(u'Integer used for constructing the next '
673                         'matriculation number'),
674        default = 0,
675        readonly = False,
676        required = False,
677        )
678
679class ISessionConfiguration(IKofaObject):
680    """A session configuration object.
681    """
682
683    academic_session = schema.Choice(
684        title = _(u'Academic Session'),
685        source = academic_sessions_vocab,
686        default = None,
687        required = True,
688        readonly = True,
689        )
690
691    application_fee = schema.Float(
692        title = _(u'Application Fee'),
693        default = 0.0,
694        required = False,
695        )
696
697    clearance_fee = schema.Float(
698        title = _(u'Acceptance Fee'),
699        default = 0.0,
700        required = False,
701        )
702
703    booking_fee = schema.Float(
704        title = _(u'Bed Booking Fee'),
705        default = 0.0,
706        required = False,
707        )
708
709    maint_fee = schema.Float(
710        title = _(u'Rent (fallback)'),
711        default = 0.0,
712        required = False,
713        )
714
715    transcript_fee = schema.Float(
716        title = _(u'Transcript Fee'),
717        default = 0.0,
718        required = False,
719        )
720
721    clearance_enabled = schema.Bool(
722        title = _(u'Clearance enabled'),
723        default = False,
724        )
725
726    payment_disabled = schema.List(
727        title = _(u'Payment disabled'),
728        value_type = schema.Choice(
729            source = DisablePaymentGroupSource(),
730            ),
731        required = False,
732        default = [],
733        )
734
735    def getSessionString():
736        """Returns the session string from the vocabulary.
737        """
738
739
740class ISessionConfigurationAdd(ISessionConfiguration):
741    """A session configuration object in add mode.
742    """
743
744    academic_session = schema.Choice(
745        title = _(u'Academic Session'),
746        source = academic_sessions_vocab,
747        default = None,
748        required = True,
749        readonly = False,
750        )
751
752ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
753    'academic_session'].order
754
755class IDataCenter(IKofaObject):
756    """A data center.
757
758    A data center manages files (uploads, downloads, etc.).
759
760    Beside providing the bare paths needed to keep files, it also
761    provides some helpers to put results of batch processing into
762    well-defined final locations (with well-defined filenames).
763
764    The main use-case is managing of site-related files, i.e. files
765    for import, export etc.
766
767    DataCenters are _not_ meant as storages for object-specific files
768    like passport photographs and similar.
769
770    It is up to the datacenter implementation how to organize data
771    (paths) inside its storage path.
772    """
773    storage = schema.Bytes(
774        title = u'Path to directory where everything is kept.'
775        )
776
777    deleted_path = schema.Bytes(
778        title = u'Path were data about deleted objects should be stored.'
779        )
780
781    def getPendingFiles(sort='name'):
782        """Get a list of files stored in `storage` sorted by basename.
783        """
784
785    def getFinishedFiles():
786        """Get a list of files stored in `finished` subfolder of `storage`.
787        """
788
789    def setStoragePath(path, move=False, overwrite=False):
790        """Set the path where to store files.
791
792        If `move` is True, move over files from the current location
793        to the new one.
794
795        If `overwrite` is also True, overwrite any already existing
796        files of same name in target location.
797
798        Triggers a DataCenterStorageMovedEvent.
799        """
800
801    def distProcessedFiles(successful, source_path, finished_file,
802                           pending_file, mode='create', move_orig=True):
803        """Distribute processed files over final locations.
804        """
805
806
807class IDataCenterFile(Interface):
808    """A data center file.
809    """
810
811    name = schema.TextLine(
812        title = u'Filename')
813
814    size = schema.TextLine(
815        title = u'Human readable file size')
816
817    uploaddate = schema.TextLine(
818        title = u'Human readable upload datetime')
819
820    lines = schema.Int(
821        title = u'Number of lines in file')
822
823    def getDate():
824        """Get creation timestamp from file in human readable form.
825        """
826
827    def getSize():
828        """Get human readable size of file.
829        """
830
831    def getLinesNumber():
832        """Get number of lines of file.
833        """
834
835class IDataCenterStorageMovedEvent(IObjectEvent):
836    """Emitted, when the storage of a datacenter changes.
837    """
838
839class IObjectUpgradeEvent(IObjectEvent):
840    """Can be fired, when an object shall be upgraded.
841    """
842
843class ILocalRoleSetEvent(IObjectEvent):
844    """A local role was granted/revoked for a principal on an object.
845    """
846    role_id = Attribute(
847        "The role id that was set.")
848    principal_id = Attribute(
849        "The principal id for which the role was granted/revoked.")
850    granted = Attribute(
851        "Boolean. If false, then the role was revoked.")
852
853class IQueryResultItem(Interface):
854    """An item in a search result.
855    """
856    url = schema.TextLine(
857        title = u'URL that links to the found item')
858    title = schema.TextLine(
859        title = u'Title displayed in search results.')
860    description = schema.Text(
861        title = u'Longer description of the item found.')
862
863class IKofaPluggable(Interface):
864    """A component that might be plugged into a Kofa Kofa app.
865
866    Components implementing this interface are referred to as
867    'plugins'. They are normally called when a new
868    :class:`waeup.kofa.app.University` instance is created.
869
870    Plugins can setup and update parts of the central site without the
871    site object (normally a :class:`waeup.kofa.app.University` object)
872    needing to know about that parts. The site simply collects all
873    available plugins, calls them and the plugins care for their
874    respective subarea like the applicants area or the datacenter
875    area.
876
877    Currently we have no mechanism to define an order of plugins. A
878    plugin should therefore make no assumptions about the state of the
879    site or other plugins being run before and instead do appropriate
880    checks if necessary.
881
882    Updates can be triggered for instance by the respective form in
883    the site configuration. You normally do updates when the
884    underlying software changed.
885    """
886    def setup(site, name, logger):
887        """Create an instance of the plugin.
888
889        The method is meant to be called by the central app (site)
890        when it is created.
891
892        `site`:
893           The site that requests a setup.
894
895        `name`:
896           The name under which the plugin was registered (utility name).
897
898        `logger`:
899           A standard Python logger for the plugins use.
900        """
901
902    def update(site, name, logger):
903        """Method to update an already existing plugin.
904
905        This might be called by a site when something serious
906        changes. It is a poor-man replacement for Zope generations
907        (but probably more comprehensive and better understandable).
908
909        `site`:
910           The site that requests an update.
911
912        `name`:
913           The name under which the plugin was registered (utility name).
914
915        `logger`:
916           A standard Python logger for the plugins use.
917        """
918
919class IAuthPluginUtility(Interface):
920    """A component that cares for authentication setup at site creation.
921
922    Utilities providing this interface are looked up when a Pluggable
923    Authentication Utility (PAU) for any
924    :class:`waeup.kofa.app.University` instance is created and put
925    into ZODB.
926
927    The setup-code then calls the `register` method of the utility and
928    expects a modified (or unmodified) version of the PAU back.
929
930    This allows to define any authentication setup modifications by
931    submodules or third-party modules/packages.
932    """
933
934    def register(pau):
935        """Register any plugins wanted to be in the PAU.
936        """
937
938    def unregister(pau):
939        """Unregister any plugins not wanted to be in the PAU.
940        """
941
942class IObjectConverter(Interface):
943    """Object converters are available as simple adapters, adapting
944       interfaces (not regular instances).
945
946    """
947
948    def fromStringDict(self, data_dict, context, form_fields=None):
949        """Convert values in `data_dict`.
950
951        Converts data in `data_dict` into real values based on
952        `context` and `form_fields`.
953
954        `data_dict` is a mapping (dict) from field names to values
955        represented as strings.
956
957        The fields (keys) to convert can be given in optional
958        `form_fields`. If given, form_fields should be an instance of
959        :class:`zope.formlib.form.Fields`. Suitable instances are for
960        example created by :class:`grok.AutoFields`.
961
962        If no `form_fields` are given, a default is computed from the
963        associated interface.
964
965        The `context` can be an existing object (implementing the
966        associated interface) or a factory name. If it is a string, we
967        try to create an object using
968        :func:`zope.component.createObject`.
969
970        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
971        <DATA_DICT>)`` where
972
973        ``<FIELD_ERRORS>``
974           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
975           error that happened when validating the input data in
976           `data_dict`
977
978        ``<INVARIANT_ERRORS>``
979           is a list of invariant errors concerning several fields
980
981        ``<DATA_DICT>``
982           is a dict with the values from input dict converted.
983
984        If errors happen, i.e. the error lists are not empty, always
985        an empty ``<DATA_DICT>`` is returned.
986
987        If ``<DATA_DICT>` is non-empty, there were no errors.
988        """
989
990class IFieldConverter(Interface):
991    def request_data(name, value, schema_field, prefix='', mode='create'):
992        """Create a dict with key-value mapping as created by a request.
993
994        `name` and `value` are expected to be parsed from CSV or a
995        similar input and represent an attribute to be set to a
996        representation of value.
997
998        `mode` gives the mode of import.
999
1000        :meth:`update_request_data` is then requested to turn this
1001        name and value into vars as they would be sent by a regular
1002        form submit. This means we do not create the real values to be
1003        set but we only define the values that would be sent in a
1004        browser request to request the creation of those values.
1005
1006        The returned dict should contain names and values of a faked
1007        browser request for the given `schema_field`.
1008
1009        Field converters are normally registered as adapters to some
1010        specific zope.schema field.
1011        """
1012
1013class IObjectHistory(Interface):
1014
1015    messages = schema.List(
1016        title = u'List of messages stored',
1017        required = True,
1018        )
1019
1020    def addMessage(message):
1021        """Add a message.
1022        """
1023
1024class IKofaWorkflowInfo(IWorkflowInfo):
1025    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
1026       methods for convenience.
1027    """
1028    def getManualTransitions():
1029        """Get allowed manual transitions.
1030
1031        Get a sorted list of tuples containing the `transition_id` and
1032        `title` of each allowed transition.
1033        """
1034
1035class ISiteLoggers(Interface):
1036
1037    loggers = Attribute("A list or generator of registered KofaLoggers")
1038
1039    def register(name, filename=None, site=None, **options):
1040        """Register a logger `name` which logs to `filename`.
1041
1042        If `filename` is not given, logfile will be `name` with
1043        ``.log`` as filename extension.
1044        """
1045
1046    def unregister(name):
1047        """Unregister a once registered logger.
1048        """
1049
1050class ILogger(Interface):
1051    """A logger cares for setup, update and restarting of a Python logger.
1052    """
1053
1054    logger = Attribute("""A :class:`logging.Logger` instance""")
1055
1056
1057    def __init__(name, filename=None, site=None, **options):
1058        """Create a Kofa logger instance.
1059        """
1060
1061    def setup():
1062        """Create a Python :class:`logging.Logger` instance.
1063
1064        The created logger is based on the params given by constructor.
1065        """
1066
1067    def update(**options):
1068        """Update the logger.
1069
1070        Updates the logger respecting modified `options` and changed
1071        paths.
1072        """
1073
1074class ILoggerCollector(Interface):
1075
1076    def getLoggers(site):
1077        """Return all loggers registered for `site`.
1078        """
1079
1080    def registerLogger(site, logging_component):
1081        """Register a logging component residing in `site`.
1082        """
1083
1084    def unregisterLogger(site, logging_component):
1085        """Unregister a logger.
1086        """
1087
1088#
1089# External File Storage and relatives
1090#
1091class IFileStoreNameChooser(INameChooser):
1092    """See zope.container.interfaces.INameChooser for base methods.
1093    """
1094    def checkName(name, attr=None):
1095        """Check whether an object name is valid.
1096
1097        Raises a user error if the name is not valid.
1098        """
1099
1100    def chooseName(name, attr=None):
1101        """Choose a unique valid file id for the object.
1102
1103        The given name may be taken into account when choosing the
1104        name (file id).
1105
1106        chooseName is expected to always choose a valid file id (that
1107        would pass the checkName test) and never raise an error.
1108
1109        If `attr` is not ``None`` it might been taken into account as
1110        well when generating the file id. Usual behaviour is to
1111        interpret `attr` as a hint for what type of file for a given
1112        context should be stored if there are several types
1113        possible. For instance for a certain student some file could
1114        be the connected passport photograph or some certificate scan
1115        or whatever. Each of them has to be stored in a different
1116        location so setting `attr` to a sensible value should give
1117        different file ids returned.
1118        """
1119
1120class IExtFileStore(IFileRetrieval):
1121    """A file storage that stores files in filesystem (not as blobs).
1122    """
1123    root = schema.TextLine(
1124        title = u'Root path of file store.',
1125        )
1126
1127    def getFile(file_id):
1128        """Get raw file data stored under file with `file_id`.
1129
1130        Returns a file descriptor open for reading or ``None`` if the
1131        file cannot be found.
1132        """
1133
1134    def getFileByContext(context, attr=None):
1135        """Get raw file data stored for the given context.
1136
1137        Returns a file descriptor open for reading or ``None`` if no
1138        such file can be found.
1139
1140        Both, `context` and `attr` might be used to find (`context`)
1141        and feed (`attr`) an appropriate file name chooser.
1142
1143        This is a convenience method.
1144        """
1145
1146    def deleteFile(file_id):
1147        """Delete file stored under `file_id`.
1148
1149        Remove file from filestore so, that it is not available
1150        anymore on next call to getFile for the same file_id.
1151
1152        Should not complain if no such file exists.
1153        """
1154
1155    def deleteFileByContext(context, attr=None):
1156        """Delete file for given `context` and `attr`.
1157
1158        Both, `context` and `attr` might be used to find (`context`)
1159        and feed (`attr`) an appropriate file name chooser.
1160
1161        This is a convenience method.
1162        """
1163
1164    def createFile(filename, f):
1165        """Create file given by f with filename `filename`
1166
1167        Returns a hurry.file.File-based object.
1168        """
1169
1170class IFileStoreHandler(Interface):
1171    """Filestore handlers handle specific files for file stores.
1172
1173    If a file to store/get provides a specific filename, a file store
1174    looks up special handlers for that type of file.
1175
1176    """
1177    def pathFromFileID(store, root, filename):
1178        """Turn file id into path to store.
1179
1180        Returned path should be absolute.
1181        """
1182
1183    def createFile(store, root, filename, file_id, file):
1184        """Return some hurry.file based on `store` and `file_id`.
1185
1186        Some kind of callback method called by file stores to create
1187        file objects from file_id.
1188
1189        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
1190        ``file_like_obj`` should be a HurryFile, a KofaImageFile or
1191        similar. ``raw_file`` is the (maybe changed) input file and
1192        ``path`` the relative internal path to store the file at.
1193
1194        Please make sure the ``raw_file`` is opened for reading and
1195        the file descriptor set at position 0 when returned.
1196
1197        This method also gets the raw input file object that is about
1198        to be stored and is expected to raise any exceptions if some
1199        kind of validation or similar fails.
1200        """
1201
1202class IPDF(Interface):
1203    """A PDF representation of some context.
1204    """
1205
1206    def __call__(view=None, note=None):
1207        """Create a bytestream representing a PDF from context.
1208
1209        If `view` is passed in additional infos might be rendered into
1210        the document.
1211
1212        `note` is optional HTML rendered at bottom of the created
1213        PDF. Please consider the limited reportlab support for HTML,
1214        but using font-tags and friends you certainly can get the
1215        desired look.
1216        """
1217
1218class IMailService(Interface):
1219    """A mail service.
1220    """
1221
1222    def __call__():
1223        """Get the default mail delivery.
1224        """
1225
1226
1227class IDataCenterConfig(Interface):
1228    path = Path(
1229        title = u'Path',
1230        description = u"Directory where the datacenter should store "
1231                      u"files by default (adjustable in web UI).",
1232        required = True,
1233        )
1234
1235#
1236# Asynchronous job handling and related
1237#
1238class IJobManager(IKofaObject):
1239    """A manager for asynchronous running jobs (tasks).
1240    """
1241    def put(job, site=None):
1242        """Put a job into task queue.
1243
1244        If no `site` is given, queue job in context of current local
1245        site.
1246
1247        Returns a job_id to identify the put job. This job_id is
1248        needed for further references to the job.
1249        """
1250
1251    def jobs(site=None):
1252        """Get an iterable of jobs stored.
1253        """
1254
1255    def get(job_id, site=None):
1256        """Get the job with id `job_id`.
1257
1258        For the `site` parameter see :meth:`put`.
1259        """
1260
1261    def remove(job_id, site=None):
1262        """Remove job with `job_id` from stored jobs.
1263        """
1264
1265    def start_test_job(site=None):
1266        """Start a test job.
1267        """
1268
1269class IProgressable(Interface):
1270    """A component that can indicate its progress status.
1271    """
1272    percent = schema.Float(
1273        title = u'Percent of job done already.',
1274        )
1275
1276class IJobContainer(IContainer):
1277    """A job container contains IJob objects.
1278    """
1279
1280class IExportJob(zc.async.interfaces.IJob):
1281    def __init__(site, exporter_name):
1282        pass
1283
1284    finished = schema.Bool(
1285        title = u'`True` if the job finished.`',
1286        default = False,
1287        )
1288
1289    failed = schema.Bool(
1290        title = u"`True` iff the job finished and didn't provide a file.",
1291        default = None,
1292        )
1293
1294class IExportJobContainer(IKofaObject):
1295    """A component that contains (maybe virtually) export jobs.
1296    """
1297    def start_export_job(exporter_name, user_id, *args, **kwargs):
1298        """Start asynchronous export job.
1299
1300        `exporter_name` is the name of an exporter utility to be used.
1301
1302        `user_id` is the ID of the user that triggers the export.
1303
1304        `args` positional arguments passed to the export job created.
1305
1306        `kwargs` keyword arguments passed to the export job.
1307
1308        The job_id is stored along with exporter name and user id in a
1309        persistent list.
1310
1311        Returns the job ID of the job started.
1312        """
1313
1314    def get_running_export_jobs(user_id=None):
1315        """Get export jobs for user with `user_id` as list of tuples.
1316
1317        Each tuples holds ``<job_id>, <exporter_name>, <user_id>`` in
1318        that order. The ``<exporter_name>`` is the utility name of the
1319        used exporter.
1320
1321        If `user_id` is ``None``, all running jobs are returned.
1322        """
1323
1324    def get_export_jobs_status(user_id=None):
1325        """Get running/completed export jobs for `user_id` as list of tuples.
1326
1327        Each tuple holds ``<raw status>, <status translated>,
1328        <exporter title>`` in that order, where ``<status
1329        translated>`` and ``<exporter title>`` are translated strings
1330        representing the status of the job and the human readable
1331        title of the exporter used.
1332        """
1333
1334    def delete_export_entry(entry):
1335        """Delete the export denoted by `entry`.
1336
1337        Removes `entry` from the local `running_exports` list and also
1338        removes the regarding job via the local job manager.
1339
1340        `entry` is a tuple ``(<job id>, <exporter name>, <user id>)``
1341        as created by :meth:`start_export_job` or returned by
1342        :meth:`get_running_export_jobs`.
1343        """
1344
1345    def entry_from_job_id(job_id):
1346        """Get entry tuple for `job_id`.
1347
1348        Returns ``None`` if no such entry can be found.
1349        """
1350
1351class IExportContainerFinder(Interface):
1352    """A finder for the central export container.
1353    """
1354    def __call__():
1355        """Return the currently used global or site-wide IExportContainer.
1356        """
1357
1358class IFilteredQuery(IKofaObject):
1359    """A query for objects.
1360    """
1361
1362    defaults = schema.Dict(
1363        title = u'Default Parameters',
1364        required = True,
1365        )
1366
1367    def __init__(**parameters):
1368        """Instantiate a filtered query by passing in parameters.
1369        """
1370
1371    def query():
1372        """Get an iterable of objects denoted by the set parameters.
1373
1374        The search should be applied to objects inside current
1375        site. It's the caller's duty to set the correct site before.
1376
1377        Result can be any iterable like a catalog result set, a list,
1378        or similar.
1379        """
1380
1381class IFilteredCatalogQuery(IFilteredQuery):
1382    """A catalog-based query for objects.
1383    """
1384
1385    cat_name = schema.TextLine(
1386        title = u'Registered name of the catalog to search.',
1387        required = True,
1388        )
1389
1390    def query_catalog(catalog):
1391        """Query catalog with the parameters passed to constructor.
1392        """
Note: See TracBrowser for help on using the repository browser.