source: main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py @ 7193

Last change on this file since 7193 was 7193, checked in by Henrik Bettermann, 13 years ago

More copyright adjustments.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 21.5 KB
Line 
1## $Id: interfaces.py 7193 2011-11-25 07:21:29Z 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
19from datetime import datetime
20from hurry.file.interfaces import IFileRetrieval
21from hurry.workflow.interfaces import IWorkflow, IWorkflowInfo
22from zc.sourcefactory.basic import BasicSourceFactory
23from zope import schema
24from zope.component import getUtility
25from zope.component.interfaces import IObjectEvent
26from zope.container.interfaces import INameChooser
27from zope.interface import Interface, Attribute, implements
28from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
29
30CREATED = 'created'
31ADMITTED = 'admitted'
32CLEARANCE = 'clearance started'
33REQUESTED = 'clearance requested'
34CLEARED = 'cleared'
35PAID = 'school fee paid'
36RETURNING = 'returning'
37REGISTERED = 'courses registered'
38VALIDATED = 'courses validated'
39
40default_frontpage = u'' + open(os.path.join(
41        os.path.dirname(__file__), 'frontpage.rst'), 'rb').read()
42
43def SimpleWAeUPVocabulary(*terms):
44    """A well-buildt vocabulary provides terms with a value, token and
45       title for each term
46    """
47    return SimpleVocabulary([
48            SimpleTerm(value, value, title) for title, value in terms])
49
50def year_range():
51    curr_year = datetime.now().year
52    return range(curr_year - 2, curr_year + 5)
53
54def academic_sessions():
55    curr_year = datetime.now().year
56    year_range = range(curr_year - 10, curr_year + 2)
57    return [('%s/%s' % (year,year+1), year) for year in year_range]
58
59academic_sessions_vocab = SimpleWAeUPVocabulary(*academic_sessions())
60
61registration_states_vocab = SimpleWAeUPVocabulary(
62    ('created', CREATED),
63    ('admitted', ADMITTED),
64    ('clearance started', CLEARANCE),
65    ('clearance requested', REQUESTED),
66    ('cleared', CLEARED),
67    ('school fee paid', PAID),
68    ('returning', RETURNING),
69    ('courses registered', REGISTERED),
70    ('courses validated', VALIDATED),
71    )
72
73class FatalCSVError(Exception):
74    """Some row could not be processed.
75    """
76    pass
77
78class DuplicationError(Exception):
79    """An exception that can be raised when duplicates are found.
80
81    When raising :exc:`DuplicationError` you can, beside the usual
82    message, specify a list of objects which are duplicates. These
83    values can be used by catching code to print something helpful or
84    similar.
85    """
86    def __init__(self, msg, entries=[]):
87        self.msg = msg
88        self.entries = entries
89
90    def __str__(self):
91        return '%r' % self.msg
92
93class RoleSource(BasicSourceFactory):
94    """A source for site roles.
95    """
96    def getValues(self):
97        # late import: in interfaces we should not import local modules
98        from waeup.sirp.permissions import get_waeup_role_names
99        return get_waeup_role_names()
100
101    def getTitle(self, value):
102        # late import: in interfaces we should not import local modules
103        from waeup.sirp.permissions import get_all_roles
104        roles = dict(get_all_roles())
105        if value in roles.keys():
106            title = roles[value].title
107            if '.' in title:
108                title = title.split('.', 2)[1]
109        return title
110
111class IWAeUPObject(Interface):
112    """A WAeUP object.
113
114    This is merely a marker interface.
115    """
116
117class IUniversity(IWAeUPObject):
118    """Representation of a university.
119    """
120
121
122class IWAeUPContainer(IWAeUPObject):
123    """A container for WAeUP objects.
124    """
125
126class IWAeUPContained(IWAeUPObject):
127    """An item contained in an IWAeUPContainer.
128    """
129
130class IWAeUPExporter(Interface):
131    """An exporter for objects.
132    """
133    def export(obj, filepath=None):
134        """Export by pickling.
135
136        Returns a file-like object containing a representation of `obj`.
137
138        This is done using `pickle`. If `filepath` is ``None``, a
139        `cStringIO` object is returned, that contains the saved data.
140        """
141
142class IWAeUPXMLExporter(Interface):
143    """An XML exporter for objects.
144    """
145    def export(obj, filepath=None):
146        """Export as XML.
147
148        Returns an XML representation of `obj`.
149
150        If `filepath` is ``None``, a StringIO` object is returned,
151        that contains the transformed data.
152        """
153
154class IWAeUPXMLImporter(Interface):
155    """An XML import for objects.
156    """
157    def doImport(filepath):
158        """Create Python object from XML.
159
160        Returns a Python object.
161        """
162
163class IBatchProcessor(Interface):
164    """A batch processor that handles mass-operations.
165    """
166    name = schema.TextLine(
167        title = u'Importer name'
168        )
169
170    mode = schema.Choice(
171        title = u'Import mode',
172        values = ['create', 'update', 'remove']
173        )
174
175    def doImport(path, headerfields, mode='create', user='Unknown',
176                 logger=None):
177        """Read data from ``path`` and update connected object.
178
179        `headerfields` is a list of headerfields as read from the file
180        to import.
181
182        `mode` gives the import mode to use (``'create'``,
183        ``'update'``, or ``'remove'``.
184
185        `user` is a string describing the user performing the
186        import. Normally fetched from current principal.
187
188        `logger` is the logger to use during import.
189        """
190
191class IUserAccount(IWAeUPObject):
192    """A user account.
193    """
194    name = schema.TextLine(
195        title = u'User ID',
196        description = u'Login name of user',
197        required = True,)
198    title = schema.TextLine(
199        title = u'Name',
200        description = u'Real name of user',
201        required = False,)
202    description = schema.TextLine(
203        title = u'Description',
204        required = False,)
205    roles = schema.List(
206        title = u'Portal roles',
207        value_type = schema.Choice(source=RoleSource()))
208
209class IPasswordValidator(Interface):
210    """A password validator utility.
211    """
212
213    def validate_password(password, password_repeat):
214        """Validates a password by comparing it with
215        control password and checking some other requirements.
216        """
217
218
219class IUsersContainer(IWAeUPObject):
220    """A container for users (principals).
221
222    These users are used for authentication purposes.
223    """
224
225    def addUser(name, password, title=None, description=None):
226        """Add a user.
227        """
228
229    def delUser(name):
230        """Delete a user if it exists.
231        """
232
233class ILocalRolesAssignable(Interface):
234    """The local roles assignable to an object.
235    """
236    def __call__():
237        """Returns a list of dicts.
238
239        Each dict contains a ``name`` referring to the role assignable
240        for the specified object and a `title` to describe the range
241        of users to which this role can be assigned.
242        """
243
244class IConfigurationContainer(IWAeUPObject):
245    """A container for session configuration objects.
246    """
247
248    name = schema.TextLine(
249        title = u'Name of University',
250        default = u'Sample University',
251        required = True,
252        )
253
254    title = schema.TextLine(
255        title = u'Title of Frontpage',
256        default = u'Welcome to the Student Information and Registration ' +
257                  u'Portal of Sample University',
258        required = False,
259        )
260
261    skin = schema.Choice(
262        title = u'Skin',
263        default = u'gray waeup theme',
264        vocabulary = 'waeup.sirp.browser.theming.ThemesVocabulary',
265        required = True,
266        )
267
268    frontpage = schema.Text(
269        title = u'Content in reST format',
270        required = False,
271        default = default_frontpage,
272        )
273
274    accommodation_session = schema.Choice(
275        title = u'Accommodation Booking Session',
276        source = academic_sessions_vocab,
277        default = datetime.now().year,
278        required = False,
279        readonly = False,
280        )
281
282    accommodation_states = schema.List(
283        title = u'Allowed States for Accommodation Booking',
284        value_type = schema.Choice(
285            vocabulary = registration_states_vocab,
286            ),
287        default = [],
288        )
289
290class ISessionConfiguration(IWAeUPObject):
291    """A session configuration object.
292    """
293
294    academic_session = schema.Choice(
295        title = u'Academic Session',
296        source = academic_sessions_vocab,
297        default = None,
298        required = True,
299        readonly = True,
300        )
301
302    school_fee_base = schema.Int(
303        title = u'School Fee',
304        default = 0,
305        )
306
307    surcharge_1 = schema.Int(
308        title = u'Surcharge 1',
309        default = 0,
310        )
311
312    surcharge_2 = schema.Int(
313        title = u'Surcharge 2',
314        default = 0,
315        )
316
317    surcharge_3 = schema.Int(
318        title = u'Surcharge 3',
319        default = 0,
320        )
321
322    clearance_fee = schema.Int(
323        title = u'Clearance Fee',
324        default = 0,
325        )
326
327    booking_fee = schema.Int(
328        title = u'Booking Fee',
329        default = 0,
330        )
331
332    def getSessionString():
333        """Returns the session string from the vocabulary.
334        """
335
336
337class ISessionConfigurationAdd(ISessionConfiguration):
338    """A session configuration object in add mode.
339    """
340
341    academic_session = schema.Choice(
342        title = u'Academic Session',
343        source = academic_sessions_vocab,
344        default = None,
345        required = True,
346        readonly = False,
347        )
348
349ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
350    'academic_session'].order
351
352class IDataCenter(IWAeUPObject):
353    """A data center.
354
355    TODO : declare methods, at least those needed by pages.
356    """
357    pass
358
359class IDataCenterFile(Interface):
360    """A data center file.
361    """
362
363    name = schema.TextLine(
364        title = u'Filename')
365
366    size = schema.TextLine(
367        title = u'Human readable file size')
368
369    uploaddate = schema.TextLine(
370        title = u'Human readable upload datetime')
371
372    lines = schema.Int(
373        title = u'Number of lines in file')
374
375    def getDate():
376        """Get creation timestamp from file in human readable form.
377        """
378
379    def getSize():
380        """Get human readable size of file.
381        """
382
383    def getLinesNumber():
384        """Get number of lines of file.
385        """
386
387class IDataCenterStorageMovedEvent(IObjectEvent):
388    """Emitted, when the storage of a datacenter changes.
389    """
390
391class IObjectUpgradeEvent(IObjectEvent):
392    """Can be fired, when an object shall be upgraded.
393    """
394
395class ILocalRoleSetEvent(IObjectEvent):
396    """A local role was granted/revoked for a principal on an object.
397    """
398    role_id = Attribute(
399        "The role id that was set.")
400    principal_id = Attribute(
401        "The principal id for which the role was granted/revoked.")
402    granted = Attribute(
403        "Boolean. If false, then the role was revoked.")
404
405class IQueryResultItem(Interface):
406    """An item in a search result.
407    """
408    url = schema.TextLine(
409        title = u'URL that links to the found item')
410    title = schema.TextLine(
411        title = u'Title displayed in search results.')
412    description = schema.Text(
413        title = u'Longer description of the item found.')
414
415class IWAeUPSIRPPluggable(Interface):
416    """A component that might be plugged into a WAeUP SIRP app.
417
418    Components implementing this interface are referred to as
419    'plugins'. They are normally called when a new
420    :class:`waeup.sirp.app.University` instance is created.
421
422    Plugins can setup and update parts of the central site without the
423    site object (normally a :class:`waeup.sirp.app.University` object)
424    needing to know about that parts. The site simply collects all
425    available plugins, calls them and the plugins care for their
426    respective subarea like the applicants area or the datacenter
427    area.
428
429    Currently we have no mechanism to define an order of plugins. A
430    plugin should therefore make no assumptions about the state of the
431    site or other plugins being run before and instead do appropriate
432    checks if necessary.
433
434    Updates can be triggered for instance by the respective form in
435    the site configuration. You normally do updates when the
436    underlying software changed.
437    """
438    def setup(site, name, logger):
439        """Create an instance of the plugin.
440
441        The method is meant to be called by the central app (site)
442        when it is created.
443
444        `site`:
445           The site that requests a setup.
446
447        `name`:
448           The name under which the plugin was registered (utility name).
449
450        `logger`:
451           A standard Python logger for the plugins use.
452        """
453
454    def update(site, name, logger):
455        """Method to update an already existing plugin.
456
457        This might be called by a site when something serious
458        changes. It is a poor-man replacement for Zope generations
459        (but probably more comprehensive and better understandable).
460
461        `site`:
462           The site that requests an update.
463
464        `name`:
465           The name under which the plugin was registered (utility name).
466
467        `logger`:
468           A standard Python logger for the plugins use.
469        """
470
471class IAuthPluginUtility(Interface):
472    """A component that cares for authentication setup at site creation.
473
474    Utilities providing this interface are looked up when a Pluggable
475    Authentication Utility (PAU) for any
476    :class:`waeup.sirp.app.University` instance is created and put
477    into ZODB.
478
479    The setup-code then calls the `register` method of the utility and
480    expects a modified (or unmodified) version of the PAU back.
481
482    This allows to define any authentication setup modifications by
483    submodules or third-party modules/packages.
484    """
485
486    def register(pau):
487        """Register any plugins wanted to be in the PAU.
488        """
489
490    def unregister(pau):
491        """Unregister any plugins not wanted to be in the PAU.
492        """
493
494class IObjectConverter(Interface):
495    """Object converters are available as simple adapters, adapting
496       interfaces (not regular instances).
497
498    """
499
500    def fromStringDict(self, data_dict, context, form_fields=None):
501        """Convert values in `data_dict`.
502
503        Converts data in `data_dict` into real values based on
504        `context` and `form_fields`.
505
506        `data_dict` is a mapping (dict) from field names to values
507        represented as strings.
508
509        The fields (keys) to convert can be given in optional
510        `form_fields`. If given, form_fields should be an instance of
511        :class:`zope.formlib.form.Fields`. Suitable instances are for
512        example created by :class:`grok.AutoFields`.
513
514        If no `form_fields` are given, a default is computed from the
515        associated interface.
516
517        The `context` can be an existing object (implementing the
518        associated interface) or a factory name. If it is a string, we
519        try to create an object using
520        :func:`zope.component.createObject`.
521
522        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
523        <DATA_DICT>)`` where
524
525        ``<FIELD_ERRORS>``
526           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
527           error that happened when validating the input data in
528           `data_dict`
529
530        ``<INVARIANT_ERRORS>``
531           is a list of invariant errors concerning several fields
532
533        ``<DATA_DICT>``
534           is a dict with the values from input dict converted.
535
536        If errors happen, i.e. the error lists are not empty, always
537        an empty ``<DATA_DICT>`` is returned.
538
539        If ``<DATA_DICT>` is non-empty, there were no errors.
540        """
541
542class IObjectHistory(Interface):
543
544    messages = schema.List(
545        title = u'List of messages stored',
546        required = True,
547        )
548
549    def addMessage(message):
550        """Add a message.
551        """
552
553class IWAeUPWorkflowInfo(IWorkflowInfo):
554    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
555       methods for convenience.
556    """
557    def getManualTransitions():
558        """Get allowed manual transitions.
559
560        Get a sorted list of tuples containing the `transition_id` and
561        `title` of each allowed transition.
562        """
563
564class ISiteLoggers(Interface):
565
566    loggers = Attribute("A list or generator of registered WAeUPLoggers")
567
568    def register(name, filename=None, site=None, **options):
569        """Register a logger `name` which logs to `filename`.
570
571        If `filename` is not given, logfile will be `name` with
572        ``.log`` as filename extension.
573        """
574
575    def unregister(name):
576        """Unregister a once registered logger.
577        """
578
579class ILogger(Interface):
580    """A logger cares for setup, update and restarting of a Python logger.
581    """
582
583    logger = Attribute("""A :class:`logging.Logger` instance""")
584
585
586    def __init__(name, filename=None, site=None, **options):
587        """Create a WAeUP logger instance.
588        """
589
590    def setup():
591        """Create a Python :class:`logging.Logger` instance.
592
593        The created logger is based on the params given by constructor.
594        """
595
596    def update(**options):
597        """Update the logger.
598
599        Updates the logger respecting modified `options` and changed
600        paths.
601        """
602
603class ILoggerCollector(Interface):
604
605    def getLoggers(site):
606        """Return all loggers registered for `site`.
607        """
608
609    def registerLogger(site, logging_component):
610        """Register a logging component residing in `site`.
611        """
612
613    def unregisterLogger(site, logging_component):
614        """Unregister a logger.
615        """
616
617#
618# External File Storage and relatives
619#
620class IFileStoreNameChooser(INameChooser):
621    """See zope.container.interfaces.INameChooser for base methods.
622    """
623    def checkName(name, attr=None):
624        """Check whether an object name is valid.
625
626        Raises a user error if the name is not valid.
627        """
628
629    def chooseName(name, attr=None):
630        """Choose a unique valid file id for the object.
631
632        The given name may be taken into account when choosing the
633        name (file id).
634
635        chooseName is expected to always choose a valid file id (that
636        would pass the checkName test) and never raise an error.
637
638        If `attr` is not ``None`` it might been taken into account as
639        well when generating the file id. Usual behaviour is to
640        interpret `attr` as a hint for what type of file for a given
641        context should be stored if there are several types
642        possible. For instance for a certain student some file could
643        be the connected passport photograph or some certificate scan
644        or whatever. Each of them has to be stored in a different
645        location so setting `attr` to a sensible value should give
646        different file ids returned.
647        """
648
649class IExtFileStore(IFileRetrieval):
650    """A file storage that stores files in filesystem (not as blobs).
651    """
652    root = schema.TextLine(
653        title = u'Root path of file store.',
654        )
655
656    def getFile(file_id):
657        """Get raw file data stored under file with `file_id`.
658
659        Returns a file descriptor open for reading or ``None`` if the
660        file cannot be found.
661        """
662
663    def getFileByContext(context, attr=None):
664        """Get raw file data stored for the given context.
665
666        Returns a file descriptor open for reading or ``None`` if no
667        such file can be found.
668
669        Both, `context` and `attr` might be used to find (`context`)
670        and feed (`attr`) an appropriate file name chooser.
671
672        This is a convenience method.
673        """
674
675    def deleteFile(file_id):
676        """Delete file stored under `file_id`.
677
678        Remove file from filestore so, that it is not available
679        anymore on next call to getFile for the same file_id.
680
681        Should not complain if no such file exists.
682        """
683
684    def deleteFileByContext(context, attr=None):
685        """Delete file for given `context` and `attr`.
686
687        Both, `context` and `attr` might be used to find (`context`)
688        and feed (`attr`) an appropriate file name chooser.
689
690        This is a convenience method.
691        """
692
693    def createFile(filename, f):
694        """Create file given by f with filename `filename`
695
696        Returns a hurry.file.File-based object.
697        """
698
699class IFileStoreHandler(Interface):
700    """Filestore handlers handle specific files for file stores.
701
702    If a file to store/get provides a specific filename, a file store
703    looks up special handlers for that type of file.
704
705    """
706    def pathFromFileID(store, root, filename):
707        """Turn file id into path to store.
708
709        Returned path should be absolute.
710        """
711
712    def createFile(store, root, filename, file_id, file):
713        """Return some hurry.file based on `store` and `file_id`.
714
715        Some kind of callback method called by file stores to create
716        file objects from file_id.
717
718        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
719        ``file_like_obj`` should be a HurryFile, a WAeUPImageFile or
720        similar. ``raw_file`` is the (maybe changed) input file and
721        ``path`` the relative internal path to store the file at.
722
723        Please make sure the ``raw_file`` is opened for reading and
724        the file descriptor set at position 0 when returned.
725
726        This method also gets the raw input file object that is about
727        to be stored and is expected to raise any exceptions if some
728        kind of validation or similar fails.
729        """
Note: See TracBrowser for help on using the repository browser.