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

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

Make email address required field of user accounts.

Use user's email adress in contactadminform.pt

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 22.9 KB
Line 
1## $Id: interfaces.py 7222 2011-11-27 07:48:48Z 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    email_admin = schema.ASCIILine(
314        title = u'Email',
315        default = 'contact@waeup.org',
316        required = False,
317        constraint=validate_email,
318        )
319
320    email_subject = schema.TextLine(
321        title = u'Subject of Email to Administrator',
322        default = u'SIRP Contact',
323        required = False,
324        )
325
326    smtp_server = schema.TextLine(
327        title = u'Adress of SMTP Server',
328        default = u'localhost',
329        required = False,
330        )
331
332    smtp_requires_login = schema.Bool(
333        title = u'Server requires login',
334        default = False,
335        )
336
337    smtp_username = schema.TextLine(
338        title = u'Username for SMTP Account',
339        default = None,
340        required = False,
341        )
342
343    smtp_password = schema.Password(
344        title = u'Password for SMTP Account',
345        default = None,
346        required = False,
347        )
348
349
350class ISessionConfiguration(IWAeUPObject):
351    """A session configuration object.
352    """
353
354    academic_session = schema.Choice(
355        title = u'Academic Session',
356        source = academic_sessions_vocab,
357        default = None,
358        required = True,
359        readonly = True,
360        )
361
362    school_fee_base = schema.Int(
363        title = u'School Fee',
364        default = 0,
365        )
366
367    surcharge_1 = schema.Int(
368        title = u'Surcharge 1',
369        default = 0,
370        )
371
372    surcharge_2 = schema.Int(
373        title = u'Surcharge 2',
374        default = 0,
375        )
376
377    surcharge_3 = schema.Int(
378        title = u'Surcharge 3',
379        default = 0,
380        )
381
382    clearance_fee = schema.Int(
383        title = u'Clearance Fee',
384        default = 0,
385        )
386
387    booking_fee = schema.Int(
388        title = u'Booking Fee',
389        default = 0,
390        )
391
392    def getSessionString():
393        """Returns the session string from the vocabulary.
394        """
395
396
397class ISessionConfigurationAdd(ISessionConfiguration):
398    """A session configuration object in add mode.
399    """
400
401    academic_session = schema.Choice(
402        title = u'Academic Session',
403        source = academic_sessions_vocab,
404        default = None,
405        required = True,
406        readonly = False,
407        )
408
409ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
410    'academic_session'].order
411
412class IDataCenter(IWAeUPObject):
413    """A data center.
414
415    TODO : declare methods, at least those needed by pages.
416    """
417    pass
418
419class IDataCenterFile(Interface):
420    """A data center file.
421    """
422
423    name = schema.TextLine(
424        title = u'Filename')
425
426    size = schema.TextLine(
427        title = u'Human readable file size')
428
429    uploaddate = schema.TextLine(
430        title = u'Human readable upload datetime')
431
432    lines = schema.Int(
433        title = u'Number of lines in file')
434
435    def getDate():
436        """Get creation timestamp from file in human readable form.
437        """
438
439    def getSize():
440        """Get human readable size of file.
441        """
442
443    def getLinesNumber():
444        """Get number of lines of file.
445        """
446
447class IDataCenterStorageMovedEvent(IObjectEvent):
448    """Emitted, when the storage of a datacenter changes.
449    """
450
451class IObjectUpgradeEvent(IObjectEvent):
452    """Can be fired, when an object shall be upgraded.
453    """
454
455class ILocalRoleSetEvent(IObjectEvent):
456    """A local role was granted/revoked for a principal on an object.
457    """
458    role_id = Attribute(
459        "The role id that was set.")
460    principal_id = Attribute(
461        "The principal id for which the role was granted/revoked.")
462    granted = Attribute(
463        "Boolean. If false, then the role was revoked.")
464
465class IQueryResultItem(Interface):
466    """An item in a search result.
467    """
468    url = schema.TextLine(
469        title = u'URL that links to the found item')
470    title = schema.TextLine(
471        title = u'Title displayed in search results.')
472    description = schema.Text(
473        title = u'Longer description of the item found.')
474
475class IWAeUPSIRPPluggable(Interface):
476    """A component that might be plugged into a WAeUP SIRP app.
477
478    Components implementing this interface are referred to as
479    'plugins'. They are normally called when a new
480    :class:`waeup.sirp.app.University` instance is created.
481
482    Plugins can setup and update parts of the central site without the
483    site object (normally a :class:`waeup.sirp.app.University` object)
484    needing to know about that parts. The site simply collects all
485    available plugins, calls them and the plugins care for their
486    respective subarea like the applicants area or the datacenter
487    area.
488
489    Currently we have no mechanism to define an order of plugins. A
490    plugin should therefore make no assumptions about the state of the
491    site or other plugins being run before and instead do appropriate
492    checks if necessary.
493
494    Updates can be triggered for instance by the respective form in
495    the site configuration. You normally do updates when the
496    underlying software changed.
497    """
498    def setup(site, name, logger):
499        """Create an instance of the plugin.
500
501        The method is meant to be called by the central app (site)
502        when it is created.
503
504        `site`:
505           The site that requests a setup.
506
507        `name`:
508           The name under which the plugin was registered (utility name).
509
510        `logger`:
511           A standard Python logger for the plugins use.
512        """
513
514    def update(site, name, logger):
515        """Method to update an already existing plugin.
516
517        This might be called by a site when something serious
518        changes. It is a poor-man replacement for Zope generations
519        (but probably more comprehensive and better understandable).
520
521        `site`:
522           The site that requests an update.
523
524        `name`:
525           The name under which the plugin was registered (utility name).
526
527        `logger`:
528           A standard Python logger for the plugins use.
529        """
530
531class IAuthPluginUtility(Interface):
532    """A component that cares for authentication setup at site creation.
533
534    Utilities providing this interface are looked up when a Pluggable
535    Authentication Utility (PAU) for any
536    :class:`waeup.sirp.app.University` instance is created and put
537    into ZODB.
538
539    The setup-code then calls the `register` method of the utility and
540    expects a modified (or unmodified) version of the PAU back.
541
542    This allows to define any authentication setup modifications by
543    submodules or third-party modules/packages.
544    """
545
546    def register(pau):
547        """Register any plugins wanted to be in the PAU.
548        """
549
550    def unregister(pau):
551        """Unregister any plugins not wanted to be in the PAU.
552        """
553
554class IObjectConverter(Interface):
555    """Object converters are available as simple adapters, adapting
556       interfaces (not regular instances).
557
558    """
559
560    def fromStringDict(self, data_dict, context, form_fields=None):
561        """Convert values in `data_dict`.
562
563        Converts data in `data_dict` into real values based on
564        `context` and `form_fields`.
565
566        `data_dict` is a mapping (dict) from field names to values
567        represented as strings.
568
569        The fields (keys) to convert can be given in optional
570        `form_fields`. If given, form_fields should be an instance of
571        :class:`zope.formlib.form.Fields`. Suitable instances are for
572        example created by :class:`grok.AutoFields`.
573
574        If no `form_fields` are given, a default is computed from the
575        associated interface.
576
577        The `context` can be an existing object (implementing the
578        associated interface) or a factory name. If it is a string, we
579        try to create an object using
580        :func:`zope.component.createObject`.
581
582        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
583        <DATA_DICT>)`` where
584
585        ``<FIELD_ERRORS>``
586           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
587           error that happened when validating the input data in
588           `data_dict`
589
590        ``<INVARIANT_ERRORS>``
591           is a list of invariant errors concerning several fields
592
593        ``<DATA_DICT>``
594           is a dict with the values from input dict converted.
595
596        If errors happen, i.e. the error lists are not empty, always
597        an empty ``<DATA_DICT>`` is returned.
598
599        If ``<DATA_DICT>` is non-empty, there were no errors.
600        """
601
602class IObjectHistory(Interface):
603
604    messages = schema.List(
605        title = u'List of messages stored',
606        required = True,
607        )
608
609    def addMessage(message):
610        """Add a message.
611        """
612
613class IWAeUPWorkflowInfo(IWorkflowInfo):
614    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
615       methods for convenience.
616    """
617    def getManualTransitions():
618        """Get allowed manual transitions.
619
620        Get a sorted list of tuples containing the `transition_id` and
621        `title` of each allowed transition.
622        """
623
624class ISiteLoggers(Interface):
625
626    loggers = Attribute("A list or generator of registered WAeUPLoggers")
627
628    def register(name, filename=None, site=None, **options):
629        """Register a logger `name` which logs to `filename`.
630
631        If `filename` is not given, logfile will be `name` with
632        ``.log`` as filename extension.
633        """
634
635    def unregister(name):
636        """Unregister a once registered logger.
637        """
638
639class ILogger(Interface):
640    """A logger cares for setup, update and restarting of a Python logger.
641    """
642
643    logger = Attribute("""A :class:`logging.Logger` instance""")
644
645
646    def __init__(name, filename=None, site=None, **options):
647        """Create a WAeUP logger instance.
648        """
649
650    def setup():
651        """Create a Python :class:`logging.Logger` instance.
652
653        The created logger is based on the params given by constructor.
654        """
655
656    def update(**options):
657        """Update the logger.
658
659        Updates the logger respecting modified `options` and changed
660        paths.
661        """
662
663class ILoggerCollector(Interface):
664
665    def getLoggers(site):
666        """Return all loggers registered for `site`.
667        """
668
669    def registerLogger(site, logging_component):
670        """Register a logging component residing in `site`.
671        """
672
673    def unregisterLogger(site, logging_component):
674        """Unregister a logger.
675        """
676
677#
678# External File Storage and relatives
679#
680class IFileStoreNameChooser(INameChooser):
681    """See zope.container.interfaces.INameChooser for base methods.
682    """
683    def checkName(name, attr=None):
684        """Check whether an object name is valid.
685
686        Raises a user error if the name is not valid.
687        """
688
689    def chooseName(name, attr=None):
690        """Choose a unique valid file id for the object.
691
692        The given name may be taken into account when choosing the
693        name (file id).
694
695        chooseName is expected to always choose a valid file id (that
696        would pass the checkName test) and never raise an error.
697
698        If `attr` is not ``None`` it might been taken into account as
699        well when generating the file id. Usual behaviour is to
700        interpret `attr` as a hint for what type of file for a given
701        context should be stored if there are several types
702        possible. For instance for a certain student some file could
703        be the connected passport photograph or some certificate scan
704        or whatever. Each of them has to be stored in a different
705        location so setting `attr` to a sensible value should give
706        different file ids returned.
707        """
708
709class IExtFileStore(IFileRetrieval):
710    """A file storage that stores files in filesystem (not as blobs).
711    """
712    root = schema.TextLine(
713        title = u'Root path of file store.',
714        )
715
716    def getFile(file_id):
717        """Get raw file data stored under file with `file_id`.
718
719        Returns a file descriptor open for reading or ``None`` if the
720        file cannot be found.
721        """
722
723    def getFileByContext(context, attr=None):
724        """Get raw file data stored for the given context.
725
726        Returns a file descriptor open for reading or ``None`` if no
727        such file can be found.
728
729        Both, `context` and `attr` might be used to find (`context`)
730        and feed (`attr`) an appropriate file name chooser.
731
732        This is a convenience method.
733        """
734
735    def deleteFile(file_id):
736        """Delete file stored under `file_id`.
737
738        Remove file from filestore so, that it is not available
739        anymore on next call to getFile for the same file_id.
740
741        Should not complain if no such file exists.
742        """
743
744    def deleteFileByContext(context, attr=None):
745        """Delete file for given `context` and `attr`.
746
747        Both, `context` and `attr` might be used to find (`context`)
748        and feed (`attr`) an appropriate file name chooser.
749
750        This is a convenience method.
751        """
752
753    def createFile(filename, f):
754        """Create file given by f with filename `filename`
755
756        Returns a hurry.file.File-based object.
757        """
758
759class IFileStoreHandler(Interface):
760    """Filestore handlers handle specific files for file stores.
761
762    If a file to store/get provides a specific filename, a file store
763    looks up special handlers for that type of file.
764
765    """
766    def pathFromFileID(store, root, filename):
767        """Turn file id into path to store.
768
769        Returned path should be absolute.
770        """
771
772    def createFile(store, root, filename, file_id, file):
773        """Return some hurry.file based on `store` and `file_id`.
774
775        Some kind of callback method called by file stores to create
776        file objects from file_id.
777
778        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
779        ``file_like_obj`` should be a HurryFile, a WAeUPImageFile or
780        similar. ``raw_file`` is the (maybe changed) input file and
781        ``path`` the relative internal path to store the file at.
782
783        Please make sure the ``raw_file`` is opened for reading and
784        the file descriptor set at position 0 when returned.
785
786        This method also gets the raw input file object that is about
787        to be stored and is expected to raise any exceptions if some
788        kind of validation or similar fails.
789        """
Note: See TracBrowser for help on using the repository browser.