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

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

We do no longer need to configure smtp server credentials through the UI.

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