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

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

Implement PasswordValidator? global utility as suggested by Uli.

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