source: main/waeup.sirp/branches/henrik-bootstrap/src/waeup/sirp/interfaces.py @ 7441

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

Rename sendMail method to be more precise and not confuse with the send_mail function.

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