source: main/waeup.kofa/branches/henrik-regista/src/waeup/ikoba/interfaces.py @ 11952

Last change on this file since 11952 was 11952, checked in by Henrik Bettermann, 10 years ago

More renaming: University -> Institution, Student -> Customer
Change portal title.

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