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

Last change on this file since 11799 was 11799, checked in by uli, 10 years ago

Fix EXAM_GRADES attrib name, add SYSTEM_MAX_LOAD.

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