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

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

Add name of administrator which is displayed on contact form.

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