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

Last change on this file since 7470 was 7470, checked in by uli, 13 years ago

First sketches of smart SMTP handling for our portal deploying zope.sendmail. Requires new buildout run. Also check new mail.zcml.

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