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

Last change on this file since 7123 was 7090, checked in by uli, 13 years ago

Extend ExtFileStore? interface with methods to remove files from file
store. This way we start to define what we expect from delete methods.

  • Property svn:eol-style set to native
File size: 20.6 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
197
198class IUserContainer(IWAeUPObject):
199    """A container for users (principals).
200
201    These users are used for authentication purposes.
202    """
203
204    def addUser(name, password, title=None, description=None):
205        """Add a user.
206        """
207
208    def delUser(name):
209        """Delete a user if it exists.
210        """
211
212class ILocalRolesAssignable(Interface):
213    """The local roles assignable to an object.
214    """
215    def __call__():
216        """Returns a list of dicts.
217
218        Each dict contains a ``name`` referring to the role assignable
219        for the specified object and a `title` to describe the range
220        of users to which this role can be assigned.
221        """
222
223class IConfigurationContainer(IWAeUPObject):
224    """A container for session configuration objects.
225    """
226
227    name = schema.TextLine(
228        title = u'Name of University',
229        default = u'Sample University',
230        required = True,
231        )
232
233    title = schema.TextLine(
234        title = u'Title of Frontpage',
235        default = u'Welcome to the Student Information and Registration ' +
236                  u'Portal of Sample University',
237        required = False,
238        )
239
240    skin = schema.Choice(
241        title = u'Skin',
242        default = u'gray waeup theme',
243        vocabulary = 'waeup.sirp.browser.theming.ThemesVocabulary',
244        required = True,
245        )
246
247    frontpage = schema.Text(
248        title = u'Content in reST format',
249        required = False,
250        default = default_frontpage,
251        )
252
253    accommodation_session = schema.Choice(
254        title = u'Accommodation Booking Session',
255        source = academic_sessions_vocab,
256        default = datetime.now().year,
257        required = False,
258        readonly = False,
259        )
260
261    accommodation_states = schema.List(
262        title = u'Allowed States for Accommodation Booking',
263        value_type = schema.Choice(
264            vocabulary = registration_states_vocab,
265            ),
266        default = [],
267        )
268
269class ISessionConfiguration(IWAeUPObject):
270    """A session configuration object.
271    """
272
273    academic_session = schema.Choice(
274        title = u'Academic Session',
275        source = academic_sessions_vocab,
276        default = None,
277        required = True,
278        readonly = True,
279        )
280
281    school_fee_base = schema.Int(
282        title = u'School Fee',
283        default = 0,
284        )
285
286    surcharge_1 = schema.Int(
287        title = u'Surcharge 1',
288        default = 0,
289        )
290
291    surcharge_2 = schema.Int(
292        title = u'Surcharge 2',
293        default = 0,
294        )
295
296    surcharge_3 = schema.Int(
297        title = u'Surcharge 3',
298        default = 0,
299        )
300
301    clearance_fee = schema.Int(
302        title = u'Clearance Fee',
303        default = 0,
304        )
305
306    booking_fee = schema.Int(
307        title = u'Booking Fee',
308        default = 0,
309        )
310
311    def getSessionString():
312        """Returns the session string from the vocabulary.
313        """
314
315
316class ISessionConfigurationAdd(ISessionConfiguration):
317    """A session configuration object in add mode.
318    """
319
320    academic_session = schema.Choice(
321        title = u'Academic Session',
322        source = academic_sessions_vocab,
323        default = None,
324        required = True,
325        readonly = False,
326        )
327
328ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
329    'academic_session'].order
330
331class IDataCenter(IWAeUPObject):
332    """A data center.
333
334    TODO : declare methods, at least those needed by pages.
335    """
336    pass
337
338class IDataCenterFile(Interface):
339    """A data center file.
340    """
341
342    name = schema.TextLine(
343        title = u'Filename')
344
345    size = schema.TextLine(
346        title = u'Human readable file size')
347
348    uploaddate = schema.TextLine(
349        title = u'Human readable upload datetime')
350
351    lines = schema.Int(
352        title = u'Number of lines in file')
353
354    def getDate():
355        """Get creation timestamp from file in human readable form.
356        """
357
358    def getSize():
359        """Get human readable size of file.
360        """
361
362    def getLinesNumber():
363        """Get number of lines of file.
364        """
365
366class IDataCenterStorageMovedEvent(IObjectEvent):
367    """Emitted, when the storage of a datacenter changes.
368    """
369
370class IObjectUpgradeEvent(IObjectEvent):
371    """Can be fired, when an object shall be upgraded.
372    """
373
374class ILocalRoleSetEvent(IObjectEvent):
375    """A local role was granted/revoked for a principal on an object.
376    """
377    role_id = Attribute(
378        "The role id that was set.")
379    principal_id = Attribute(
380        "The principal id for which the role was granted/revoked.")
381    granted = Attribute(
382        "Boolean. If false, then the role was revoked.")
383
384class IQueryResultItem(Interface):
385    """An item in a search result.
386    """
387    url = schema.TextLine(
388        title = u'URL that links to the found item')
389    title = schema.TextLine(
390        title = u'Title displayed in search results.')
391    description = schema.Text(
392        title = u'Longer description of the item found.')
393
394class IWAeUPSIRPPluggable(Interface):
395    """A component that might be plugged into a WAeUP SIRP app.
396
397    Components implementing this interface are referred to as
398    'plugins'. They are normally called when a new
399    :class:`waeup.sirp.app.University` instance is created.
400
401    Plugins can setup and update parts of the central site without the
402    site object (normally a :class:`waeup.sirp.app.University` object)
403    needing to know about that parts. The site simply collects all
404    available plugins, calls them and the plugins care for their
405    respective subarea like the applicants area or the datacenter
406    area.
407
408    Currently we have no mechanism to define an order of plugins. A
409    plugin should therefore make no assumptions about the state of the
410    site or other plugins being run before and instead do appropriate
411    checks if necessary.
412
413    Updates can be triggered for instance by the respective form in
414    the site configuration. You normally do updates when the
415    underlying software changed.
416    """
417    def setup(site, name, logger):
418        """Create an instance of the plugin.
419
420        The method is meant to be called by the central app (site)
421        when it is created.
422
423        `site`:
424           The site that requests a setup.
425
426        `name`:
427           The name under which the plugin was registered (utility name).
428
429        `logger`:
430           A standard Python logger for the plugins use.
431        """
432
433    def update(site, name, logger):
434        """Method to update an already existing plugin.
435
436        This might be called by a site when something serious
437        changes. It is a poor-man replacement for Zope generations
438        (but probably more comprehensive and better understandable).
439
440        `site`:
441           The site that requests an update.
442
443        `name`:
444           The name under which the plugin was registered (utility name).
445
446        `logger`:
447           A standard Python logger for the plugins use.
448        """
449
450class IAuthPluginUtility(Interface):
451    """A component that cares for authentication setup at site creation.
452
453    Utilities providing this interface are looked up when a Pluggable
454    Authentication Utility (PAU) for any
455    :class:`waeup.sirp.app.University` instance is created and put
456    into ZODB.
457
458    The setup-code then calls the `register` method of the utility and
459    expects a modified (or unmodified) version of the PAU back.
460
461    This allows to define any authentication setup modifications by
462    submodules or third-party modules/packages.
463    """
464
465    def register(pau):
466        """Register any plugins wanted to be in the PAU.
467        """
468
469    def unregister(pau):
470        """Unregister any plugins not wanted to be in the PAU.
471        """
472
473class IObjectConverter(Interface):
474    """Object converters are available as simple adapters, adapting
475       interfaces (not regular instances).
476
477    """
478
479    def fromStringDict(self, data_dict, context, form_fields=None):
480        """Convert values in `data_dict`.
481
482        Converts data in `data_dict` into real values based on
483        `context` and `form_fields`.
484
485        `data_dict` is a mapping (dict) from field names to values
486        represented as strings.
487
488        The fields (keys) to convert can be given in optional
489        `form_fields`. If given, form_fields should be an instance of
490        :class:`zope.formlib.form.Fields`. Suitable instances are for
491        example created by :class:`grok.AutoFields`.
492
493        If no `form_fields` are given, a default is computed from the
494        associated interface.
495
496        The `context` can be an existing object (implementing the
497        associated interface) or a factory name. If it is a string, we
498        try to create an object using
499        :func:`zope.component.createObject`.
500
501        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
502        <DATA_DICT>)`` where
503
504        ``<FIELD_ERRORS>``
505           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
506           error that happened when validating the input data in
507           `data_dict`
508
509        ``<INVARIANT_ERRORS>``
510           is a list of invariant errors concerning several fields
511
512        ``<DATA_DICT>``
513           is a dict with the values from input dict converted.
514
515        If errors happen, i.e. the error lists are not empty, always
516        an empty ``<DATA_DICT>`` is returned.
517
518        If ``<DATA_DICT>` is non-empty, there were no errors.
519        """
520
521class IObjectHistory(Interface):
522
523    messages = schema.List(
524        title = u'List of messages stored',
525        required = True,
526        )
527
528    def addMessage(message):
529        """Add a message.
530        """
531
532class IWAeUPWorkflowInfo(IWorkflowInfo):
533    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
534       methods for convenience.
535    """
536    def getManualTransitions():
537        """Get allowed manual transitions.
538
539        Get a sorted list of tuples containing the `transition_id` and
540        `title` of each allowed transition.
541        """
542
543class ISiteLoggers(Interface):
544
545    loggers = Attribute("A list or generator of registered WAeUPLoggers")
546
547    def register(name, filename=None, site=None, **options):
548        """Register a logger `name` which logs to `filename`.
549
550        If `filename` is not given, logfile will be `name` with
551        ``.log`` as filename extension.
552        """
553
554    def unregister(name):
555        """Unregister a once registered logger.
556        """
557
558class ILogger(Interface):
559    """A logger cares for setup, update and restarting of a Python logger.
560    """
561
562    logger = Attribute("""A :class:`logging.Logger` instance""")
563
564
565    def __init__(name, filename=None, site=None, **options):
566        """Create a WAeUP logger instance.
567        """
568
569    def setup():
570        """Create a Python :class:`logging.Logger` instance.
571
572        The created logger is based on the params given by constructor.
573        """
574
575    def update(**options):
576        """Update the logger.
577
578        Updates the logger respecting modified `options` and changed
579        paths.
580        """
581
582class ILoggerCollector(Interface):
583
584    def getLoggers(site):
585        """Return all loggers registered for `site`.
586        """
587
588    def registerLogger(site, logging_component):
589        """Register a logging component residing in `site`.
590        """
591
592    def unregisterLogger(site, logging_component):
593        """Unregister a logger.
594        """
595
596#
597# External File Storage and relatives
598#
599class IFileStoreNameChooser(INameChooser):
600    """See zope.container.interfaces.INameChooser for base methods.
601    """
602    def checkName(name, attr=None):
603        """Check whether an object name is valid.
604
605        Raises a user error if the name is not valid.
606        """
607
608    def chooseName(name, attr=None):
609        """Choose a unique valid file id for the object.
610
611        The given name may be taken into account when choosing the
612        name (file id).
613
614        chooseName is expected to always choose a valid file id (that
615        would pass the checkName test) and never raise an error.
616
617        If `attr` is not ``None`` it might been taken into account as
618        well when generating the file id. Usual behaviour is to
619        interpret `attr` as a hint for what type of file for a given
620        context should be stored if there are several types
621        possible. For instance for a certain student some file could
622        be the connected passport photograph or some certificate scan
623        or whatever. Each of them has to be stored in a different
624        location so setting `attr` to a sensible value should give
625        different file ids returned.
626        """
627
628class IExtFileStore(IFileRetrieval):
629    """A file storage that stores files in filesystem (not as blobs).
630    """
631    root = schema.TextLine(
632        title = u'Root path of file store.',
633        )
634
635    def getFile(file_id):
636        """Get raw file data stored under file with `file_id`.
637
638        Returns a file descriptor open for reading or ``None`` if the
639        file cannot be found.
640        """
641
642    def getFileByContext(context, attr=None):
643        """Get raw file data stored for the given context.
644
645        Returns a file descriptor open for reading or ``None`` if no
646        such file can be found.
647
648        Both, `context` and `attr` might be used to find (`context`)
649        and feed (`attr`) an appropriate file name chooser.
650
651        This is a convenience method.
652        """
653
654    def deleteFile(file_id):
655        """Delete file stored under `file_id`.
656
657        Remove file from filestore so, that it is not available
658        anymore on next call to getFile for the same file_id.
659
660        Should not complain if no such file exists.
661        """
662
663    def deleteFileByContext(context, attr=None):
664        """Delete file for given `context` and `attr`.
665
666        Both, `context` and `attr` might be used to find (`context`)
667        and feed (`attr`) an appropriate file name chooser.
668
669        This is a convenience method.
670        """
671
672    def createFile(filename, f):
673        """Create file given by f with filename `filename`
674
675        Returns a hurry.file.File-based object.
676        """
677
678class IFileStoreHandler(Interface):
679    """Filestore handlers handle specific files for file stores.
680
681    If a file to store/get provides a specific filename, a file store
682    looks up special handlers for that type of file.
683
684    """
685    def pathFromFileID(store, root, filename):
686        """Turn file id into path to store.
687
688        Returned path should be absolute.
689        """
690
691    def createFile(store, root, filename, file_id, file):
692        """Return some hurry.file based on `store` and `file_id`.
693
694        Some kind of callback method called by file stores to create
695        file objects from file_id.
696
697        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
698        ``file_like_obj`` should be a HurryFile, a WAeUPImageFile or
699        similar. ``raw_file`` is the (maybe changed) input file and
700        ``path`` the relative internal path to store the file at.
701
702        Please make sure the ``raw_file`` is opened for reading and
703        the file descriptor set at position 0 when returned.
704
705        This method also gets the raw input file object that is about
706        to be stored and is expected to raise any exceptions if some
707        kind of validation or similar fails.
708        """
Note: See TracBrowser for help on using the repository browser.