source: main/waeup.ikoba/trunk/src/waeup/ikoba/interfaces.py @ 12283

Last change on this file since 12283 was 12260, checked in by Henrik Bettermann, 10 years ago

Validate document and contract ids properly.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 38.6 KB
Line 
1## $Id: interfaces.py 12260 2014-12-19 08:11:12Z 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
20import codecs
21import zc.async.interfaces
22import zope.i18nmessageid
23from datetime import datetime
24from hurry.file.interfaces import IFileRetrieval
25from hurry.workflow.interfaces import IWorkflowInfo
26from zc.sourcefactory.basic import BasicSourceFactory
27from zope import schema
28from zope.pluggableauth.interfaces import IPrincipalInfo
29from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
30from zope.component import getUtility
31from zope.component.interfaces import IObjectEvent
32from zope.configuration.fields import Path
33from zope.container.interfaces import INameChooser, IContainer
34from zope.interface import Interface, Attribute
35from zope.schema.interfaces import IObject
36from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
37from waeup.ikoba.schema import PhoneNumber
38from waeup.ikoba.sourcefactory import SmartBasicContextualSourceFactory
39
40_ = MessageFactory = zope.i18nmessageid.MessageFactory('waeup.ikoba')
41
42DELETION_MARKER = 'XXX'
43IGNORE_MARKER = '<IGNORE>'
44WAEUP_KEY = 'waeup.ikoba'
45VIRT_JOBS_CONTAINER_NAME = 'jobs'
46
47# States for various workflows
48
49CREATED = 'created'
50
51STARTED = 'started'
52REQUESTED = 'requested'
53APPROVED = 'approved'
54
55SUBMITTED = 'submitted'
56VERIFIED = 'verified'
57REJECTED = 'rejected'
58EXPIRED = 'expired'
59PUBLISHED = 'published'
60
61
62#: A dict giving job status as tuple (<STRING>, <TRANSLATED_STRING>),
63#: the latter for UI purposes.
64JOB_STATUS_MAP = {
65    zc.async.interfaces.NEW: ('new', _('new')),
66    zc.async.interfaces.COMPLETED: ('completed', _('completed')),
67    zc.async.interfaces.PENDING: ('pending', _('pending')),
68    zc.async.interfaces.ACTIVE: ('active', _('active')),
69    zc.async.interfaces.ASSIGNED: ('assigned', _('assigned')),
70    zc.async.interfaces.CALLBACKS: ('callbacks', _('callbacks')),
71    }
72
73#default_rest_frontpage = u'' + codecs.open(os.path.join(
74#        os.path.dirname(__file__), 'frontpage.rst'),
75#        encoding='utf-8', mode='rb').read()
76
77default_html_frontpage = u'' + codecs.open(os.path.join(
78        os.path.dirname(__file__), 'frontpage.html'),
79        encoding='utf-8', mode='rb').read()
80
81def SimpleIkobaVocabulary(*terms):
82    """A well-buildt vocabulary provides terms with a value, token and
83       title for each term
84    """
85    return SimpleVocabulary([
86            SimpleTerm(value, value, title) for title, value in terms])
87
88
89class ContextualDictSourceFactoryBase(SmartBasicContextualSourceFactory):
90    """A base for contextual sources based on IkobaUtils dicts.
91
92    To create a real source, you have to set the `DICT_NAME` attribute
93    which should be the name of a dictionary in IkobaUtils.
94    """
95    def getValues(self, context):
96        utils = getUtility(IIkobaUtils)
97        sorted_items = sorted(getattr(utils, self.DICT_NAME).items(),
98                              key=lambda item: item[1])
99        return [item[0] for item in sorted_items]
100
101    def getToken(self, context, value):
102        return str(value)
103
104    def getTitle(self, context, value):
105        utils = getUtility(IIkobaUtils)
106        return getattr(utils, self.DICT_NAME)[value]
107
108class SubjectSource(BasicSourceFactory):
109    """A source for school subjects used in exam documentation.
110    """
111    def getValues(self):
112        subjects_dict = getUtility(IIkobaUtils).EXAM_SUBJECTS_DICT
113        return sorted(subjects_dict.keys())
114
115    def getTitle(self, value):
116        subjects_dict = getUtility(IIkobaUtils).EXAM_SUBJECTS_DICT
117        return "%s:" % subjects_dict[value]
118
119class GradeSource(BasicSourceFactory):
120    """A source for exam grades.
121    """
122    def getValues(self):
123        for entry in getUtility(IIkobaUtils).EXAM_GRADES:
124            yield entry[0]
125
126    def getTitle(self, value):
127        return dict(getUtility(IIkobaUtils).EXAM_GRADES)[value]
128
129# Define a validation method for email addresses
130class NotAnEmailAddress(schema.ValidationError):
131    __doc__ = u"Invalid email address"
132
133#: Regular expression to check email-address formats. As these can
134#: become rather complex (nearly everything is allowed by RFCs), we only
135#: forbid whitespaces, commas and dots following onto each other.
136check_email = re.compile(
137    r"^[^@\s,]+@[^@\.\s,]+(\.[^@\.\s,]+)*$").match
138
139def validate_email(value):
140    if not check_email(value):
141        raise NotAnEmailAddress(value)
142    return True
143
144# Define a validation method for email addresses
145class NotIdValue(schema.ValidationError):
146    __doc__ = u"Invalid id"
147
148#: Regular expressions to check id formats.
149check_id = re.compile(r"^[a-zA-Z0-9_-]{2,6}$").match
150check_uuid = re.compile( r"^[a-zA-Z0-9]{32}$").match
151
152def validate_id(value):
153    if not check_id(value):
154        raise NotIdValue(value)
155    return True
156
157def validate_uuid(value):
158    # We are using also short ids in tests
159    if not check_uuid(value) and not check_id(value):
160        raise NotIdValue(value)
161    return True
162
163# Define a validation method for international phone numbers
164class InvalidPhoneNumber(schema.ValidationError):
165    __doc__ = u"Invalid phone number"
166
167# represent format +NNN-NNNN-NNNN
168RE_INT_PHONE = re.compile(r"^\+?\d+\-\d+\-[\d\-]+$")
169
170def validate_phone(value):
171    if not RE_INT_PHONE.match(value):
172        raise InvalidPhoneNumber(value)
173    return True
174
175class FatalCSVError(Exception):
176    """Some row could not be processed.
177    """
178    pass
179
180class DuplicationError(Exception):
181    """An exception that can be raised when duplicates are found.
182
183    When raising :exc:`DuplicationError` you can, beside the usual
184    message, specify a list of objects which are duplicates. These
185    values can be used by catching code to print something helpful or
186    similar.
187    """
188    def __init__(self, msg, entries=[]):
189        self.msg = msg
190        self.entries = entries
191
192    def __str__(self):
193        return '%r' % self.msg
194
195class RoleSource(BasicSourceFactory):
196    """A source for site roles.
197    """
198    def getValues(self):
199        # late import: in interfaces we should not import local modules
200        from waeup.ikoba.permissions import get_waeup_role_names
201        return get_waeup_role_names()
202
203    def getTitle(self, value):
204        # late import: in interfaces we should not import local modules
205        from waeup.ikoba.permissions import get_all_roles
206        roles = dict(get_all_roles())
207        if value in roles.keys():
208            title = roles[value].title
209            if '.' in title:
210                title = title.split('.', 2)[1]
211        return title
212
213class CaptchaSource(BasicSourceFactory):
214    """A source for captchas.
215    """
216    def getValues(self):
217        captchas = ['No captcha', 'Testing captcha', 'ReCaptcha']
218        try:
219            # we have to 'try' because IConfiguration can only handle
220            # interfaces from w.k.interface.
221            from waeup.ikoba.browser.interfaces import ICaptchaManager
222        except:
223            return captchas
224        return sorted(getUtility(ICaptchaManager).getAvailCaptchas().keys())
225
226    def getTitle(self, value):
227        return value
228
229class IResultEntry(Interface):
230    """A school grade entry.
231    """
232    subject = schema.Choice(
233        title = _(u'Subject'),
234        source = SubjectSource(),
235        )
236    grade = schema.Choice(
237        title = _(u'Grade'),
238        source = GradeSource(),
239        )
240
241class IResultEntryField(IObject):
242    """A zope.schema-like field for usage in interfaces.
243
244    Marker interface to distuingish result entries from ordinary
245    object fields. Needed for registration of widgets.
246    """
247
248class IIkobaUtils(Interface):
249    """A collection of methods which are subject to customization.
250    """
251
252    PORTAL_LANGUAGE = Attribute("Dict of global language setting")
253    PREFERRED_LANGUAGES_DICT = Attribute("Dict of preferred languages")
254    EXAM_SUBJECTS_DICT = Attribute("Dict of examination subjects")
255    EXAM_GRADES = Attribute("Dict of examination grades")
256    SYSTEM_MAX_LOAD = Attribute("Dict of maximum system loads.")
257
258    def sendContactForm(
259          from_name,from_addr,rcpt_name,rcpt_addr,
260          from_username,usertype,portal,body,subject):
261        """Send an email with data provided by forms.
262        """
263
264    def fullname(firstname,lastname,middlename):
265        """Full name constructor.
266        """
267
268    def sendCredentials(user, password, url_info, msg):
269        """Send credentials as email.
270
271        Input is the customer for which credentials are sent and the
272        password.
273
274        Returns True or False to indicate successful operation.
275        """
276
277    def genPassword(length, chars):
278        """Generate a random password.
279        """
280
281class IIkobaObject(Interface):
282    """A Ikoba object.
283
284    This is merely a marker interface.
285    """
286
287class ICompany(IIkobaObject):
288    """Representation of an company.
289    """
290
291
292class IIkobaContainer(IIkobaObject):
293    """A container for Ikoba objects.
294    """
295
296class IIkobaContained(IIkobaObject):
297    """An item contained in an IIkobaContainer.
298    """
299
300class ICSVExporter(Interface):
301    """A CSV file exporter for objects.
302    """
303    fields = Attribute("""List of fieldnames in resulting CSV""")
304
305    title = schema.TextLine(
306        title = u'Title',
307        description = u'Description to be displayed in selections.',
308        )
309    def mangle_value(value, name, obj):
310        """Mangle `value` extracted from `obj` or suobjects thereof.
311
312        This is called by export before actually writing to the result
313        file.
314        """
315
316    def get_filtered(site, **kw):
317        """Get datasets in `site` to be exported.
318
319        The set of data is specified by keywords, which might be
320        different for any implementaion of exporter.
321
322        Returns an iterable.
323        """
324
325    def export(iterable, filepath=None):
326        """Export iterables as rows in a CSV file.
327
328        If `filepath` is not given, a string with the data should be
329        returned.
330
331        What kind of iterables are acceptable depends on the specific
332        exporter implementation.
333        """
334
335    def export_all(site, filepath=None):
336        """Export all items in `site` as CSV file.
337
338        if `filepath` is not given, a string with the data should be
339        returned.
340        """
341
342    def export_filtered(site, filepath=None, **kw):
343        """Export those items in `site` specified by `args` and `kw`.
344
345        If `filepath` is not given, a string with the data should be
346        returned.
347
348        Which special keywords are supported is up to the respective
349        exporter.
350        """
351
352class IIkobaExporter(Interface):
353    """An exporter for objects.
354    """
355    def export(obj, filepath=None):
356        """Export by pickling.
357
358        Returns a file-like object containing a representation of `obj`.
359
360        This is done using `pickle`. If `filepath` is ``None``, a
361        `cStringIO` object is returned, that contains the saved data.
362        """
363
364class IIkobaXMLExporter(Interface):
365    """An XML exporter for objects.
366    """
367    def export(obj, filepath=None):
368        """Export as XML.
369
370        Returns an XML representation of `obj`.
371
372        If `filepath` is ``None``, a StringIO` object is returned,
373        that contains the transformed data.
374        """
375
376class IIkobaXMLImporter(Interface):
377    """An XML import for objects.
378    """
379    def doImport(filepath):
380        """Create Python object from XML.
381
382        Returns a Python object.
383        """
384
385class IBatchProcessor(Interface):
386    """A batch processor that handles mass-operations.
387    """
388    name = schema.TextLine(
389        title = _(u'Processor name')
390        )
391
392    def doImport(path, headerfields, mode='create', user='Unknown',
393                 logger=None, ignore_empty=True):
394        """Read data from ``path`` and update connected object.
395
396        `headerfields` is a list of headerfields as read from the file
397        to import.
398
399        `mode` gives the import mode to use (``'create'``,
400        ``'update'``, or ``'remove'``.
401
402        `user` is a string describing the user performing the
403        import. Normally fetched from current principal.
404
405        `logger` is the logger to use during import.
406
407        `ignore_emtpy` in update mode ignores empty fields if true.
408        """
409
410class IContactForm(IIkobaObject):
411    """A contact form.
412    """
413
414    email_from = schema.ASCIILine(
415        title = _(u'Email Address:'),
416        default = None,
417        required = True,
418        constraint=validate_email,
419        )
420
421    email_to = schema.ASCIILine(
422        title = _(u'Email to:'),
423        default = None,
424        required = True,
425        constraint=validate_email,
426        )
427
428    subject = schema.TextLine(
429        title = _(u'Subject:'),
430        required = True,)
431
432    fullname = schema.TextLine(
433        title = _(u'Full Name:'),
434        required = True,)
435
436    body = schema.Text(
437        title = _(u'Text:'),
438        required = True,)
439
440class IIkobaPrincipalInfo(IPrincipalInfo):
441    """Infos about principals that are users of Ikoba Ikoba.
442    """
443    email = Attribute("The email address of a user")
444    phone = Attribute("The phone number of a user")
445    public_name = Attribute("The public name of a user")
446
447
448class IIkobaPrincipal(IPrincipal):
449    """A principle for Ikoba Ikoba.
450
451    This interface extends zope.security.interfaces.IPrincipal and
452    requires also an `id` and other attributes defined there.
453    """
454
455    email = schema.TextLine(
456        title = _(u'Email Address'),
457        description = u'',
458        required=False,)
459
460    phone = PhoneNumber(
461        title = _(u'Phone'),
462        description = u'',
463        required=False,)
464
465    public_name = schema.TextLine(
466        title = _(u'Public Name'),
467        required = False,)
468
469class IFailedLoginInfo(IIkobaObject):
470    """Info about failed logins.
471
472    Timestamps are supposed to be stored as floats using time.time()
473    or similar.
474    """
475    num = schema.Int(
476        title = _(u'Number of failed logins'),
477        description = _(u'Number of failed logins'),
478        required = True,
479        default = 0,
480        )
481
482    last = schema.Float(
483        title = _(u'Timestamp'),
484        description = _(u'Timestamp of last failed login or `None`'),
485        required = False,
486        default = None,
487        )
488
489    def as_tuple():
490        """Get login info as tuple ``<NUM>, <TIMESTAMP>``.
491        """
492
493    def set_values(num=0, last=None):
494        """Set number of failed logins and timestamp of last one.
495        """
496
497    def increase():
498        """Increase the current number of failed logins and set timestamp.
499        """
500
501    def reset():
502        """Set failed login counters back to zero.
503        """
504
505
506class IUserAccount(IIkobaObject):
507    """A user account.
508    """
509
510    failed_logins = Attribute("""FailedLoginInfo for this account""")
511
512    name = schema.TextLine(
513        title = _(u'User Id'),
514        description = u'Login name of user',
515        required = True,)
516
517    title = schema.TextLine(
518        title = _(u'Full Name'),
519        required = True,)
520
521    public_name = schema.TextLine(
522        title = _(u'Public Name'),
523        description = u"Substitute for officer's real name "
524                       "in object histories.",
525        required = False,)
526
527    description = schema.Text(
528        title = _(u'Description/Notice'),
529        required = False,)
530
531    email = schema.ASCIILine(
532        title = _(u'Email Address'),
533        default = None,
534        required = True,
535        constraint=validate_email,
536        )
537
538    phone = PhoneNumber(
539        title = _(u'Phone'),
540        default = None,
541        required = False,
542        )
543
544    roles = schema.List(
545        title = _(u'Portal Roles'),
546        value_type = schema.Choice(source=RoleSource()),
547        required = False,
548        )
549
550
551
552class IPasswordValidator(Interface):
553    """A password validator utility.
554    """
555
556    def validate_password(password, password_repeat):
557        """Validates a password by comparing it with
558        control password and checking some other requirements.
559        """
560
561
562class IUsersContainer(IIkobaObject):
563    """A container for users (principals).
564
565    These users are used for authentication purposes.
566    """
567
568    def addUser(name, password, title=None, description=None):
569        """Add a user.
570        """
571
572    def delUser(name):
573        """Delete a user if it exists.
574        """
575
576class ILocalRolesAssignable(Interface):
577    """The local roles assignable to an object.
578    """
579    def __call__():
580        """Returns a list of dicts.
581
582        Each dict contains a ``name`` referring to the role assignable
583        for the specified object and a `title` to describe the range
584        of users to which this role can be assigned.
585        """
586
587class IConfigurationContainer(IIkobaObject):
588    """A container for session configuration objects.
589    """
590
591    name = schema.TextLine(
592        title = _(u'Name of Company'),
593        default = _(u'Sample Company'),
594        required = True,
595        )
596
597    acronym = schema.TextLine(
598        title = _(u'Abbreviated Title of Company'),
599        default = u'WAeUP.Ikoba',
600        required = True,
601        )
602
603    frontpage = schema.Text(
604        title = _(u'Content in HTML format'),
605        required = False,
606        default = default_html_frontpage,
607        )
608
609    frontpage_dict = schema.Dict(
610        title = u'Content as language dictionary with values in html format',
611        required = False,
612        default = {},
613        )
614
615    name_admin = schema.TextLine(
616        title = _(u'Name of Administrator'),
617        default = u'Administrator',
618        required = True,
619        )
620
621    email_admin = schema.ASCIILine(
622        title = _(u'Email Address of Administrator'),
623        default = 'contact@waeup.org',
624        required = True,
625        constraint=validate_email,
626        )
627
628    email_subject = schema.TextLine(
629        title = _(u'Subject of Email to Administrator'),
630        default = _(u'Ikoba Contact'),
631        required = True,
632        )
633
634    smtp_mailer = schema.Choice(
635        title = _(u'SMTP mailer to use when sending mail'),
636        vocabulary = 'Mail Delivery Names',
637        default = 'No email service',
638        required = True,
639        )
640
641    captcha = schema.Choice(
642        title = _(u'Captcha used for public registration pages'),
643        source = CaptchaSource(),
644        default = u'No captcha',
645        required = True,
646        )
647
648
649class IDataCenter(IIkobaObject):
650    """A data center.
651
652    A data center manages files (uploads, downloads, etc.).
653
654    Beside providing the bare paths needed to keep files, it also
655    provides some helpers to put results of batch processing into
656    well-defined final locations (with well-defined filenames).
657
658    The main use-case is managing of site-related files, i.e. files
659    for import, export etc.
660
661    DataCenters are _not_ meant as storages for object-specific files
662    like passport photographs and similar.
663
664    It is up to the datacenter implementation how to organize data
665    (paths) inside its storage path.
666    """
667    storage = schema.Bytes(
668        title = u'Path to directory where everything is kept.'
669        )
670
671    deleted_path = schema.Bytes(
672        title = u'Path were data about deleted objects should be stored.'
673        )
674
675    def getPendingFiles(sort='name'):
676        """Get a list of files stored in `storage` sorted by basename.
677        """
678
679    def getFinishedFiles():
680        """Get a list of files stored in `finished` subfolder of `storage`.
681        """
682
683    def setStoragePath(path, move=False, overwrite=False):
684        """Set the path where to store files.
685
686        If `move` is True, move over files from the current location
687        to the new one.
688
689        If `overwrite` is also True, overwrite any already existing
690        files of same name in target location.
691
692        Triggers a DataCenterStorageMovedEvent.
693        """
694
695    def distProcessedFiles(successful, source_path, finished_file,
696                           pending_file, mode='create', move_orig=True):
697        """Distribute processed files over final locations.
698        """
699
700
701class IDataCenterFile(Interface):
702    """A data center file.
703    """
704
705    name = schema.TextLine(
706        title = u'Filename')
707
708    size = schema.TextLine(
709        title = u'Human readable file size')
710
711    uploaddate = schema.TextLine(
712        title = u'Human readable upload datetime')
713
714    lines = schema.Int(
715        title = u'Number of lines in file')
716
717    def getDate():
718        """Get creation timestamp from file in human readable form.
719        """
720
721    def getSize():
722        """Get human readable size of file.
723        """
724
725    def getLinesNumber():
726        """Get number of lines of file.
727        """
728
729class IDataCenterStorageMovedEvent(IObjectEvent):
730    """Emitted, when the storage of a datacenter changes.
731    """
732
733class IObjectUpgradeEvent(IObjectEvent):
734    """Can be fired, when an object shall be upgraded.
735    """
736
737class ILocalRoleSetEvent(IObjectEvent):
738    """A local role was granted/revoked for a principal on an object.
739    """
740    role_id = Attribute(
741        "The role id that was set.")
742    principal_id = Attribute(
743        "The principal id for which the role was granted/revoked.")
744    granted = Attribute(
745        "Boolean. If false, then the role was revoked.")
746
747class IQueryResultItem(Interface):
748    """An item in a search result.
749    """
750    url = schema.TextLine(
751        title = u'URL that links to the found item')
752    title = schema.TextLine(
753        title = u'Title displayed in search results.')
754    description = schema.Text(
755        title = u'Longer description of the item found.')
756
757class IIkobaPluggable(Interface):
758    """A component that might be plugged into a Ikoba Ikoba app.
759
760    Components implementing this interface are referred to as
761    'plugins'. They are normally called when a new
762    :class:`waeup.ikoba.app.Company` instance is created.
763
764    Plugins can setup and update parts of the central site without the
765    site object (normally a :class:`waeup.ikoba.app.Company` object)
766    needing to know about that parts. The site simply collects all
767    available plugins, calls them and the plugins care for their
768    respective subarea like the cutomers area or the datacenter
769    area.
770
771    Currently we have no mechanism to define an order of plugins. A
772    plugin should therefore make no assumptions about the state of the
773    site or other plugins being run before and instead do appropriate
774    checks if necessary.
775
776    Updates can be triggered for instance by the respective form in
777    the site configuration. You normally do updates when the
778    underlying software changed.
779    """
780    def setup(site, name, logger):
781        """Create an instance of the plugin.
782
783        The method is meant to be called by the central app (site)
784        when it is created.
785
786        `site`:
787           The site that requests a setup.
788
789        `name`:
790           The name under which the plugin was registered (utility name).
791
792        `logger`:
793           A standard Python logger for the plugins use.
794        """
795
796    def update(site, name, logger):
797        """Method to update an already existing plugin.
798
799        This might be called by a site when something serious
800        changes. It is a poor-man replacement for Zope generations
801        (but probably more comprehensive and better understandable).
802
803        `site`:
804           The site that requests an update.
805
806        `name`:
807           The name under which the plugin was registered (utility name).
808
809        `logger`:
810           A standard Python logger for the plugins use.
811        """
812
813class IAuthPluginUtility(Interface):
814    """A component that cares for authentication setup at site creation.
815
816    Utilities providing this interface are looked up when a Pluggable
817    Authentication Utility (PAU) for any
818    :class:`waeup.ikoba.app.Company` instance is created and put
819    into ZODB.
820
821    The setup-code then calls the `register` method of the utility and
822    expects a modified (or unmodified) version of the PAU back.
823
824    This allows to define any authentication setup modifications by
825    submodules or third-party modules/packages.
826    """
827
828    def register(pau):
829        """Register any plugins wanted to be in the PAU.
830        """
831
832    def unregister(pau):
833        """Unregister any plugins not wanted to be in the PAU.
834        """
835
836class IObjectConverter(Interface):
837    """Object converters are available as simple adapters, adapting
838       interfaces (not regular instances).
839
840    """
841
842    def fromStringDict(self, data_dict, context, form_fields=None):
843        """Convert values in `data_dict`.
844
845        Converts data in `data_dict` into real values based on
846        `context` and `form_fields`.
847
848        `data_dict` is a mapping (dict) from field names to values
849        represented as strings.
850
851        The fields (keys) to convert can be given in optional
852        `form_fields`. If given, form_fields should be an instance of
853        :class:`zope.formlib.form.Fields`. Suitable instances are for
854        example created by :class:`grok.AutoFields`.
855
856        If no `form_fields` are given, a default is computed from the
857        associated interface.
858
859        The `context` can be an existing object (implementing the
860        associated interface) or a factory name. If it is a string, we
861        try to create an object using
862        :func:`zope.component.createObject`.
863
864        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
865        <DATA_DICT>)`` where
866
867        ``<FIELD_ERRORS>``
868           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
869           error that happened when validating the input data in
870           `data_dict`
871
872        ``<INVARIANT_ERRORS>``
873           is a list of invariant errors concerning several fields
874
875        ``<DATA_DICT>``
876           is a dict with the values from input dict converted.
877
878        If errors happen, i.e. the error lists are not empty, always
879        an empty ``<DATA_DICT>`` is returned.
880
881        If ``<DATA_DICT>` is non-empty, there were no errors.
882        """
883
884class IFieldConverter(Interface):
885    def request_data(name, value, schema_field, prefix='', mode='create'):
886        """Create a dict with key-value mapping as created by a request.
887
888        `name` and `value` are expected to be parsed from CSV or a
889        similar input and represent an attribute to be set to a
890        representation of value.
891
892        `mode` gives the mode of import.
893
894        :meth:`update_request_data` is then requested to turn this
895        name and value into vars as they would be sent by a regular
896        form submit. This means we do not create the real values to be
897        set but we only define the values that would be sent in a
898        browser request to request the creation of those values.
899
900        The returned dict should contain names and values of a faked
901        browser request for the given `schema_field`.
902
903        Field converters are normally registered as adapters to some
904        specific zope.schema field.
905        """
906
907class IObjectHistory(Interface):
908
909    messages = schema.List(
910        title = u'List of messages stored',
911        required = True,
912        )
913
914    def addMessage(message):
915        """Add a message.
916        """
917
918class IIkobaWorkflowInfo(IWorkflowInfo):
919    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
920       methods for convenience.
921    """
922    def getManualTransitions():
923        """Get allowed manual transitions.
924
925        Get a sorted list of tuples containing the `transition_id` and
926        `title` of each allowed transition.
927        """
928
929class ISiteLoggers(Interface):
930
931    loggers = Attribute("A list or generator of registered IkobaLoggers")
932
933    def register(name, filename=None, site=None, **options):
934        """Register a logger `name` which logs to `filename`.
935
936        If `filename` is not given, logfile will be `name` with
937        ``.log`` as filename extension.
938        """
939
940    def unregister(name):
941        """Unregister a once registered logger.
942        """
943
944class ILogger(Interface):
945    """A logger cares for setup, update and restarting of a Python logger.
946    """
947
948    logger = Attribute("""A :class:`logging.Logger` instance""")
949
950
951    def __init__(name, filename=None, site=None, **options):
952        """Create a Ikoba logger instance.
953        """
954
955    def setup():
956        """Create a Python :class:`logging.Logger` instance.
957
958        The created logger is based on the params given by constructor.
959        """
960
961    def update(**options):
962        """Update the logger.
963
964        Updates the logger respecting modified `options` and changed
965        paths.
966        """
967
968class ILoggerCollector(Interface):
969
970    def getLoggers(site):
971        """Return all loggers registered for `site`.
972        """
973
974    def registerLogger(site, logging_component):
975        """Register a logging component residing in `site`.
976        """
977
978    def unregisterLogger(site, logging_component):
979        """Unregister a logger.
980        """
981
982#
983# External File Storage and relatives
984#
985class IFileStoreNameChooser(INameChooser):
986    """See zope.container.interfaces.INameChooser for base methods.
987    """
988    def checkName(name, attr=None):
989        """Check whether an object name is valid.
990
991        Raises a user error if the name is not valid.
992        """
993
994    def chooseName(name, attr=None):
995        """Choose a unique valid file id for the object.
996
997        The given name may be taken into account when choosing the
998        name (file id).
999
1000        chooseName is expected to always choose a valid file id (that
1001        would pass the checkName test) and never raise an error.
1002
1003        If `attr` is not ``None`` it might been taken into account as
1004        well when generating the file id. Usual behaviour is to
1005        interpret `attr` as a hint for what type of file for a given
1006        context should be stored if there are several types
1007        possible. For instance for a certain customer some file could
1008        be the connected passport photograph or some certificate scan
1009        or whatever. Each of them has to be stored in a different
1010        location so setting `attr` to a sensible value should give
1011        different file ids returned.
1012        """
1013
1014class IExtFileStore(IFileRetrieval):
1015    """A file storage that stores files in filesystem (not as blobs).
1016    """
1017    root = schema.TextLine(
1018        title = u'Root path of file store.',
1019        )
1020
1021    def getFile(file_id):
1022        """Get raw file data stored under file with `file_id`.
1023
1024        Returns a file descriptor open for reading or ``None`` if the
1025        file cannot be found.
1026        """
1027
1028    def getFileByContext(context, attr=None):
1029        """Get raw file data stored for the given context.
1030
1031        Returns a file descriptor open for reading or ``None`` if no
1032        such file can be found.
1033
1034        Both, `context` and `attr` might be used to find (`context`)
1035        and feed (`attr`) an appropriate file name chooser.
1036
1037        This is a convenience method.
1038        """
1039
1040    def deleteFile(file_id):
1041        """Delete file stored under `file_id`.
1042
1043        Remove file from filestore so, that it is not available
1044        anymore on next call to getFile for the same file_id.
1045
1046        Should not complain if no such file exists.
1047        """
1048
1049    def deleteFileByContext(context, attr=None):
1050        """Delete file for given `context` and `attr`.
1051
1052        Both, `context` and `attr` might be used to find (`context`)
1053        and feed (`attr`) an appropriate file name chooser.
1054
1055        This is a convenience method.
1056        """
1057
1058    def createFile(filename, f):
1059        """Create file given by f with filename `filename`
1060
1061        Returns a hurry.file.File-based object.
1062        """
1063
1064class IFileStoreHandler(Interface):
1065    """Filestore handlers handle specific files for file stores.
1066
1067    If a file to store/get provides a specific filename, a file store
1068    looks up special handlers for that type of file.
1069
1070    """
1071    def pathFromFileID(store, root, filename):
1072        """Turn file id into path to store.
1073
1074        Returned path should be absolute.
1075        """
1076
1077    def createFile(store, root, filename, file_id, file):
1078        """Return some hurry.file based on `store` and `file_id`.
1079
1080        Some kind of callback method called by file stores to create
1081        file objects from file_id.
1082
1083        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
1084        ``file_like_obj`` should be a HurryFile, a IkobaImageFile or
1085        similar. ``raw_file`` is the (maybe changed) input file and
1086        ``path`` the relative internal path to store the file at.
1087
1088        Please make sure the ``raw_file`` is opened for reading and
1089        the file descriptor set at position 0 when returned.
1090
1091        This method also gets the raw input file object that is about
1092        to be stored and is expected to raise any exceptions if some
1093        kind of validation or similar fails.
1094        """
1095
1096class IPDF(Interface):
1097    """A PDF representation of some context.
1098    """
1099
1100    def __call__(view=None, note=None):
1101        """Create a bytestream representing a PDF from context.
1102
1103        If `view` is passed in additional infos might be rendered into
1104        the document.
1105
1106        `note` is optional HTML rendered at bottom of the created
1107        PDF. Please consider the limited reportlab support for HTML,
1108        but using font-tags and friends you certainly can get the
1109        desired look.
1110        """
1111
1112class IMailService(Interface):
1113    """A mail service.
1114    """
1115
1116    def __call__():
1117        """Get the default mail delivery.
1118        """
1119
1120
1121class IDataCenterConfig(Interface):
1122    path = Path(
1123        title = u'Path',
1124        description = u"Directory where the datacenter should store "
1125                      u"files by default (adjustable in web UI).",
1126        required = True,
1127        )
1128
1129
1130class IPayPalConfig(Interface):
1131    path = Path(
1132        title = u'Path',
1133        description = u"Path to config file for PayPal REST API.",
1134        required = True,
1135        )
1136
1137
1138#
1139# Asynchronous job handling and related
1140#
1141class IJobManager(IIkobaObject):
1142    """A manager for asynchronous running jobs (tasks).
1143    """
1144    def put(job, site=None):
1145        """Put a job into task queue.
1146
1147        If no `site` is given, queue job in context of current local
1148        site.
1149
1150        Returns a job_id to identify the put job. This job_id is
1151        needed for further references to the job.
1152        """
1153
1154    def jobs(site=None):
1155        """Get an iterable of jobs stored.
1156        """
1157
1158    def get(job_id, site=None):
1159        """Get the job with id `job_id`.
1160
1161        For the `site` parameter see :meth:`put`.
1162        """
1163
1164    def remove(job_id, site=None):
1165        """Remove job with `job_id` from stored jobs.
1166        """
1167
1168    def start_test_job(site=None):
1169        """Start a test job.
1170        """
1171
1172class IProgressable(Interface):
1173    """A component that can indicate its progress status.
1174    """
1175    percent = schema.Float(
1176        title = u'Percent of job done already.',
1177        )
1178
1179class IJobContainer(IContainer):
1180    """A job container contains IJob objects.
1181    """
1182
1183class IExportJob(zc.async.interfaces.IJob):
1184    def __init__(site, exporter_name):
1185        pass
1186
1187    finished = schema.Bool(
1188        title = u'`True` if the job finished.`',
1189        default = False,
1190        )
1191
1192    failed = schema.Bool(
1193        title = u"`True` iff the job finished and didn't provide a file.",
1194        default = None,
1195        )
1196
1197class IExportJobContainer(IIkobaObject):
1198    """A component that contains (maybe virtually) export jobs.
1199    """
1200    def start_export_job(exporter_name, user_id, *args, **kwargs):
1201        """Start asynchronous export job.
1202
1203        `exporter_name` is the name of an exporter utility to be used.
1204
1205        `user_id` is the ID of the user that triggers the export.
1206
1207        `args` positional arguments passed to the export job created.
1208
1209        `kwargs` keyword arguments passed to the export job.
1210
1211        The job_id is stored along with exporter name and user id in a
1212        persistent list.
1213
1214        Returns the job ID of the job started.
1215        """
1216
1217    def get_running_export_jobs(user_id=None):
1218        """Get export jobs for user with `user_id` as list of tuples.
1219
1220        Each tuples holds ``<job_id>, <exporter_name>, <user_id>`` in
1221        that order. The ``<exporter_name>`` is the utility name of the
1222        used exporter.
1223
1224        If `user_id` is ``None``, all running jobs are returned.
1225        """
1226
1227    def get_export_jobs_status(user_id=None):
1228        """Get running/completed export jobs for `user_id` as list of tuples.
1229
1230        Each tuple holds ``<raw status>, <status translated>,
1231        <exporter title>`` in that order, where ``<status
1232        translated>`` and ``<exporter title>`` are translated strings
1233        representing the status of the job and the human readable
1234        title of the exporter used.
1235        """
1236
1237    def delete_export_entry(entry):
1238        """Delete the export denoted by `entry`.
1239
1240        Removes `entry` from the local `running_exports` list and also
1241        removes the regarding job via the local job manager.
1242
1243        `entry` is a tuple ``(<job id>, <exporter name>, <user id>)``
1244        as created by :meth:`start_export_job` or returned by
1245        :meth:`get_running_export_jobs`.
1246        """
1247
1248    def entry_from_job_id(job_id):
1249        """Get entry tuple for `job_id`.
1250
1251        Returns ``None`` if no such entry can be found.
1252        """
1253
1254class IExportContainerFinder(Interface):
1255    """A finder for the central export container.
1256    """
1257    def __call__():
1258        """Return the currently used global or site-wide IExportContainer.
1259        """
1260
1261class IFilteredQuery(IIkobaObject):
1262    """A query for objects.
1263    """
1264
1265    defaults = schema.Dict(
1266        title = u'Default Parameters',
1267        required = True,
1268        )
1269
1270    def __init__(**parameters):
1271        """Instantiate a filtered query by passing in parameters.
1272        """
1273
1274    def query():
1275        """Get an iterable of objects denoted by the set parameters.
1276
1277        The search should be applied to objects inside current
1278        site. It's the caller's duty to set the correct site before.
1279
1280        Result can be any iterable like a catalog result set, a list,
1281        or similar.
1282        """
1283
1284class IFilteredCatalogQuery(IFilteredQuery):
1285    """A catalog-based query for objects.
1286    """
1287
1288    cat_name = schema.TextLine(
1289        title = u'Registered name of the catalog to search.',
1290        required = True,
1291        )
1292
1293    def query_catalog(catalog):
1294        """Query catalog with the parameters passed to constructor.
1295        """
1296
1297
1298class IIDSource(Interface):
1299    """A source for unique IDs, according to RFC 4122.
1300    """
1301    def get_uuid():
1302        """Get a random unique ID.
1303
1304        Get a 'Universially Unique IDentifier'. The result string
1305        might contain any printable characters.
1306        """
1307
1308    def get_hex_uuid():
1309        """Get a random unique ID as a string representing a hex number.
1310        """
Note: See TracBrowser for help on using the repository browser.