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

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

Use PasswordValidator? also when adding or editing portal users. This is also a fix because the old UserEditFormPage? did not use the setPassword method but stored passwords as plain text.

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