source: main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py @ 17763

Last change on this file since 17763 was 17376, checked in by Henrik Bettermann, 21 months ago

Enable customization of certificate select box entries also in the students section.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 46.8 KB
RevLine 
[7193]1## $Id: interfaces.py 17376 2023-04-06 17:29:31Z henrik $
[3521]2##
[7193]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##
[6361]18import os
[7221]19import re
[7702]20import codecs
[9217]21import zc.async.interfaces
[7670]22import zope.i18nmessageid
[6915]23from datetime import datetime
[7063]24from hurry.file.interfaces import IFileRetrieval
[8394]25from hurry.workflow.interfaces import IWorkflowInfo
[4789]26from zc.sourcefactory.basic import BasicSourceFactory
[6147]27from zope import schema
[7233]28from zope.pluggableauth.interfaces import IPrincipalInfo
29from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
[4789]30from zope.component import getUtility
[4882]31from zope.component.interfaces import IObjectEvent
[9217]32from zope.configuration.fields import Path
33from zope.container.interfaces import INameChooser, IContainer
[8394]34from zope.interface import Interface, Attribute
[7795]35from zope.schema.interfaces import IObject
[4789]36from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
[8176]37from waeup.kofa.schema import PhoneNumber
[11450]38from waeup.kofa.sourcefactory import SmartBasicContextualSourceFactory
[3521]39
[7811]40_ = MessageFactory = zope.i18nmessageid.MessageFactory('waeup.kofa')
[6990]41
[8214]42DELETION_MARKER = 'XXX'
43IGNORE_MARKER = '<IGNORE>'
[9217]44WAEUP_KEY = 'waeup.kofa'
45VIRT_JOBS_CONTAINER_NAME = 'jobs'
[12901]46DOCLINK = 'http://kofa-doc.waeup.org/userdocs'
[8202]47
[7673]48CREATED = 'created'
49ADMITTED = 'admitted'
50CLEARANCE = 'clearance started'
51REQUESTED = 'clearance requested'
52CLEARED = 'cleared'
53PAID = 'school fee paid'
54RETURNING = 'returning'
55REGISTERED = 'courses registered'
56VALIDATED = 'courses validated'
[10446]57GRADUATED = 'graduated'
[15163]58TRANSREQ = 'transcript requested'
59TRANSVAL = 'transcript validated'
60TRANSREL = 'transcript released'
[7670]61
[10446]62
[9217]63#: A dict giving job status as tuple (<STRING>, <TRANSLATED_STRING>),
64#: the latter for UI purposes.
65JOB_STATUS_MAP = {
66    zc.async.interfaces.NEW: ('new', _('new')),
67    zc.async.interfaces.COMPLETED: ('completed', _('completed')),
68    zc.async.interfaces.PENDING: ('pending', _('pending')),
69    zc.async.interfaces.ACTIVE: ('active', _('active')),
70    zc.async.interfaces.ASSIGNED: ('assigned', _('assigned')),
71    zc.async.interfaces.CALLBACKS: ('callbacks', _('callbacks')),
72    }
73
[8361]74#default_rest_frontpage = u'' + codecs.open(os.path.join(
75#        os.path.dirname(__file__), 'frontpage.rst'),
76#        encoding='utf-8', mode='rb').read()
77
78default_html_frontpage = u'' + codecs.open(os.path.join(
79        os.path.dirname(__file__), 'frontpage.html'),
[7702]80        encoding='utf-8', mode='rb').read()
[6361]81
[7819]82def SimpleKofaVocabulary(*terms):
[6915]83    """A well-buildt vocabulary provides terms with a value, token and
84       title for each term
85    """
86    return SimpleVocabulary([
87            SimpleTerm(value, value, title) for title, value in terms])
88
89def academic_sessions():
90    curr_year = datetime.now().year
[16372]91    year_range = range(1970, curr_year + 2)
[6915]92    return [('%s/%s' % (year,year+1), year) for year in year_range]
93
[7819]94academic_sessions_vocab = SimpleKofaVocabulary(*academic_sessions())
[6915]95
[7819]96registration_states_vocab = SimpleKofaVocabulary(
[7677]97    (_('created'), CREATED),
98    (_('admitted'), ADMITTED),
99    (_('clearance started'), CLEARANCE),
100    (_('clearance requested'), REQUESTED),
101    (_('cleared'), CLEARED),
102    (_('school fee paid'), PAID),
103    (_('courses registered'), REGISTERED),
104    (_('courses validated'), VALIDATED),
[9671]105    (_('returning'), RETURNING),
[10451]106    (_('graduated'), GRADUATED),
[15163]107    (_('transcript requested'), TRANSREQ),
108    (_('transcript validated'), TRANSVAL),
109    (_('transcript released'), TRANSREL),
[6990]110    )
111
[11450]112class ContextualDictSourceFactoryBase(SmartBasicContextualSourceFactory):
113    """A base for contextual sources based on KofaUtils dicts.
114
115    To create a real source, you have to set the `DICT_NAME` attribute
116    which should be the name of a dictionary in KofaUtils.
117    """
118    def getValues(self, context):
119        utils = getUtility(IKofaUtils)
[11664]120        sorted_items = sorted(getattr(utils, self.DICT_NAME).items(),
121                              key=lambda item: item[1])
122        return [item[0] for item in sorted_items]
[11450]123
124    def getToken(self, context, value):
125        return str(value)
126
127    def getTitle(self, context, value):
128        utils = getUtility(IKofaUtils)
129        return getattr(utils, self.DICT_NAME)[value]
130
[7795]131class SubjectSource(BasicSourceFactory):
[7918]132    """A source for school subjects used in exam documentation.
133    """
[7795]134    def getValues(self):
[7841]135        subjects_dict = getUtility(IKofaUtils).EXAM_SUBJECTS_DICT
[7837]136        return sorted(subjects_dict.keys())
137
[7795]138    def getTitle(self, value):
[7841]139        subjects_dict = getUtility(IKofaUtils).EXAM_SUBJECTS_DICT
[7837]140        return "%s:" % subjects_dict[value]
[7795]141
142class GradeSource(BasicSourceFactory):
[7918]143    """A source for exam grades.
144    """
[7795]145    def getValues(self):
[7918]146        for entry in getUtility(IKofaUtils).EXAM_GRADES:
147            yield entry[0]
[7837]148
[7795]149    def getTitle(self, value):
[7918]150        return dict(getUtility(IKofaUtils).EXAM_GRADES)[value]
[7795]151
[11451]152class DisablePaymentGroupSource(ContextualDictSourceFactoryBase):
153    """A source for filtering groups of students
154    """
[14699]155    #: name of dict to deliver from kofa utils.
[11451]156    DICT_NAME = 'DISABLE_PAYMENT_GROUP_DICT'
157
[17094]158class MonthSource(BasicSourceFactory):
159    """A months source delivers all months of the year.
160    """
161    def getValues(self):
162        months_dict = getUtility(IKofaUtils).MONTHS_DICT
[17214]163        return sorted(months_dict.keys(), key=lambda x: int(x))
[17094]164
165    def getToken(self, value):
166        return value
167
168    def getTitle(self, value):
169        months_dict = getUtility(IKofaUtils).MONTHS_DICT
170        return months_dict[value]
171
[7850]172# Define a validation method for email addresses
[7221]173class NotAnEmailAddress(schema.ValidationError):
174    __doc__ = u"Invalid email address"
175
[8638]176#: Regular expression to check email-address formats. As these can
177#: become rather complex (nearly everything is allowed by RFCs), we only
178#: forbid whitespaces, commas and dots following onto each other.
[7221]179check_email = re.compile(
[8638]180    r"^[^@\s,]+@[^@\.\s,]+(\.[^@\.\s,]+)*$").match
[7221]181
182def validate_email(value):
183    if not check_email(value):
184        raise NotAnEmailAddress(value)
185    return True
186
[12414]187# Define a validation method for ids
188class NotIdValue(schema.ValidationError):
189    __doc__ = u"Invalid id"
190
191#: Regular expressions to check id formats.
[14051]192check_id = re.compile(r"^[a-zA-Z0-9_-]{2,11}$").match
[12414]193
194def validate_id(value):
195    if not check_id(value):
196        raise NotIdValue(value)
197    return True
198
[13235]199# Define a validation method for HTML fields
200class NotHTMLValue(schema.ValidationError):
201    __doc__ = u"Style or script elements not allowed"
202
203def validate_html(value):
204    if '<style' in value or '<script' in value:
205        raise NotHTMLValue(value)
206    return True
207
[7850]208# Define a validation method for international phone numbers
209class InvalidPhoneNumber(schema.ValidationError):
210    __doc__ = u"Invalid phone number"
211
212# represent format +NNN-NNNN-NNNN
213RE_INT_PHONE = re.compile(r"^\+?\d+\-\d+\-[\d\-]+$")
214
215def validate_phone(value):
[7851]216    if not RE_INT_PHONE.match(value):
[7850]217        raise InvalidPhoneNumber(value)
218    return True
219
[4858]220class FatalCSVError(Exception):
221    """Some row could not be processed.
222    """
223    pass
224
[6226]225class DuplicationError(Exception):
226    """An exception that can be raised when duplicates are found.
227
228    When raising :exc:`DuplicationError` you can, beside the usual
229    message, specify a list of objects which are duplicates. These
230    values can be used by catching code to print something helpful or
231    similar.
232    """
233    def __init__(self, msg, entries=[]):
234        self.msg = msg
235        self.entries = entries
236
237    def __str__(self):
238        return '%r' % self.msg
239
[6143]240class RoleSource(BasicSourceFactory):
[7178]241    """A source for site roles.
[6508]242    """
[6143]243    def getValues(self):
[6157]244        # late import: in interfaces we should not import local modules
[7811]245        from waeup.kofa.permissions import get_waeup_role_names
[7186]246        return get_waeup_role_names()
[6157]247
248    def getTitle(self, value):
249        # late import: in interfaces we should not import local modules
[7811]250        from waeup.kofa.permissions import get_all_roles
[7186]251        roles = dict(get_all_roles())
[6157]252        if value in roles.keys():
253            title = roles[value].title
[6569]254            if '.' in title:
255                title = title.split('.', 2)[1]
[6157]256        return title
[6143]257
[7313]258class CaptchaSource(BasicSourceFactory):
259    """A source for captchas.
260    """
261    def getValues(self):
[7323]262        captchas = ['No captcha', 'Testing captcha', 'ReCaptcha']
[7313]263        try:
264            # we have to 'try' because IConfiguration can only handle
[7817]265            # interfaces from w.k.interface.
[7811]266            from waeup.kofa.browser.interfaces import ICaptchaManager
[7313]267        except:
268            return captchas
269        return sorted(getUtility(ICaptchaManager).getAvailCaptchas().keys())
270
271    def getTitle(self, value):
272        return value
273
[7795]274class IResultEntry(Interface):
275    """A school grade entry.
276    """
[14012]277
[7795]278    subject = schema.Choice(
279        title = _(u'Subject'),
280        source = SubjectSource(),
281        )
[14012]282
[7795]283    grade = schema.Choice(
284        title = _(u'Grade'),
285        source = GradeSource(),
286        )
287
288class IResultEntryField(IObject):
289    """A zope.schema-like field for usage in interfaces.
290
291    Marker interface to distuingish result entries from ordinary
292    object fields. Needed for registration of widgets.
293    """
294
[14011]295class IRefereeEntry(Interface):
296    """A referee entry.
297    """
[14012]298    email_sent = Attribute('True if email has been sent')
299
[14011]300    name = schema.TextLine(
301        title = _(u'Name'),
302        required = True,
303        description = _(u'Name'),
304        )
[14012]305
[14011]306    email = schema.ASCIILine(
307        title = _(u'Email Address'),
308        default = None,
309        required = True,
310        constraint=validate_email,
[14040]311        description = _(
[16059]312            u'Email Address'),
[14011]313        )
314
315class IRefereeEntryField(IObject):
316    """A zope.schema-like field for usage in interfaces.
317
[15664]318    Marker interface to distuingish referee entries from ordinary
[14011]319    object fields. Needed for registration of widgets.
320    """
321
[7819]322class IKofaUtils(Interface):
[7358]323    """A collection of methods which are subject to customization.
324    """
[7568]325
[7841]326    PORTAL_LANGUAGE = Attribute("Dict of global language setting")
[17094]327    MONTHS_DICT = Attribute("Dict of months of the year")
[7841]328    PREFERRED_LANGUAGES_DICT = Attribute("Dict of preferred languages")
329    EXAM_SUBJECTS_DICT = Attribute("Dict of examination subjects")
[11799]330    EXAM_GRADES = Attribute("Dict of examination grades")
[7841]331    INST_TYPES_DICT = Attribute("Dict if institution types")
332    STUDY_MODES_DICT = Attribute("Dict of study modes")
333    APP_CATS_DICT = Attribute("Dict of application categories")
334    SEMESTER_DICT = Attribute("Dict of semesters or trimesters")
[11799]335    SYSTEM_MAX_LOAD = Attribute("Dict of maximum system loads.")
[15609]336    TEMP_PASSWORD_MINUTES = Attribute(
337        "Temporary passwords and parents password validity period")
[7568]338
[17376]339    def sortCertificates(context, resultset):
340        """Sort already filtered certificates in `CertificateSource`.
341        """
342
343    def getCertTitle(context, value):
344        """Compose the titles in `CertificateSource`.
345        """
346
[7404]347    def sendContactForm(
348          from_name,from_addr,rcpt_name,rcpt_addr,
349          from_username,usertype,portal,body,subject):
[7358]350        """Send an email with data provided by forms.
351        """
352
[7475]353    def fullname(firstname,lastname,middlename):
354        """Full name constructor.
355        """
356
[8853]357    def sendCredentials(user, password, url_info, msg):
[7475]358        """Send credentials as email.
359
360        Input is the applicant for which credentials are sent and the
361        password.
362
363        Returns True or False to indicate successful operation.
364        """
365
366    def genPassword(length, chars):
367        """Generate a random password.
368        """
369
[7819]370class IKofaObject(Interface):
371    """A Kofa object.
[5663]372
373    This is merely a marker interface.
[4789]374    """
375
[7819]376class IUniversity(IKofaObject):
[3521]377    """Representation of a university.
378    """
[5955]379
[6065]380
[7819]381class IKofaContainer(IKofaObject):
382    """A container for Kofa objects.
[4789]383    """
384
[7819]385class IKofaContained(IKofaObject):
386    """An item contained in an IKofaContainer.
[4789]387    """
[6136]388
[7726]389class ICSVExporter(Interface):
390    """A CSV file exporter for objects.
391    """
392    fields = Attribute("""List of fieldnames in resulting CSV""")
[7907]393
394    title = schema.TextLine(
395        title = u'Title',
396        description = u'Description to be displayed in selections.',
397        )
[7726]398    def mangle_value(value, name, obj):
399        """Mangle `value` extracted from `obj` or suobjects thereof.
400
[8394]401        This is called by export before actually writing to the result
402        file.
[7726]403        """
404
[9797]405    def get_filtered(site, **kw):
406        """Get datasets in `site` to be exported.
407
408        The set of data is specified by keywords, which might be
409        different for any implementaion of exporter.
410
411        Returns an iterable.
412        """
413
[12516]414    def get_selected(site, selected):
415        """Get datasets in `site` to be exported.
416
417        The set of data is specified by a list of identifiers.
418
419        Returns an iterable.
420        """
421
[7730]422    def export(iterable, filepath=None):
423        """Export iterables as rows in a CSV file.
[7726]424
[8394]425        If `filepath` is not given, a string with the data should be
426        returned.
[7730]427
428        What kind of iterables are acceptable depends on the specific
429        exporter implementation.
[7726]430        """
431
[9766]432    def export_all(site, filepath=None):
[7726]433        """Export all items in `site` as CSV file.
434
[8394]435        if `filepath` is not given, a string with the data should be
436        returned.
[7726]437        """
438
[9797]439    def export_filtered(site, filepath=None, **kw):
440        """Export those items in `site` specified by `args` and `kw`.
441
442        If `filepath` is not given, a string with the data should be
443        returned.
444
445        Which special keywords are supported is up to the respective
446        exporter.
447        """
448
[12516]449    def export_selected(site, filepath=None, **kw):
450        """Export items in `site` specified by a list of identifiers
451        called `selected`.
452
453        If `filepath` is not given, a string with the data should be
454        returned.
455        """
456
[7819]457class IKofaExporter(Interface):
[4789]458    """An exporter for objects.
459    """
460    def export(obj, filepath=None):
461        """Export by pickling.
462
463        Returns a file-like object containing a representation of `obj`.
464
465        This is done using `pickle`. If `filepath` is ``None``, a
466        `cStringIO` object is returned, that contains the saved data.
467        """
468
[7819]469class IKofaXMLExporter(Interface):
[4789]470    """An XML exporter for objects.
471    """
472    def export(obj, filepath=None):
473        """Export as XML.
474
475        Returns an XML representation of `obj`.
476
477        If `filepath` is ``None``, a StringIO` object is returned,
478        that contains the transformed data.
479        """
480
[7819]481class IKofaXMLImporter(Interface):
[4789]482    """An XML import for objects.
483    """
484    def doImport(filepath):
485        """Create Python object from XML.
486
487        Returns a Python object.
488        """
489
[4858]490class IBatchProcessor(Interface):
491    """A batch processor that handles mass-operations.
492    """
493    name = schema.TextLine(
[7933]494        title = _(u'Processor name')
[4858]495        )
496
[5476]497    def doImport(path, headerfields, mode='create', user='Unknown',
[8218]498                 logger=None, ignore_empty=True):
[4858]499        """Read data from ``path`` and update connected object.
[5476]500
501        `headerfields` is a list of headerfields as read from the file
502        to import.
503
504        `mode` gives the import mode to use (``'create'``,
505        ``'update'``, or ``'remove'``.
506
507        `user` is a string describing the user performing the
508        import. Normally fetched from current principal.
509
510        `logger` is the logger to use during import.
[8218]511
512        `ignore_emtpy` in update mode ignores empty fields if true.
[4858]513        """
514
[7819]515class IContactForm(IKofaObject):
[7225]516    """A contact form.
517    """
518
519    email_from = schema.ASCIILine(
[7828]520        title = _(u'Email Address:'),
[7225]521        default = None,
522        required = True,
523        constraint=validate_email,
524        )
525
526    email_to = schema.ASCIILine(
[7828]527        title = _(u'Email to:'),
[7225]528        default = None,
529        required = True,
530        constraint=validate_email,
531        )
532
[16299]533    bcc_to = schema.Text(
534        title = _(u'Bcc to:'),
535        required = True,
536        #readonly = True,
537        )
538
[7225]539    subject = schema.TextLine(
[7828]540        title = _(u'Subject:'),
[7225]541        required = True,)
542
543    fullname = schema.TextLine(
[7828]544        title = _(u'Full Name:'),
[7225]545        required = True,)
546
547    body = schema.Text(
[7828]548        title = _(u'Text:'),
[7225]549        required = True,)
550
[16299]551
[7819]552class IKofaPrincipalInfo(IPrincipalInfo):
553    """Infos about principals that are users of Kofa Kofa.
[7233]554    """
555    email = Attribute("The email address of a user")
556    phone = Attribute("The phone number of a user")
[8757]557    public_name = Attribute("The public name of a user")
[12915]558    user_type = Attribute("The type of a user")
[7225]559
[7233]560
[7819]561class IKofaPrincipal(IPrincipal):
562    """A principle for Kofa Kofa.
[7233]563
564    This interface extends zope.security.interfaces.IPrincipal and
565    requires also an `id` and other attributes defined there.
566    """
567
568    email = schema.TextLine(
[7828]569        title = _(u'Email Address'),
[7233]570        description = u'',
571        required=False,)
572
[8176]573    phone = PhoneNumber(
[7828]574        title = _(u'Phone'),
[7233]575        description = u'',
576        required=False,)
577
[8757]578    public_name = schema.TextLine(
579        title = _(u'Public Name'),
580        required = False,)
581
[12915]582    user_type = Attribute('The user type of the principal')
583
[10055]584class IFailedLoginInfo(IKofaObject):
585    """Info about failed logins.
586
587    Timestamps are supposed to be stored as floats using time.time()
588    or similar.
589    """
590    num = schema.Int(
591        title = _(u'Number of failed logins'),
592        description = _(u'Number of failed logins'),
593        required = True,
594        default = 0,
595        )
596
597    last = schema.Float(
598        title = _(u'Timestamp'),
599        description = _(u'Timestamp of last failed login or `None`'),
600        required = False,
601        default = None,
602        )
603
604    def as_tuple():
605        """Get login info as tuple ``<NUM>, <TIMESTAMP>``.
606        """
607
608    def set_values(num=0, last=None):
609        """Set number of failed logins and timestamp of last one.
610        """
611
612    def increase():
613        """Increase the current number of failed logins and set timestamp.
614        """
615
616    def reset():
617        """Set failed login counters back to zero.
618        """
619
620
[7819]621class IUserAccount(IKofaObject):
[4789]622    """A user account.
623    """
[10055]624
[12926]625    failed_logins = Attribute('FailedLoginInfo for this account')
[10055]626
[4789]627    name = schema.TextLine(
[7828]628        title = _(u'User Id'),
[12926]629        description = _(u'Login name of user'),
[4789]630        required = True,)
[7221]631
[4789]632    title = schema.TextLine(
[7828]633        title = _(u'Full Name'),
[8759]634        required = True,)
[7221]635
[8756]636    public_name = schema.TextLine(
637        title = _(u'Public Name'),
[12926]638        description = _(u"Substitute for officer's real name "
[16531]639                       "in student object histories "
640                       "and in local roles tables."),
[8756]641        required = False,)
642
[7197]643    description = schema.Text(
[7828]644        title = _(u'Description/Notice'),
[4789]645        required = False,)
[7221]646
647    email = schema.ASCIILine(
[7828]648        title = _(u'Email Address'),
[7221]649        default = None,
[7222]650        required = True,
[7221]651        constraint=validate_email,
652        )
653
[8176]654    phone = PhoneNumber(
[7828]655        title = _(u'Phone'),
[7233]656        default = None,
[8062]657        required = False,
[7233]658        )
659
[4789]660    roles = schema.List(
[8486]661        title = _(u'Portal Roles'),
[8079]662        value_type = schema.Choice(source=RoleSource()),
663        required = False,
664        )
[6136]665
[12926]666    suspended = schema.Bool(
667        title = _(u'Account suspended'),
668        description = _(u'If set, the account is immediately blocked.'),
669        default = False,
670        required = False,
671        )
[10055]672
673
[7147]674class IPasswordValidator(Interface):
675    """A password validator utility.
676    """
[6136]677
[7147]678    def validate_password(password, password_repeat):
679        """Validates a password by comparing it with
680        control password and checking some other requirements.
681        """
682
[15287]683    def validate_secure_password(self, pw, pw_repeat):
[15286]684        """ Validates a password by comparing it with
685        control password and checks password strength by
686        matching with the regular expression:
[7147]687
[15286]688        ^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{8,}$
689
690        ^              Start anchor
691        (?=.*[A-Z])    Ensure password has one uppercase letters.
692        (?=.*[0-9])    Ensure password has one digit.
693        (?=.*[a-z])    Ensure password has one lowercase letter.
694        .{8,}          Ensure password is of length 8.
695        $              End anchor.
696        """
697
698
[7819]699class IUsersContainer(IKofaObject):
[12915]700    """A container for officers.
[4789]701    """
702
703    def addUser(name, password, title=None, description=None):
704        """Add a user.
705        """
706
707    def delUser(name):
708        """Delete a user if it exists.
709        """
710
[6141]711class ILocalRolesAssignable(Interface):
712    """The local roles assignable to an object.
713    """
714    def __call__():
715        """Returns a list of dicts.
716
717        Each dict contains a ``name`` referring to the role assignable
718        for the specified object and a `title` to describe the range
719        of users to which this role can be assigned.
720        """
721
[7819]722class IConfigurationContainer(IKofaObject):
[6907]723    """A container for session configuration objects.
724    """
725
726    name = schema.TextLine(
[7828]727        title = _(u'Name of University'),
728        default = _(u'Sample University'),
[6907]729        required = True,
730        )
731
[7459]732    acronym = schema.TextLine(
[7828]733        title = _(u'Abbreviated Title of University'),
[7819]734        default = u'WAeUP.Kofa',
[7459]735        required = True,
736        )
737
[6907]738    frontpage = schema.Text(
[8361]739        title = _(u'Content in HTML format'),
[6907]740        required = False,
[8361]741        default = default_html_frontpage,
[13235]742        constraint=validate_html,
[6907]743        )
744
[7702]745    frontpage_dict = schema.Dict(
746        title = u'Content as language dictionary with values in html format',
[7485]747        required = False,
[7702]748        default = {},
[7485]749        )
750
[7223]751    name_admin = schema.TextLine(
[7828]752        title = _(u'Name of Administrator'),
[7223]753        default = u'Administrator',
[8230]754        required = True,
[7223]755        )
756
[7221]757    email_admin = schema.ASCIILine(
[7828]758        title = _(u'Email Address of Administrator'),
[7221]759        default = 'contact@waeup.org',
[8230]760        required = True,
[13158]761        #constraint=validate_email,
[7221]762        )
763
764    email_subject = schema.TextLine(
[7828]765        title = _(u'Subject of Email to Administrator'),
766        default = _(u'Kofa Contact'),
[8230]767        required = True,
[7221]768        )
769
[7470]770    smtp_mailer = schema.Choice(
[7828]771        title = _(u'SMTP mailer to use when sending mail'),
[7470]772        vocabulary = 'Mail Delivery Names',
773        default = 'No email service',
774        required = True,
775        )
776
[7313]777    captcha = schema.Choice(
[7828]778        title = _(u'Captcha used for public registration pages'),
[7313]779        source = CaptchaSource(),
780        default = u'No captcha',
781        required = True,
782        )
[7221]783
[7664]784    carry_over = schema.Bool(
[7828]785        title = _(u'Carry-over Course Registration'),
[7664]786        default = False,
787        )
788
[10627]789    current_academic_session = schema.Choice(
790        title = _(u'Current Academic Session'),
[14699]791        description = _(u'Session for which score editing is allowed'),
[10627]792        source = academic_sessions_vocab,
793        default = None,
794        required = False,
795        readonly = False,
796        )
797
[11589]798    next_matric_integer = schema.Int(
799        title = _(u'Next Matriculation Number Integer'),
800        description = _(u'Integer used for constructing the next '
801                         'matriculation number'),
802        default = 0,
803        readonly = False,
804        required = False,
805        )
806
[13354]807    next_matric_integer_2 = schema.Int(
808        title = _(u'Next Matriculation Number Integer 2'),
809        description = _(u'2nd integer used for constructing the next '
810                         'matriculation number'),
811        default = 0,
812        readonly = False,
813        required = False,
814        )
815
[13608]816    next_matric_integer_3 = schema.Int(
817        title = _(u'Next Matriculation Number Integer 3'),
818        description = _(u'3rd integer used for constructing the next '
819                         'matriculation number'),
820        default = 0,
821        readonly = False,
822        required = False,
823        )
824
[13789]825    next_matric_integer_4 = schema.Int(
826        title = _(u'Next Matriculation Number Integer 4'),
827        description = _(u'4th integer used for constructing the next '
828                         'matriculation number'),
829        default = 0,
830        readonly = False,
831        required = False,
832        )
833
[13198]834    export_disabled_message = schema.Text(
[13239]835        title = _(u'Export-disabled message'),
836        description = _(u'Message which will show up if an officer tries '
837                         'to export data. All exporters are automatcally '
838                         'disabled if this field is set.'),
[13198]839        required = False,
840        )
841
[13394]842    maintmode_enabled_by = schema.TextLine(
843        title = _(u'Maintenance Mode enabled by'),
844        default = None,
845        required = False,
846        )
[13198]847
[13118]848    def addSessionConfiguration(sessionconfiguration):
849        """Add a session configuration object.
850        """
851
[7819]852class ISessionConfiguration(IKofaObject):
[6915]853    """A session configuration object.
[6907]854    """
855
[6915]856    academic_session = schema.Choice(
[7828]857        title = _(u'Academic Session'),
[6915]858        source = academic_sessions_vocab,
859        default = None,
860        required = True,
861        readonly = True,
862        )
863
[13033]864    clearance_enabled = schema.Bool(
865        title = _(u'Clearance enabled'),
866        default = False,
867        )
868
869    payment_disabled = schema.List(
870        title = _(u'Payment disabled'),
871        value_type = schema.Choice(
872            source = DisablePaymentGroupSource(),
873            ),
874        required = False,
[14011]875        defaultFactory=list,
[13033]876        )
877
[13034]878    coursereg_deadline = schema.Datetime(
879        title = _(u'Course Reg. Deadline'),
880        required = False,
881        description = _('Example: ') + u'2011-12-31 23:59:59+01:00',
882        )
883
[8260]884    clearance_fee = schema.Float(
[9243]885        title = _(u'Acceptance Fee'),
[7927]886        default = 0.0,
[7881]887        required = False,
[6993]888        )
889
[8260]890    booking_fee = schema.Float(
891        title = _(u'Bed Booking Fee'),
[7927]892        default = 0.0,
[7881]893        required = False,
[7250]894        )
895
[9423]896    maint_fee = schema.Float(
[10680]897        title = _(u'Rent (fallback)'),
[9423]898        default = 0.0,
899        required = False,
900        )
901
[14461]902    late_registration_fee = schema.Float(
903        title = _(u'Late Course Reg. Fee'),
904        default = 0.0,
905        required = False,
906        )
907
[10449]908    transcript_fee = schema.Float(
909        title = _(u'Transcript Fee'),
910        default = 0.0,
911        required = False,
912        )
913
[13574]914    transfer_fee = schema.Float(
915        title = _(u'Transfer Fee'),
916        default = 0.0,
917        required = False,
918        )
919
[6918]920    def getSessionString():
[13118]921        """Return the session string from the vocabulary.
[6918]922        """
923
924
[6916]925class ISessionConfigurationAdd(ISessionConfiguration):
926    """A session configuration object in add mode.
927    """
928
929    academic_session = schema.Choice(
[7828]930        title = _(u'Academic Session'),
[6916]931        source = academic_sessions_vocab,
932        default = None,
933        required = True,
934        readonly = False,
935        )
936
937ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
938    'academic_session'].order
939
[7819]940class IDataCenter(IKofaObject):
[4789]941    """A data center.
942
[8394]943    A data center manages files (uploads, downloads, etc.).
944
945    Beside providing the bare paths needed to keep files, it also
946    provides some helpers to put results of batch processing into
947    well-defined final locations (with well-defined filenames).
948
949    The main use-case is managing of site-related files, i.e. files
950    for import, export etc.
951
952    DataCenters are _not_ meant as storages for object-specific files
953    like passport photographs and similar.
954
955    It is up to the datacenter implementation how to organize data
956    (paths) inside its storage path.
[4789]957    """
[8394]958    storage = schema.Bytes(
959        title = u'Path to directory where everything is kept.'
960        )
[4789]961
[8394]962    deleted_path = schema.Bytes(
[15416]963        title = u'Path for deleted student data.'
[8394]964        )
965
[15416]966    graduated_path = schema.Bytes(
967        title = u'Path for deleted graduated student data.'
968        )
969
[9023]970    def getPendingFiles(sort='name'):
[8394]971        """Get a list of files stored in `storage` sorted by basename.
972        """
[9023]973
[9074]974    def getFinishedFiles():
975        """Get a list of files stored in `finished` subfolder of `storage`.
[9023]976        """
977
[8394]978    def setStoragePath(path, move=False, overwrite=False):
979        """Set the path where to store files.
980
981        If `move` is True, move over files from the current location
982        to the new one.
983
984        If `overwrite` is also True, overwrite any already existing
985        files of same name in target location.
986
987        Triggers a DataCenterStorageMovedEvent.
988        """
989
990    def distProcessedFiles(successful, source_path, finished_file,
991                           pending_file, mode='create', move_orig=True):
992        """Distribute processed files over final locations.
993        """
994
995
[4789]996class IDataCenterFile(Interface):
997    """A data center file.
998    """
[4858]999
1000    name = schema.TextLine(
1001        title = u'Filename')
1002
1003    size = schema.TextLine(
1004        title = u'Human readable file size')
1005
1006    uploaddate = schema.TextLine(
1007        title = u'Human readable upload datetime')
1008
1009    lines = schema.Int(
1010        title = u'Number of lines in file')
[6136]1011
[4789]1012    def getDate():
1013        """Get creation timestamp from file in human readable form.
1014        """
1015
1016    def getSize():
1017        """Get human readable size of file.
1018        """
[4858]1019
1020    def getLinesNumber():
1021        """Get number of lines of file.
1022        """
[4882]1023
1024class IDataCenterStorageMovedEvent(IObjectEvent):
1025    """Emitted, when the storage of a datacenter changes.
1026    """
[5007]1027
[6136]1028class IObjectUpgradeEvent(IObjectEvent):
1029    """Can be fired, when an object shall be upgraded.
1030    """
1031
[6180]1032class ILocalRoleSetEvent(IObjectEvent):
1033    """A local role was granted/revoked for a principal on an object.
1034    """
1035    role_id = Attribute(
1036        "The role id that was set.")
1037    principal_id = Attribute(
1038        "The principal id for which the role was granted/revoked.")
1039    granted = Attribute(
1040        "Boolean. If false, then the role was revoked.")
1041
[5007]1042class IQueryResultItem(Interface):
1043    """An item in a search result.
1044    """
1045    url = schema.TextLine(
1046        title = u'URL that links to the found item')
1047    title = schema.TextLine(
1048        title = u'Title displayed in search results.')
1049    description = schema.Text(
1050        title = u'Longer description of the item found.')
[6136]1051
[7819]1052class IKofaPluggable(Interface):
1053    """A component that might be plugged into a Kofa Kofa app.
[5658]1054
1055    Components implementing this interface are referred to as
1056    'plugins'. They are normally called when a new
[7811]1057    :class:`waeup.kofa.app.University` instance is created.
[5658]1058
1059    Plugins can setup and update parts of the central site without the
[7811]1060    site object (normally a :class:`waeup.kofa.app.University` object)
[5658]1061    needing to know about that parts. The site simply collects all
1062    available plugins, calls them and the plugins care for their
[5676]1063    respective subarea like the applicants area or the datacenter
[5658]1064    area.
1065
1066    Currently we have no mechanism to define an order of plugins. A
1067    plugin should therefore make no assumptions about the state of the
1068    site or other plugins being run before and instead do appropriate
1069    checks if necessary.
1070
1071    Updates can be triggered for instance by the respective form in
1072    the site configuration. You normally do updates when the
1073    underlying software changed.
[5013]1074    """
[5069]1075    def setup(site, name, logger):
1076        """Create an instance of the plugin.
[5013]1077
[5658]1078        The method is meant to be called by the central app (site)
1079        when it is created.
1080
1081        `site`:
1082           The site that requests a setup.
1083
1084        `name`:
1085           The name under which the plugin was registered (utility name).
1086
1087        `logger`:
1088           A standard Python logger for the plugins use.
[5069]1089        """
1090
1091    def update(site, name, logger):
1092        """Method to update an already existing plugin.
1093
1094        This might be called by a site when something serious
[5658]1095        changes. It is a poor-man replacement for Zope generations
1096        (but probably more comprehensive and better understandable).
1097
1098        `site`:
1099           The site that requests an update.
1100
1101        `name`:
1102           The name under which the plugin was registered (utility name).
1103
1104        `logger`:
1105           A standard Python logger for the plugins use.
[5069]1106        """
[5898]1107
[5899]1108class IAuthPluginUtility(Interface):
[5898]1109    """A component that cares for authentication setup at site creation.
1110
1111    Utilities providing this interface are looked up when a Pluggable
1112    Authentication Utility (PAU) for any
[7811]1113    :class:`waeup.kofa.app.University` instance is created and put
[5898]1114    into ZODB.
1115
1116    The setup-code then calls the `register` method of the utility and
1117    expects a modified (or unmodified) version of the PAU back.
1118
1119    This allows to define any authentication setup modifications by
1120    submodules or third-party modules/packages.
1121    """
1122
1123    def register(pau):
1124        """Register any plugins wanted to be in the PAU.
1125        """
1126
1127    def unregister(pau):
1128        """Unregister any plugins not wanted to be in the PAU.
1129        """
[6273]1130
1131class IObjectConverter(Interface):
1132    """Object converters are available as simple adapters, adapting
1133       interfaces (not regular instances).
1134
1135    """
1136
[6277]1137    def fromStringDict(self, data_dict, context, form_fields=None):
1138        """Convert values in `data_dict`.
[6273]1139
[6277]1140        Converts data in `data_dict` into real values based on
1141        `context` and `form_fields`.
[6273]1142
[6277]1143        `data_dict` is a mapping (dict) from field names to values
1144        represented as strings.
[6273]1145
[6277]1146        The fields (keys) to convert can be given in optional
1147        `form_fields`. If given, form_fields should be an instance of
1148        :class:`zope.formlib.form.Fields`. Suitable instances are for
1149        example created by :class:`grok.AutoFields`.
[6273]1150
[6277]1151        If no `form_fields` are given, a default is computed from the
1152        associated interface.
[6273]1153
[6277]1154        The `context` can be an existing object (implementing the
1155        associated interface) or a factory name. If it is a string, we
1156        try to create an object using
1157        :func:`zope.component.createObject`.
1158
1159        Returns a tuple ``(<FIELD_ERRORS>, <INVARIANT_ERRORS>,
1160        <DATA_DICT>)`` where
1161
1162        ``<FIELD_ERRORS>``
1163           is a list of tuples ``(<FIELD_NAME>, <ERROR>)`` for each
1164           error that happened when validating the input data in
1165           `data_dict`
1166
1167        ``<INVARIANT_ERRORS>``
1168           is a list of invariant errors concerning several fields
1169
1170        ``<DATA_DICT>``
1171           is a dict with the values from input dict converted.
1172
1173        If errors happen, i.e. the error lists are not empty, always
1174        an empty ``<DATA_DICT>`` is returned.
1175
[12836]1176        If ``<DATA_DICT>`` is non-empty, there were no errors.
[6273]1177        """
[6293]1178
[7932]1179class IFieldConverter(Interface):
[8214]1180    def request_data(name, value, schema_field, prefix='', mode='create'):
[7932]1181        """Create a dict with key-value mapping as created by a request.
1182
1183        `name` and `value` are expected to be parsed from CSV or a
1184        similar input and represent an attribute to be set to a
1185        representation of value.
1186
[8214]1187        `mode` gives the mode of import.
1188
[7932]1189        :meth:`update_request_data` is then requested to turn this
1190        name and value into vars as they would be sent by a regular
1191        form submit. This means we do not create the real values to be
1192        set but we only define the values that would be sent in a
1193        browser request to request the creation of those values.
1194
1195        The returned dict should contain names and values of a faked
1196        browser request for the given `schema_field`.
1197
1198        Field converters are normally registered as adapters to some
1199        specific zope.schema field.
1200        """
1201
[6338]1202class IObjectHistory(Interface):
1203
1204    messages = schema.List(
1205        title = u'List of messages stored',
1206        required = True,
1207        )
1208
1209    def addMessage(message):
1210        """Add a message.
1211        """
[6353]1212
[7819]1213class IKofaWorkflowInfo(IWorkflowInfo):
[6353]1214    """A :class:`hurry.workflow.workflow.WorkflowInfo` with additional
1215       methods for convenience.
1216    """
1217    def getManualTransitions():
1218        """Get allowed manual transitions.
1219
1220        Get a sorted list of tuples containing the `transition_id` and
1221        `title` of each allowed transition.
1222        """
[6481]1223
1224class ISiteLoggers(Interface):
1225
[7819]1226    loggers = Attribute("A list or generator of registered KofaLoggers")
[6481]1227
1228    def register(name, filename=None, site=None, **options):
1229        """Register a logger `name` which logs to `filename`.
1230
1231        If `filename` is not given, logfile will be `name` with
1232        ``.log`` as filename extension.
1233        """
1234
1235    def unregister(name):
1236        """Unregister a once registered logger.
1237        """
1238
1239class ILogger(Interface):
1240    """A logger cares for setup, update and restarting of a Python logger.
1241    """
1242
1243    logger = Attribute("""A :class:`logging.Logger` instance""")
1244
1245
1246    def __init__(name, filename=None, site=None, **options):
[7819]1247        """Create a Kofa logger instance.
[6481]1248        """
1249
1250    def setup():
1251        """Create a Python :class:`logging.Logger` instance.
1252
1253        The created logger is based on the params given by constructor.
1254        """
1255
1256    def update(**options):
1257        """Update the logger.
1258
1259        Updates the logger respecting modified `options` and changed
1260        paths.
1261        """
[6754]1262
1263class ILoggerCollector(Interface):
1264
1265    def getLoggers(site):
1266        """Return all loggers registered for `site`.
1267        """
1268
1269    def registerLogger(site, logging_component):
1270        """Register a logging component residing in `site`.
1271        """
1272
1273    def unregisterLogger(site, logging_component):
1274        """Unregister a logger.
1275        """
[7063]1276
1277#
1278# External File Storage and relatives
1279#
1280class IFileStoreNameChooser(INameChooser):
1281    """See zope.container.interfaces.INameChooser for base methods.
1282    """
[7066]1283    def checkName(name, attr=None):
[7063]1284        """Check whether an object name is valid.
1285
1286        Raises a user error if the name is not valid.
1287        """
1288
[7066]1289    def chooseName(name, attr=None):
1290        """Choose a unique valid file id for the object.
[7063]1291
[7066]1292        The given name may be taken into account when choosing the
1293        name (file id).
[7063]1294
[7066]1295        chooseName is expected to always choose a valid file id (that
1296        would pass the checkName test) and never raise an error.
1297
1298        If `attr` is not ``None`` it might been taken into account as
1299        well when generating the file id. Usual behaviour is to
1300        interpret `attr` as a hint for what type of file for a given
1301        context should be stored if there are several types
1302        possible. For instance for a certain student some file could
1303        be the connected passport photograph or some certificate scan
1304        or whatever. Each of them has to be stored in a different
1305        location so setting `attr` to a sensible value should give
1306        different file ids returned.
[7063]1307        """
1308
1309class IExtFileStore(IFileRetrieval):
1310    """A file storage that stores files in filesystem (not as blobs).
1311    """
1312    root = schema.TextLine(
1313        title = u'Root path of file store.',
1314        )
1315
1316    def getFile(file_id):
1317        """Get raw file data stored under file with `file_id`.
1318
1319        Returns a file descriptor open for reading or ``None`` if the
1320        file cannot be found.
1321        """
1322
[7071]1323    def getFileByContext(context, attr=None):
[7063]1324        """Get raw file data stored for the given context.
1325
1326        Returns a file descriptor open for reading or ``None`` if no
1327        such file can be found.
1328
[7071]1329        Both, `context` and `attr` might be used to find (`context`)
1330        and feed (`attr`) an appropriate file name chooser.
1331
[7063]1332        This is a convenience method.
1333        """
1334
[7090]1335    def deleteFile(file_id):
1336        """Delete file stored under `file_id`.
1337
1338        Remove file from filestore so, that it is not available
1339        anymore on next call to getFile for the same file_id.
1340
1341        Should not complain if no such file exists.
1342        """
1343
1344    def deleteFileByContext(context, attr=None):
1345        """Delete file for given `context` and `attr`.
1346
1347        Both, `context` and `attr` might be used to find (`context`)
1348        and feed (`attr`) an appropriate file name chooser.
1349
1350        This is a convenience method.
1351        """
1352
[7063]1353    def createFile(filename, f):
1354        """Create file given by f with filename `filename`
1355
1356        Returns a hurry.file.File-based object.
1357        """
1358
1359class IFileStoreHandler(Interface):
1360    """Filestore handlers handle specific files for file stores.
1361
1362    If a file to store/get provides a specific filename, a file store
1363    looks up special handlers for that type of file.
1364
1365    """
1366    def pathFromFileID(store, root, filename):
1367        """Turn file id into path to store.
1368
1369        Returned path should be absolute.
1370        """
1371
1372    def createFile(store, root, filename, file_id, file):
1373        """Return some hurry.file based on `store` and `file_id`.
1374
1375        Some kind of callback method called by file stores to create
1376        file objects from file_id.
1377
1378        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
[7819]1379        ``file_like_obj`` should be a HurryFile, a KofaImageFile or
[7063]1380        similar. ``raw_file`` is the (maybe changed) input file and
1381        ``path`` the relative internal path to store the file at.
1382
1383        Please make sure the ``raw_file`` is opened for reading and
1384        the file descriptor set at position 0 when returned.
1385
1386        This method also gets the raw input file object that is about
1387        to be stored and is expected to raise any exceptions if some
1388        kind of validation or similar fails.
1389        """
[7389]1390
1391class IPDF(Interface):
1392    """A PDF representation of some context.
1393    """
1394
[8257]1395    def __call__(view=None, note=None):
[7389]1396        """Create a bytestream representing a PDF from context.
1397
1398        If `view` is passed in additional infos might be rendered into
1399        the document.
[8257]1400
1401        `note` is optional HTML rendered at bottom of the created
1402        PDF. Please consider the limited reportlab support for HTML,
1403        but using font-tags and friends you certainly can get the
1404        desired look.
[7389]1405        """
[7473]1406
1407class IMailService(Interface):
1408    """A mail service.
1409    """
1410
1411    def __call__():
1412        """Get the default mail delivery.
1413        """
[7576]1414
[9217]1415
[7576]1416class IDataCenterConfig(Interface):
1417    path = Path(
1418        title = u'Path',
[7828]1419        description = u"Directory where the datacenter should store "
1420                      u"files by default (adjustable in web UI).",
[7576]1421        required = True,
1422        )
[8346]1423
[9217]1424#
1425# Asynchronous job handling and related
1426#
1427class IJobManager(IKofaObject):
1428    """A manager for asynchronous running jobs (tasks).
1429    """
1430    def put(job, site=None):
1431        """Put a job into task queue.
[8346]1432
[9217]1433        If no `site` is given, queue job in context of current local
1434        site.
1435
1436        Returns a job_id to identify the put job. This job_id is
1437        needed for further references to the job.
1438        """
1439
1440    def jobs(site=None):
1441        """Get an iterable of jobs stored.
1442        """
1443
1444    def get(job_id, site=None):
1445        """Get the job with id `job_id`.
1446
1447        For the `site` parameter see :meth:`put`.
1448        """
1449
1450    def remove(job_id, site=None):
1451        """Remove job with `job_id` from stored jobs.
1452        """
1453
1454    def start_test_job(site=None):
1455        """Start a test job.
1456        """
1457
1458class IProgressable(Interface):
1459    """A component that can indicate its progress status.
[8346]1460    """
[9217]1461    percent = schema.Float(
1462        title = u'Percent of job done already.',
[8346]1463        )
1464
[9217]1465class IJobContainer(IContainer):
1466    """A job container contains IJob objects.
1467    """
1468
1469class IExportJob(zc.async.interfaces.IJob):
1470    def __init__(site, exporter_name):
1471        pass
1472
[9816]1473    finished = schema.Bool(
1474        title = u'`True` if the job finished.`',
1475        default = False,
1476        )
1477
1478    failed = schema.Bool(
1479        title = u"`True` iff the job finished and didn't provide a file.",
1480        default = None,
1481        )
1482
[9764]1483class IExportJobContainer(IKofaObject):
[9217]1484    """A component that contains (maybe virtually) export jobs.
1485    """
[9718]1486    def start_export_job(exporter_name, user_id, *args, **kwargs):
[9217]1487        """Start asynchronous export job.
1488
1489        `exporter_name` is the name of an exporter utility to be used.
1490
1491        `user_id` is the ID of the user that triggers the export.
1492
[9718]1493        `args` positional arguments passed to the export job created.
1494
1495        `kwargs` keyword arguments passed to the export job.
1496
[9217]1497        The job_id is stored along with exporter name and user id in a
1498        persistent list.
1499
1500        Returns the job ID of the job started.
1501        """
1502
1503    def get_running_export_jobs(user_id=None):
1504        """Get export jobs for user with `user_id` as list of tuples.
1505
1506        Each tuples holds ``<job_id>, <exporter_name>, <user_id>`` in
1507        that order. The ``<exporter_name>`` is the utility name of the
1508        used exporter.
1509
1510        If `user_id` is ``None``, all running jobs are returned.
1511        """
1512
1513    def get_export_jobs_status(user_id=None):
1514        """Get running/completed export jobs for `user_id` as list of tuples.
1515
1516        Each tuple holds ``<raw status>, <status translated>,
1517        <exporter title>`` in that order, where ``<status
1518        translated>`` and ``<exporter title>`` are translated strings
1519        representing the status of the job and the human readable
1520        title of the exporter used.
1521        """
1522
1523    def delete_export_entry(entry):
1524        """Delete the export denoted by `entry`.
1525
1526        Removes `entry` from the local `running_exports` list and also
1527        removes the regarding job via the local job manager.
1528
1529        `entry` is a tuple ``(<job id>, <exporter name>, <user id>)``
1530        as created by :meth:`start_export_job` or returned by
1531        :meth:`get_running_export_jobs`.
1532        """
1533
1534    def entry_from_job_id(job_id):
1535        """Get entry tuple for `job_id`.
1536
1537        Returns ``None`` if no such entry can be found.
1538        """
[9726]1539
1540class IExportContainerFinder(Interface):
1541    """A finder for the central export container.
1542    """
1543    def __call__():
1544        """Return the currently used global or site-wide IExportContainer.
1545        """
[9766]1546
1547class IFilteredQuery(IKofaObject):
1548    """A query for objects.
1549    """
1550
1551    defaults = schema.Dict(
1552        title = u'Default Parameters',
1553        required = True,
1554        )
1555
1556    def __init__(**parameters):
1557        """Instantiate a filtered query by passing in parameters.
1558        """
1559
1560    def query():
1561        """Get an iterable of objects denoted by the set parameters.
1562
1563        The search should be applied to objects inside current
1564        site. It's the caller's duty to set the correct site before.
1565
1566        Result can be any iterable like a catalog result set, a list,
1567        or similar.
1568        """
1569
1570class IFilteredCatalogQuery(IFilteredQuery):
1571    """A catalog-based query for objects.
1572    """
1573
1574    cat_name = schema.TextLine(
1575        title = u'Registered name of the catalog to search.',
1576        required = True,
1577        )
1578
1579    def query_catalog(catalog):
1580        """Query catalog with the parameters passed to constructor.
1581        """
Note: See TracBrowser for help on using the repository browser.