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

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

Store html of frontpage as an attribute of the configuration container. This increases the performance of the UniversityPage? by 400%.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 26.3 KB
Line 
1## $Id: interfaces.py 7485 2012-01-16 10:07:19Z 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    frontpage_html = schema.Text(
407        title = u'Content in HTML format',
408        required = False,
409        )
410
411    accommodation_session = schema.Choice(
412        title = u'Accommodation Booking Session',
413        source = academic_sessions_vocab,
414        default = datetime.now().year,
415        required = False,
416        readonly = False,
417        )
418
419    accommodation_states = schema.List(
420        title = u'Allowed States for Accommodation Booking',
421        value_type = schema.Choice(
422            vocabulary = registration_states_vocab,
423            ),
424        default = [],
425        )
426
427    name_admin = schema.TextLine(
428        title = u'Name of Administrator',
429        default = u'Administrator',
430        required = False,
431        )
432
433    email_admin = schema.ASCIILine(
434        title = u'Email Address of Administrator',
435        default = 'contact@waeup.org',
436        required = False,
437        constraint=validate_email,
438        )
439
440    email_subject = schema.TextLine(
441        title = u'Subject of Email to Administrator',
442        default = u'SIRP Contact',
443        required = False,
444        )
445
446    smtp_mailer = schema.Choice(
447        title = u'SMTP mailer to use when sending mail',
448        vocabulary = 'Mail Delivery Names',
449        default = 'No email service',
450        required = True,
451        )
452
453    captcha = schema.Choice(
454        title = u'Captcha used for public registration pages',
455        source = CaptchaSource(),
456        default = u'No captcha',
457        required = True,
458        )
459
460class ISessionConfiguration(ISIRPObject):
461    """A session configuration object.
462    """
463
464    academic_session = schema.Choice(
465        title = u'Academic Session',
466        source = academic_sessions_vocab,
467        default = None,
468        required = True,
469        readonly = True,
470        )
471
472    school_fee_base = schema.Int(
473        title = u'School Fee',
474        default = 0,
475        )
476
477    surcharge_1 = schema.Int(
478        title = u'Surcharge 1',
479        default = 0,
480        )
481
482    surcharge_2 = schema.Int(
483        title = u'Surcharge 2',
484        default = 0,
485        )
486
487    surcharge_3 = schema.Int(
488        title = u'Surcharge 3',
489        default = 0,
490        )
491
492    clearance_fee = schema.Int(
493        title = u'Clearance Fee',
494        default = 0,
495        )
496
497    booking_fee = schema.Int(
498        title = u'Booking Fee',
499        default = 0,
500        )
501
502    acceptance_fee = schema.Int(
503        title = u'Acceptance Fee',
504        default = 0,
505        )
506
507    def getSessionString():
508        """Returns the session string from the vocabulary.
509        """
510
511
512class ISessionConfigurationAdd(ISessionConfiguration):
513    """A session configuration object in add mode.
514    """
515
516    academic_session = schema.Choice(
517        title = u'Academic Session',
518        source = academic_sessions_vocab,
519        default = None,
520        required = True,
521        readonly = False,
522        )
523
524ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
525    'academic_session'].order
526
527class IDataCenter(ISIRPObject):
528    """A data center.
529
530    TODO : declare methods, at least those needed by pages.
531    """
532    pass
533
534class IDataCenterFile(Interface):
535    """A data center file.
536    """
537
538    name = schema.TextLine(
539        title = u'Filename')
540
541    size = schema.TextLine(
542        title = u'Human readable file size')
543
544    uploaddate = schema.TextLine(
545        title = u'Human readable upload datetime')
546
547    lines = schema.Int(
548        title = u'Number of lines in file')
549
550    def getDate():
551        """Get creation timestamp from file in human readable form.
552        """
553
554    def getSize():
555        """Get human readable size of file.
556        """
557
558    def getLinesNumber():
559        """Get number of lines of file.
560        """
561
562class IDataCenterStorageMovedEvent(IObjectEvent):
563    """Emitted, when the storage of a datacenter changes.
564    """
565
566class IObjectUpgradeEvent(IObjectEvent):
567    """Can be fired, when an object shall be upgraded.
568    """
569
570class ILocalRoleSetEvent(IObjectEvent):
571    """A local role was granted/revoked for a principal on an object.
572    """
573    role_id = Attribute(
574        "The role id that was set.")
575    principal_id = Attribute(
576        "The principal id for which the role was granted/revoked.")
577    granted = Attribute(
578        "Boolean. If false, then the role was revoked.")
579
580class IQueryResultItem(Interface):
581    """An item in a search result.
582    """
583    url = schema.TextLine(
584        title = u'URL that links to the found item')
585    title = schema.TextLine(
586        title = u'Title displayed in search results.')
587    description = schema.Text(
588        title = u'Longer description of the item found.')
589
590class ISIRPPluggable(Interface):
591    """A component that might be plugged into a SIRP SIRP app.
592
593    Components implementing this interface are referred to as
594    'plugins'. They are normally called when a new
595    :class:`waeup.sirp.app.University` instance is created.
596
597    Plugins can setup and update parts of the central site without the
598    site object (normally a :class:`waeup.sirp.app.University` object)
599    needing to know about that parts. The site simply collects all
600    available plugins, calls them and the plugins care for their
601    respective subarea like the applicants area or the datacenter
602    area.
603
604    Currently we have no mechanism to define an order of plugins. A
605    plugin should therefore make no assumptions about the state of the
606    site or other plugins being run before and instead do appropriate
607    checks if necessary.
608
609    Updates can be triggered for instance by the respective form in
610    the site configuration. You normally do updates when the
611    underlying software changed.
612    """
613    def setup(site, name, logger):
614        """Create an instance of the plugin.
615
616        The method is meant to be called by the central app (site)
617        when it is created.
618
619        `site`:
620           The site that requests a setup.
621
622        `name`:
623           The name under which the plugin was registered (utility name).
624
625        `logger`:
626           A standard Python logger for the plugins use.
627        """
628
629    def update(site, name, logger):
630        """Method to update an already existing plugin.
631
632        This might be called by a site when something serious
633        changes. It is a poor-man replacement for Zope generations
634        (but probably more comprehensive and better understandable).
635
636        `site`:
637           The site that requests an update.
638
639        `name`:
640           The name under which the plugin was registered (utility name).
641
642        `logger`:
643           A standard Python logger for the plugins use.
644        """
645
646class IAuthPluginUtility(Interface):
647    """A component that cares for authentication setup at site creation.
648
649    Utilities providing this interface are looked up when a Pluggable
650    Authentication Utility (PAU) for any
651    :class:`waeup.sirp.app.University` instance is created and put
652    into ZODB.
653
654    The setup-code then calls the `register` method of the utility and
655    expects a modified (or unmodified) version of the PAU back.
656
657    This allows to define any authentication setup modifications by
658    submodules or third-party modules/packages.
659    """
660
661    def register(pau):
662        """Register any plugins wanted to be in the PAU.
663        """
664
665    def unregister(pau):
666        """Unregister any plugins not wanted to be in the PAU.
667        """
668
669class IObjectConverter(Interface):
670    """Object converters are available as simple adapters, adapting
671       interfaces (not regular instances).
672
673    """
674
675    def fromStringDict(self, data_dict, context, form_fields=None):
676        """Convert values in `data_dict`.
677
678        Converts data in `data_dict` into real values based on
679        `context` and `form_fields`.
680
681        `data_dict` is a mapping (dict) from field names to values
682        represented as strings.
683
684        The fields (keys) to convert can be given in optional
685        `form_fields`. If given, form_fields should be an instance of
686        :class:`zope.formlib.form.Fields`. Suitable instances are for
687        example created by :class:`grok.AutoFields`.
688
689        If no `form_fields` are given, a default is computed from the
690        associated interface.
691
692        The `context` can be an existing object (implementing the
693        associated interface) or a factory name. If it is a string, we
694        try to create an object using
695        :func:`zope.component.createObject`.
696
697        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
698        <DATA_DICT>)`` where
699
700        ``<FIELD_ERRORS>``
701           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
702           error that happened when validating the input data in
703           `data_dict`
704
705        ``<INVARIANT_ERRORS>``
706           is a list of invariant errors concerning several fields
707
708        ``<DATA_DICT>``
709           is a dict with the values from input dict converted.
710
711        If errors happen, i.e. the error lists are not empty, always
712        an empty ``<DATA_DICT>`` is returned.
713
714        If ``<DATA_DICT>` is non-empty, there were no errors.
715        """
716
717class IObjectHistory(Interface):
718
719    messages = schema.List(
720        title = u'List of messages stored',
721        required = True,
722        )
723
724    def addMessage(message):
725        """Add a message.
726        """
727
728class ISIRPWorkflowInfo(IWorkflowInfo):
729    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
730       methods for convenience.
731    """
732    def getManualTransitions():
733        """Get allowed manual transitions.
734
735        Get a sorted list of tuples containing the `transition_id` and
736        `title` of each allowed transition.
737        """
738
739class ISiteLoggers(Interface):
740
741    loggers = Attribute("A list or generator of registered SIRPLoggers")
742
743    def register(name, filename=None, site=None, **options):
744        """Register a logger `name` which logs to `filename`.
745
746        If `filename` is not given, logfile will be `name` with
747        ``.log`` as filename extension.
748        """
749
750    def unregister(name):
751        """Unregister a once registered logger.
752        """
753
754class ILogger(Interface):
755    """A logger cares for setup, update and restarting of a Python logger.
756    """
757
758    logger = Attribute("""A :class:`logging.Logger` instance""")
759
760
761    def __init__(name, filename=None, site=None, **options):
762        """Create a SIRP logger instance.
763        """
764
765    def setup():
766        """Create a Python :class:`logging.Logger` instance.
767
768        The created logger is based on the params given by constructor.
769        """
770
771    def update(**options):
772        """Update the logger.
773
774        Updates the logger respecting modified `options` and changed
775        paths.
776        """
777
778class ILoggerCollector(Interface):
779
780    def getLoggers(site):
781        """Return all loggers registered for `site`.
782        """
783
784    def registerLogger(site, logging_component):
785        """Register a logging component residing in `site`.
786        """
787
788    def unregisterLogger(site, logging_component):
789        """Unregister a logger.
790        """
791
792#
793# External File Storage and relatives
794#
795class IFileStoreNameChooser(INameChooser):
796    """See zope.container.interfaces.INameChooser for base methods.
797    """
798    def checkName(name, attr=None):
799        """Check whether an object name is valid.
800
801        Raises a user error if the name is not valid.
802        """
803
804    def chooseName(name, attr=None):
805        """Choose a unique valid file id for the object.
806
807        The given name may be taken into account when choosing the
808        name (file id).
809
810        chooseName is expected to always choose a valid file id (that
811        would pass the checkName test) and never raise an error.
812
813        If `attr` is not ``None`` it might been taken into account as
814        well when generating the file id. Usual behaviour is to
815        interpret `attr` as a hint for what type of file for a given
816        context should be stored if there are several types
817        possible. For instance for a certain student some file could
818        be the connected passport photograph or some certificate scan
819        or whatever. Each of them has to be stored in a different
820        location so setting `attr` to a sensible value should give
821        different file ids returned.
822        """
823
824class IExtFileStore(IFileRetrieval):
825    """A file storage that stores files in filesystem (not as blobs).
826    """
827    root = schema.TextLine(
828        title = u'Root path of file store.',
829        )
830
831    def getFile(file_id):
832        """Get raw file data stored under file with `file_id`.
833
834        Returns a file descriptor open for reading or ``None`` if the
835        file cannot be found.
836        """
837
838    def getFileByContext(context, attr=None):
839        """Get raw file data stored for the given context.
840
841        Returns a file descriptor open for reading or ``None`` if no
842        such file can be found.
843
844        Both, `context` and `attr` might be used to find (`context`)
845        and feed (`attr`) an appropriate file name chooser.
846
847        This is a convenience method.
848        """
849
850    def deleteFile(file_id):
851        """Delete file stored under `file_id`.
852
853        Remove file from filestore so, that it is not available
854        anymore on next call to getFile for the same file_id.
855
856        Should not complain if no such file exists.
857        """
858
859    def deleteFileByContext(context, attr=None):
860        """Delete file for given `context` and `attr`.
861
862        Both, `context` and `attr` might be used to find (`context`)
863        and feed (`attr`) an appropriate file name chooser.
864
865        This is a convenience method.
866        """
867
868    def createFile(filename, f):
869        """Create file given by f with filename `filename`
870
871        Returns a hurry.file.File-based object.
872        """
873
874class IFileStoreHandler(Interface):
875    """Filestore handlers handle specific files for file stores.
876
877    If a file to store/get provides a specific filename, a file store
878    looks up special handlers for that type of file.
879
880    """
881    def pathFromFileID(store, root, filename):
882        """Turn file id into path to store.
883
884        Returned path should be absolute.
885        """
886
887    def createFile(store, root, filename, file_id, file):
888        """Return some hurry.file based on `store` and `file_id`.
889
890        Some kind of callback method called by file stores to create
891        file objects from file_id.
892
893        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
894        ``file_like_obj`` should be a HurryFile, a SIRPImageFile or
895        similar. ``raw_file`` is the (maybe changed) input file and
896        ``path`` the relative internal path to store the file at.
897
898        Please make sure the ``raw_file`` is opened for reading and
899        the file descriptor set at position 0 when returned.
900
901        This method also gets the raw input file object that is about
902        to be stored and is expected to raise any exceptions if some
903        kind of validation or similar fails.
904        """
905
906class IPDF(Interface):
907    """A PDF representation of some context.
908    """
909
910    def __call__(view=None):
911        """Create a bytestream representing a PDF from context.
912
913        If `view` is passed in additional infos might be rendered into
914        the document.
915        """
916
917class IMailService(Interface):
918    """A mail service.
919    """
920
921    def __call__():
922        """Get the default mail delivery.
923        """
Note: See TracBrowser for help on using the repository browser.