Changeset 7063


Ignore:
Timestamp:
9 Nov 2011, 15:42:45 (13 years ago)
Author:
uli
Message:

Merge changes from branch ulif-extimgstore back into trunk.
Beside external image storage also waeupdocs should work again.

Location:
main/waeup.sirp/trunk
Files:
28 edited
2 copied

Legend:

Unmodified
Added
Removed
  • main/waeup.sirp/trunk

  • main/waeup.sirp/trunk/buildout.cfg

    r6871 r7063  
    5454zope.xmlpickle = 3.4.0
    5555# Require latest version...
    56 Sphinx =
     56Sphinx = 1.0.7
    5757ZODB3 = 3.10.3
    5858docutils = 0.7
  • main/waeup.sirp/trunk/docs/source/developerdocs/api/applicants/api_browser.rst

    r6596 r7063  
    44.. automodule:: waeup.sirp.applicants.browser
    55   :members:
    6    :undoc-members:
     6
     7   .. :undoc-members:
  • main/waeup.sirp/trunk/docs/source/developerdocs/api/index.rst

    r6505 r7063  
    1919   api_permissions
    2020   api_datacenter
     21   api_imagestorage
    2122   api_users
    2223   api_workflow
  • main/waeup.sirp/trunk/src/waeup/sirp/app.py

    r6952 r7063  
    11import grok
    2 from hurry.file.interfaces import IFileRetrieval
    32from zope.authentication.interfaces import IAuthentication
    43from zope.component import getUtilitiesFor
     
    98from waeup.sirp.students.container import StudentsContainer
    109from waeup.sirp.hostels.container import HostelsContainer
    11 from waeup.sirp.imagestorage import ImageStorageFileRetrieval, ImageStorage
    1210from waeup.sirp.interfaces import (
    1311    IUniversity, IWAeUPSIRPPluggable, IObjectUpgradeEvent, )
     
    2826        setup = setup_authentication,)
    2927
    30     grok.local_utility(
    31         ImageStorageFileRetrieval, provides = IFileRetrieval)
    32 
    3328    def __init__(self, *args, **kw):
    3429        super(University, self).__init__(*args, **kw)
     
    4439        self['users'] = UserContainer()
    4540        self['datacenter'] = DataCenter()
    46         self['images'] = ImageStorage()
    4741        self['students'] = StudentsContainer()
    4842        self['configuration'] = ConfigurationContainer()
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py

    r6632 r7063  
    33# Make this a package.
    44from waeup.sirp.applicants.applicant import (
    5     ResultEntry, Applicant, ApplicantFactory, ApplicantTraverser,
     5    ResultEntry, Applicant, ApplicantFactory, ApplicantImageStoreHandler,
     6    get_regno_or_ac, ApplicantImageNameChooser,
    67    )
    78from waeup.sirp.applicants.container import ApplicantsContainer
     
    1415    'Applicant',
    1516    'ApplicantFactory',
     17    'ApplicantImageNameChooser',
     18    'ApplicantImageStoreHandler',
    1619    'ApplicantsContainer',
    1720    'ApplicantsRoot',
    18     'ApplicantTraverser',
    1921    'application_exists',
    2022    'get_applicant_data',
     23    'get_regno_or_ac',
    2124    ]
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py

    r6632 r7063  
    2020## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    2121##
     22import os
    2223import grok
    2324from grok import index
     
    2526from zope.interface import implementedBy
    2627from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
    27 from waeup.sirp.interfaces import IObjectHistory
    2828from waeup.sirp.app import University
     29from waeup.sirp.image import WAeUPImageFile
     30from waeup.sirp.imagestorage import DefaultFileStoreHandler
     31from waeup.sirp.interfaces import (
     32    IObjectHistory, IFileStoreHandler, IFileStoreNameChooser)
     33from waeup.sirp.utils.helpers import attrs_to_fields
    2934from waeup.sirp.applicants.interfaces import (
    30     IResultEntry, IApplicant, IApplicantEdit, default_passport_image,
     35    IResultEntry, IApplicant, IApplicantEdit,
    3136    )
    32 from waeup.sirp.utils.helpers import attrs_to_fields
     37
     38
     39def get_regno_or_ac(context):
     40    reg_no = getattr(context, 'reg_no', None)
     41    if reg_no is None:
     42        return getattr(context, 'access_code', None)
     43    return reg_no
    3344
    3445class ResultEntry(grok.Context):
     
    4960        return
    5061
    51     #def getApplicantsRootLogger(self):
    52     #    return grok.getSite()['applicants'].logger
    53 
    5462    def loggerInfo(self, ob_class, comment=None):
    5563        target = self.__name__
     
    8189    access_code = index.Field(attribute='access_code')
    8290
    83 class ApplicantTraverser(grok.Traverser):
    84     """Get image of the context applicant.
    85 
    86     Each applicant can provide a passport photograph which will be
    87     returned by this traverser if:
    88 
    89     - we request the exact filename of the picture or
    90 
    91     - ask for a picture named 'passport.jpg'.
    92 
    93     If no picture was stored yet, we get a placeholder image when
    94     asking for `passport.jpg`.
    95 
    96     If none of the above applies, we return ``None``, most probably
    97     resulting a :exc:`NotFound` exception.
    98 
    99     """
    100     grok.context(IApplicant)
    101     def traverse(self, name):
    102         passport_filename = getattr(self.context.passport, 'filename', None)
    103         if name == passport_filename:
    104             return self.context.passport
    105         if name == 'passport.jpg':
    106             if self.context.passport is not None:
    107                 return self.context.passport
    108         return
    109 
    11091class ApplicantFactory(grok.GlobalUtility):
    11192    """A factory for applicants.
     
    121102    def getInterfaces(self):
    122103        return implementedBy(Applicant)
     104
     105
     106#: The file id marker for applicant passport images
     107APPLICANT_IMAGE_STORE_NAME = 'img-applicant'
     108
     109class ApplicantImageNameChooser(grok.Adapter):
     110    """A file id chooser for :class:`Applicant` objects.
     111
     112    `context` is an :class:`Applicant` instance.
     113
     114    The :class:`ApplicantImageNameChooser` can build/check file ids
     115    for :class:`Applicant` objects suitable for use with
     116    :class:`ExtFileStore` instances. The delivered file_id contains
     117    the file id marker for :class:`Applicant` object and the
     118    registration number or access code of the context applicant. Also
     119    the name of the connected applicant container will be part of the
     120    generated file id.
     121
     122    This chooser is registered as an adapter providing
     123    :class:`waeup.sirp.interfaces.IFileStoreNameChooser`.
     124
     125    File store name choosers like this one are only convenience
     126    components to ease the task of creating file ids for applicant
     127    objects. You are nevertheless encouraged to use them instead of
     128    manually setting up filenames for applicants.
     129
     130    .. seealso:: :mod:`waeup.sirp.imagestorage`
     131
     132    """
     133    grok.context(IApplicant)
     134    grok.implements(IFileStoreNameChooser)
     135
     136    def checkName(self, name=None):
     137        """Check whether the given name is a valid file id for the context.
     138
     139        Returns ``True`` only if `name` equals the result of
     140        :meth:`chooseName`.
     141        """
     142        return name == self.chooseName()
     143
     144    def chooseName(self, name=None):
     145        """Get a valid file id for applicant context.
     146
     147        *Example:*
     148
     149        For an applicant with registration no. ``'My_reg_no_1234'``
     150        and stored in an applicants container called
     151        ``'mycontainer'``, this chooser would create:
     152
     153          ``'__img-applicant__mycontainer/My_reg_no_1234.jpg'``
     154
     155        meaning that the passport image of this applicant would be
     156        stored in the site-wide file storage in path:
     157
     158          ``mycontainer/My_reg_no_1234.jpg``
     159
     160        If the context applicant has no parent, ``'_default'`` is used
     161        as parent name.
     162        """
     163        parent_name = getattr(
     164            getattr(self.context, '__parent__', None),
     165            '__name__', '_default')
     166        marked_filename = '__%s__%s/%s.jpg' % (
     167            APPLICANT_IMAGE_STORE_NAME,
     168            parent_name, get_regno_or_ac(self.context))
     169        return marked_filename
     170
     171
     172class ApplicantImageStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
     173    """Applicant specific image handling.
     174
     175    This handler knows in which path in a filestore to store applicant
     176    images and how to turn this kind of data into some (browsable)
     177    file object.
     178
     179    It is called from the global file storage, when it wants to
     180    get/store a file with a file id starting with
     181    ``__img-applicant__`` (the marker string for applicant images).
     182
     183    Like each other file store handler it does not handle the files
     184    really (this is done by the global file store) but only computes
     185    paths and things like this.
     186    """
     187    grok.implements(IFileStoreHandler)
     188    grok.name(APPLICANT_IMAGE_STORE_NAME)
     189
     190    def pathFromFileID(self, store, root, file_id):
     191        """All applicants images are filed in directory ``applicants``.
     192        """
     193        marker, filename, basename, ext = store.extractMarker(file_id)
     194        return os.path.join(root, 'applicants', filename)
     195
     196    def createFile(self, store, root, filename, file_id, file):
     197        """Create a browsable file-like object.
     198        """
     199        # possible other actions: check for jpeg format
     200        path = self.pathFromFileID(store, root, file_id)
     201        return file, path, WAeUPImageFile(filename, file_id)
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/authentication.py

    r6670 r7063  
    232232        if ac.state == 'used' and appl_ac != ac.representation:
    233233            return None
    234         if appl_ac is not None and appl_ac != ac.representation:
    235             # This should never happen. It means a catalog error.
    236             return None
     234        # If the following fails we have a catalog error. Bad enough
     235        # to pull emergency break.
     236        assert appl_ac is None or appl_ac == ac.representation
    237237        return ApplicantPrincipalInfo(accesscode)
    238238
     
    256256    systems (regular users, officers, etc.) were set up.
    257257    """
    258     grok.provides(IAuthPluginUtility)
     258    grok.implements(IAuthPluginUtility)
    259259    grok.name('applicants_auth_setup')
    260260
     
    285285
    286286    def unregister(self, pau):
     287        """Unregister applicant specific authentication components from PAU.
     288        """
     289        pau.credentialsPlugins = tuple(
     290            [x for x in list(pau.credentialsPlugins)
     291             if x != 'applicant_credentials'])
     292        pau.authenticatorPlugins = tuple(
     293            [x for x in list(pau.authenticatorPlugins)
     294             if x != 'applicants'])
    287295        return pau
    288296
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py

    r6816 r7063  
    2222"""UI components for basic applicants and related components.
    2323"""
     24import os
    2425import sys
    2526import grok
     
    5354    ManageActionButton, PrimaryNavTab, LeftSidebarLink
    5455    )
    55 from waeup.sirp.image.browser.widget import (
    56     ThumbnailWidget, EncodingImageFileWidget,
    57     )
    58 from waeup.sirp.image.image import createWAeUPImageFile
    59 from waeup.sirp.interfaces import IWAeUPObject, ILocalRolesAssignable
     56from waeup.sirp.interfaces import (
     57    IWAeUPObject, ILocalRolesAssignable, IExtFileStore, IFileStoreNameChooser)
    6058from waeup.sirp.permissions import get_users_with_local_roles
    6159from waeup.sirp.university.interfaces import ICertificate
     
    6866from waeup.sirp.applicants.interfaces import (
    6967    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
    70     IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab
     68    IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
     69    IMAGE_PATH,
    7170    )
    7271from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
     
    425424        # If application has ended and applicant record doesn't exist,
    426425        # logout without marking AC as used
    427         if not pin in self.context.keys() and self.context.enddate < date.today():
     426        if not pin in self.context.keys() and (
     427            self.context.enddate < date.today()):
    428428            self.flash('Application has ended.')
    429429            auth = getUtility(IAuthentication)
     
    433433
    434434        # Mark AC as used (this also fires a pin related transition)
    435         if get_access_code(pin).state == USED:
    436             pass
    437         else:
     435        if get_access_code(pin).state != USED:
    438436            comment = u"AC invalidated"
    439437            # Here we know that the ac is in state initialized so we do not
     
    514512            applicant_object = get_applicant_data(access_code)
    515513            return absoluteURL(applicant_object, self.request) + self.target
    516         #else:
    517         #    return ''
     514        return ''
    518515
    519516class AccessCodeEditLink(AccessCodeViewLink):
     
    534531                return ''
    535532            return absoluteURL(applicant_object, self.request) + self.target
    536         #else:
    537         #    return ''
     533        return ''
    538534
    539535class AccessCodeSlipLink(AccessCodeViewLink):
     
    550546    form_fields = grok.AutoFields(IApplicant).omit(
    551547        'locked').omit('course_admitted')
    552     #form_fields['fst_sit_results'].custom_widget = list_results_display_widget
    553     form_fields['passport'].custom_widget = ThumbnailWidget
    554548    form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
    555549    label = 'Applicant'
    556550    grok.template('form_display')
    557551    pnav = 3
     552
     553    def update(self):
     554        self.passport_url = self.url(self.context, 'passport.jpg')
     555        return
    558556
    559557    @property
     
    582580class PDFActionButton(ManageActionButton):
    583581    grok.context(IApplicant)
    584     #grok.view(DisplayApplicant)
    585582    grok.require('waeup.manageApplications')
    586583    icon = 'actionicon_pdf.png'
     
    624621
    625622    def render(self):
    626         # (0,0),(-1,-1) = whole table
    627         # (0,0),(0,-1) = first column
    628         # (-1,0),(-1,-1) = last column
    629         # (0,0),(-1,0) = first row
    630         # (0,-1),(-1,-1) = last row
    631623        SLIP_STYLE = TableStyle(
    632624            [('VALIGN',(0,0),(-1,-1),'TOP')]
     
    652644        frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
    653645        story.append(Paragraph(self.label, style["Heading2"]))
    654         #story.append(HRFlowable())
    655646        story.append(Spacer(1, 18))
    656647        for msg in self.context.history.messages:
     
    658649            story.append(Paragraph(f_msg, style["Normal"]))
    659650        story.append(Spacer(1, 24))
     651
     652        data = []
     653        # insert passport photograph
     654        img = getUtility(IExtFileStore).getFileByContext(self.context)
     655        if img is None:
     656            img = open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg'), 'rb')
     657        doc_img = Image(img.name, width=4*cm, height=3*cm, kind='bound')
     658        data.append(['', doc_img])
     659
     660        # render widget fields
    660661        self.setUpWidgets()
    661         data = []
    662662        for widget in self.widgets:
    663663            f_label = '<font size=12>%s</font>:' % widget.label.strip()
    664664            f_label = Paragraph(f_label, style["Normal"])
    665             if widget.name != 'form.passport':
    666                 f_text = '<font size=12>%s</font>' % widget()
    667                 f_text = Paragraph(f_text, style["Normal"])
    668                 data.append([f_label,f_text])
    669             else:
    670                 filename = widget._data.file.name
    671                 im = Image(filename,width=4*cm, height=3*cm,kind='bound')
    672                 data.append([f_label,im])
     665            f_text = '<font size=12>%s</font>' % widget()
     666            f_text = Paragraph(f_text, style["Normal"])
     667            data.append([f_label,f_text])
    673668        f_label = '<font size=12>Admitted Course of Study:</font>'
    674669        f_text = '<font size=12>%s</font>' % self.getCourseAdmitted()
     
    698693    target = 'edit_full'
    699694
     695def handle_img_upload(upload, context):
     696    """Handle upload of applicant image.
     697    """
     698    store = getUtility(IExtFileStore)
     699    file_id = IFileStoreNameChooser(context).chooseName()
     700    store.createFile(file_id, upload)
     701    upload.seek(0) # XXX: really neccessary?
     702    return
     703
    700704class EditApplicantFull(WAeUPEditFormPage):
    701705    """A full edit view for applicant data.
     
    705709    grok.require('waeup.manageApplications')
    706710    form_fields = grok.AutoFields(IApplicant)
    707     form_fields['passport'].custom_widget = EncodingImageFileWidget
    708711    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
    709712    grok.template('form_edit')
     
    718721        if upload:
    719722            # We got a fresh upload
    720             image = createWAeUPImageFile(upload.filename, upload)
    721             # This would normally be totally illegal. We set context
    722             # data without the complete form data being
    723             # verified. Normally data is set in methods like `save`
    724             # only, which is only called when all form data is correct
    725             # (and if not, the context is not touched). With images
    726             # and their uploads the problem then is, that the uploaded
    727             # image won't be shown in a partially erranous form
    728             # because the new uploaded image is still not part of the
    729             # context obeject and the image widget cannot work around
    730             # that (it has no access to the new data). For this reason
    731             # we set the image here and not in 'save()' or other
    732             # methods.
    733             self.context.passport = image
    734             upload.seek(0)
     723            handle_img_upload(upload, self.context)
    735724            self.passport_changed = True
    736725        return
     
    760749        changed_fields = self.applyData(self.context, **data)
    761750        changed_fields = changed_fields.values()
    762         fields_string = '+'.join(' + '.join(str(i) for i in b) for b in changed_fields)
     751        fields_string = '+'.join(
     752            ' + '.join(str(i) for i in b) for b in changed_fields)
    763753        self.context._p_changed = True
    764754        form = self.request.form
     
    782772        'screening_score',
    783773        )
    784     form_fields['passport'].custom_widget = EncodingImageFileWidget
    785774    form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
    786775    grok.template('form_edit')
     
    788777    title = u'Your Application Form'
    789778
    790 
    791779    def emitLockMessage(self):
    792780        self.flash('The requested form is locked (read-only).')
     
    802790        if upload:
    803791            # We got a fresh upload
    804             image = createWAeUPImageFile(upload.filename, upload)
    805             # This would normally be totally illegal. We set context
    806             # data without the complete form data being
    807             # verified. Normally data is set in methods like `save`
    808             # only, which is only called when all form data is correct
    809             # (and if not, the context is not touched). With images
    810             # and their uploads the problem then is, that the uploaded
    811             # image won't be shown in a partially erranous form
    812             # because the new uploaded image is still not part of the
    813             # context obeject and the image widget cannot work around
    814             # that (it has no access to the new data). For this reason
    815             # we set the image here and not in 'save()' or other
    816             # methods.
    817             self.context.passport = image
    818             upload.seek(0)
     792            handle_img_upload(upload, self.context)
    819793            self.passport_changed = True
    820794        super(EditApplicantStudent, self).update()
     
    824798        if not self.request.form.get('confirm_passport', False):
    825799            return 'Passport confirmation box not ticked.'
    826         #if len(self.errors) > 0:
    827         #    return 'Form has errors.'
    828800        return False
    829801
    830802    @grok.action('Save')
    831803    def save(self, **data):
    832         #if self.context.locked:
    833         #    self.emitLockMessage()
    834         #    return
    835804        self.applyData(self.context, **data)
    836805        self.context._p_changed = True
     
    840809    @grok.action('Final Submit')
    841810    def finalsubmit(self, **data):
    842         #if self.context.locked:
    843         #    self.emitLockMessage()
    844         #    return
    845811        self.applyData(self.context, **data)
    846812        self.context._p_changed = True
     
    868834    text = 'View application record'
    869835    target = 'index'
     836
     837class PassportImage(grok.View):
     838    """Renders the passport image for applicants.
     839    """
     840    grok.name('passport.jpg')
     841    grok.context(IApplicant)
     842    grok.require('waeup.handleApplication')
     843
     844    def render(self):
     845        # A filename chooser turns a context into a filename suitable
     846        # for file storage.
     847        image = getUtility(IExtFileStore).getFileByContext(self.context)
     848        self.response.setHeader(
     849            'Content-Type', 'image/jpeg')
     850        if image is None:
     851            # show placeholder image
     852            return open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg'), 'rb')
     853        return image
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/form_display.pt

    r6350 r7063  
    1313<table class="zebra">
    1414  <tbody>
     15    <tr><td></td>
     16    <td><img src="" tal:attributes="src view/passport_url" /></td>
     17    </tr>
    1518    <tal:block repeat="widget view/widgets">
    1619      <tr>
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/form_edit.pt

    r6756 r7063  
    4646        </tr>
    4747      </tal:block>
     48      <tr>
     49        <td class="label">Photograph</td>
     50        <td class="field">
     51          <span class="widget">
     52            <img src="passport.jpg" /><br />
     53            <input type="file" name="form.passport" />
     54          </span>
     55      </tr>
    4856      <tr tal:condition="view/manage_applications">
    4957        <td class="label"><label>Application Transition:</label></td>
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py

    r6915 r7063  
    2929
    3030from zope import schema
    31 from zope.interface import Interface, Attribute, provider
     31from zope.interface import Interface, Attribute
    3232from zope.component import getUtilitiesFor
    3333from zope.pluggableauth.interfaces import IPrincipalInfo
    3434from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
    3535from zc.sourcefactory.basic import BasicSourceFactory
    36 from waeup.sirp.image import createWAeUPImageFile
    37 from waeup.sirp.image.schema import ImageFile
    3836from waeup.sirp.interfaces import IWAeUPObject, year_range
    3937from waeup.sirp.university.vocabularies import application_categories
     
    6664    return True
    6765
    68 @provider(schema.interfaces.IContextAwareDefaultFactory)
    69 def default_passport_image(context):
    70     """A default value factory for ImageFile fields.
    71 
    72     Returns some default image as WAeUPImageFile. We cannot set the
    73     default directly in ImageFile fields, as, if we want to set
    74     max_size or min_size as well, some utility lookups are needed
    75     which are not possible during startup.
    76 
    77     Developers which use IContextAwareDefaultFactories like this one
    78     always should make sure that the delivered default meets all
    79     constraints of the field that makes use of this default value
    80     provider.
    81     """
    82     imagefile = createWAeUPImageFile(
    83         'placeholder_m.jpg',
    84         open(os.path.join(IMAGE_PATH, 'placeholder_m.jpg'), 'r')
    85         )
    86     return imagefile
    87 
    8866class ApplicantContainerProviderSource(BasicSourceFactory):
    8967    """A source offering all available applicants container types.
     
    370348        required = False,
    371349        )
    372     passport = ImageFile(
    373         title = u'Passport Photograph',
    374         #default = DEFAULT_PASSPORT_IMAGE_MALE,
    375         defaultFactory = default_passport_image,
    376         description = u'Maximun file size is 20 kB.',
    377         required = True,
    378         max_size = 20480,
    379         )
     350    #passport = ImageFile(
     351    #    title = u'Passport Photograph',
     352    #    #default = DEFAULT_PASSPORT_IMAGE_MALE,
     353    #    defaultFactory = default_passport_image,
     354    #    description = u'Maximun file size is 20 kB.',
     355    #    required = True,
     356    #    max_size = 20480,
     357    #    )
    380358
    381359    #
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/root.py

    r6582 r7063  
    4747        """
    4848        user = get_current_principal()
    49         if user is None:
    50             user = 'system'
    51         elif user.title == 'Applicant':
    52             user = 'applicant'
    53         else:
    54             user = user.id
     49        user = getattr(user, 'id', 'system')
    5550        self.logger.info('%s - %s - %s - %s' % (
    5651                user, target, ob_class, comment))
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_applicant.py

    r6657 r7063  
    2222"""Tests for applicants and related.
    2323"""
     24import grok
    2425import unittest
    25 from StringIO import StringIO
    26 from hurry.file.interfaces import IFileRetrieval
    27 from zope.component import (
    28     provideAdapter, adapts, getGlobalSiteManager, provideUtility)
    29 from zope.component.hooks import setSite
     26from zope.component import adapts, queryUtility
    3027from zope.component.interfaces import IFactory
    3128from zope.interface import verify, implements
    3229from zope.location.interfaces import ILocation
    33 from zope.publisher.base import TestRequest
    34 from zope.publisher.interfaces import NotFound
    35 from waeup.sirp.app import University
    36 from waeup.sirp.image import WAeUPImageFile, createWAeUPImageFile
    3730from waeup.sirp.image.interfaces import IWAeUPImageFile
     31from waeup.sirp.imagestorage import DefaultStorage
     32from waeup.sirp.interfaces import IFileStoreHandler, IFileStoreNameChooser
    3833from waeup.sirp.applicants import (
    39     ResultEntry, Applicant, ApplicantFactory, ApplicantTraverser,
     34    ResultEntry, Applicant, ApplicantFactory, get_regno_or_ac,
     35    ApplicantImageStoreHandler, ApplicantImageNameChooser,
    4036    )
    41 from waeup.sirp.applicants.interfaces import (
    42     IResultEntry, IApplicant,
    43     )
    44 from waeup.sirp.imagestorage import ImageStorageFileRetrieval
    45 from waeup.sirp.testing import (FunctionalTestCase, FunctionalLayer)
     37from waeup.sirp.applicants.interfaces import IResultEntry, IApplicant
     38from waeup.sirp.testing import FunctionalTestCase, FunctionalLayer
    4639
    4740class FakeImageLocation(object):
     
    5144        pass
    5245
     46class HelperTests(FunctionalTestCase):
     47
     48    layer = FunctionalLayer
     49
     50    def test_get_regno_or_ac(self):
     51        # we can get reg_no or access_code of an applicants if it is set
     52        appl1 = Applicant()
     53        appl2 = Applicant()
     54        appl2.reg_no = u'foo'
     55        appl3 = Applicant()
     56        appl3.access_code = u'bar'
     57        appl4 = Applicant()
     58        appl4.reg_no = u'foo'
     59        appl4.access_code = u'bar'
     60        self.assertTrue(
     61            get_regno_or_ac(appl1) is None)
     62        self.assertEqual(
     63            get_regno_or_ac(appl2), u'foo')
     64        self.assertEqual(
     65            get_regno_or_ac(appl3), u'bar')
     66        self.assertEqual(
     67            get_regno_or_ac(appl4), u'foo')
     68        return
     69
     70    def test_image_store_handler_util_accessible(self):
     71        # we can get an IFileStoreHandler utility for applicants
     72        handler = queryUtility(IFileStoreHandler, name='img-applicant')
     73        self.assertTrue(
     74            isinstance(handler, ApplicantImageStoreHandler))
     75        return
     76
     77    def test_image_store_handler(self):
     78        store = DefaultStorage()
     79        handler = queryUtility(IFileStoreHandler, name='img-applicant')
     80        result1 = handler.pathFromFileID(
     81            store, '/fake-root', '__img-applicant__sample.jpg')
     82        result2 = handler.pathFromFileID(
     83            store, '/fake-root', '__img-applicant__dir1/sample.jpg')
     84        result3 = handler.pathFromFileID(
     85            store, '/fake-root', '__img-applicant__dir1/dir2/sample.jpg')
     86        self.assertEqual(
     87            result1, '/fake-root/applicants/sample.jpg')
     88        self.assertEqual(
     89            result2, '/fake-root/applicants/dir1/sample.jpg')
     90        self.assertEqual(
     91            result3, '/fake-root/applicants/dir1/dir2/sample.jpg')
     92        return
     93
     94class ApplicantImageNameChooserTests(FunctionalTestCase):
     95
     96    layer = FunctionalLayer
     97
     98    def test_iface(self):
     99        # make sure we implement promised interfaces
     100        obj = ApplicantImageNameChooser(None) # needs a context normally
     101        verify.verifyClass(IFileStoreNameChooser, ApplicantImageNameChooser)
     102        verify.verifyObject(IFileStoreNameChooser, obj)
     103        return
     104
     105    def test_name_chooser_available(self):
     106        # we can get a name chooser for applicant objects as adapter
     107        appl = Applicant()
     108        chooser = IFileStoreNameChooser(appl)
     109        self.assertTrue(chooser is not None)
     110        return
     111
     112    def test_name_chooser_applicant_wo_container(self):
     113        # we can get an image filename for applicants not in a container
     114        appl = Applicant()
     115        appl.reg_no = u'MY_REG_NO'
     116        chooser = IFileStoreNameChooser(appl)
     117        result = chooser.chooseName()
     118        # the file would be stored in a ``_default`` directory.
     119        self.assertEqual(
     120            result, '__img-applicant___default/MY_REG_NO.jpg')
     121        return
     122
     123    def test_name_chooser_applicant_w_container(self):
     124        appl = Applicant()
     125        appl.reg_no = u'MY_REG_NO'
     126        fake_container = grok.Container()
     127        fake_container.__name__ = 'folder'
     128        fake_container['appl'] = appl
     129        appl.__parent__ = fake_container
     130        chooser = IFileStoreNameChooser(appl)
     131        result = chooser.chooseName()
     132        self.assertEqual(
     133            result, '__img-applicant__folder/MY_REG_NO.jpg')
     134        return
     135
     136    def test_name_chooser_check_name(self):
     137        # we can check file ids for applicants
     138        appl = Applicant()
     139        appl.reg_no = u'MY_REG_NO'
     140        fake_container = grok.Container()
     141        fake_container.__name__ = 'folder'
     142        fake_container['appl'] = appl
     143        appl.__parent__ = fake_container
     144        chooser = IFileStoreNameChooser(appl)
     145        result1 = chooser.checkName('foo')
     146        result2 = chooser.checkName('__img-applicant__folder/MY_REG_NO.jpg')
     147        self.assertEqual(result1, False)
     148        self.assertEqual(result2, True)
     149        return
     150
    53151class ResultEntryTest(unittest.TestCase):
    54152
     
    87185        return
    88186
    89     def test_passport_no_site(self):
    90         # make sure we get a real image stored in passport attr
    91         image = self.applicant.passport.file.read()
    92         self.assertEqual(len(image), 2059)
    93         return
    94 
    95     def test_passport_insite(self):
    96         # make sure we get a real image in passport attr when inside a site.
    97         # When an applicant is created 'inside a site', its passport
    98         # photograph should be put inside the images folder of the
    99         # site, even if it is only a placeholder.
    100         self.getRootFolder()['app'] = University()
    101         app = self.getRootFolder()['app']
    102         setSite(app)
    103         applicant = Applicant()
    104         image = self.applicant.passport.file.read()
    105         self.assertEqual(len(image), 2059)
    106         # The image contains a file_id instead of real image-data
    107         self.assertEqual(self.applicant.passport.data,
    108                          u'b48a1d39bbcb32e955d9ff2dea4ed0e6-1')
    109         assert u'b48a1d39bbcb32e955d9ff2dea4ed0e6' in app['images'].keys()
    110         return
    111 
    112187class ApplicantFactoryTest(FunctionalTestCase):
    113188
     
    116191    def setUp(self):
    117192        super(ApplicantFactoryTest, self).setUp()
    118         # Install a IFileRetrieval utility that returns WAeUPImageFiles.
    119         storage = ImageStorageFileRetrieval()
    120         provideUtility(storage, IFileRetrieval)
    121193        self.factory = ApplicantFactory()
    122194        return
     
    137209        implemented_by = self.factory.getInterfaces()
    138210        assert implemented_by.isOrExtends(IApplicant)
    139 
    140 
    141 class ApplicantTraverserTest(FunctionalTestCase):
    142 
    143     layer = FunctionalLayer
    144 
    145     def setUp(self):
    146         super(ApplicantTraverserTest, self).setUp()
    147         provideAdapter(FakeImageLocation)
    148         self.applicant = Applicant()
    149 
    150         self.request = TestRequest('')
    151         return
    152 
    153     def tearDown(self):
    154         gsm = getGlobalSiteManager()
    155         gsm.unregisterAdapter(FakeImageLocation)
    156         super(ApplicantTraverserTest, self).tearDown()
    157         return
    158 
    159     def test_traverse_wo_passport(self):
    160         # Ask for some attribute not provided
    161         traverser = ApplicantTraverser(
    162             self.applicant, self.request
    163             )
    164         self.assertRaises(
    165             NotFound,
    166             traverser.publishTraverse, self.request, 'passport'
    167             )
    168         return
    169 
    170     def test_traverse_wo_image_passport_jpg(self):
    171         # Ask for applicant pic if we didn't provided one
    172         # We get a placeholder.
    173         traverser = ApplicantTraverser(
    174             self.applicant, self.request
    175             )
    176         result = traverser.publishTraverse(self.request, 'passport.jpg')
    177         self.assertTrue(isinstance(result, FakeImageLocation))
    178         return
    179 
    180     def test_traverse_w_image_passport_jpg(self):
    181         # Ask for applicant pic that's named 'passport.jpg'
    182         traverser = ApplicantTraverser(
    183             self.applicant, self.request
    184             )
    185         self.applicant.passport = createWAeUPImageFile(
    186             'nofile.jpg', StringIO('no-content'))
    187         self.applicant.passport.filename = 'mypic.jpg'
    188         result = traverser.publishTraverse(self.request, 'passport.jpg')
    189         self.assertTrue(isinstance(result, FakeImageLocation))
    190         return
    191 
    192     def test_traverse_w_image_some_jpg(self):
    193         # Ask for applicant pic by correct name
    194         traverser = ApplicantTraverser(
    195             self.applicant, self.request
    196             )
    197         self.applicant.passport = WAeUPImageFile('nofile.jpg', '')
    198         self.applicant.passport.filename = 'mypic.jpg'
    199         result = traverser.publishTraverse(self.request, 'mypic.jpg')
    200         self.assertTrue(isinstance(result, FakeImageLocation))
    201         return
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_authentication.py

    r6730 r7063  
    2424import unittest
    2525from zope.authentication.interfaces import IAuthentication
    26 from zope.component import provideAdapter, getUtility
     26from zope.component import provideAdapter, getUtility, queryUtility
    2727from zope.component.hooks import setSite
    2828from zope.interface import verify
     
    3535from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
    3636from waeup.sirp.app import University
     37from waeup.sirp.interfaces import IAuthPluginUtility
    3738from waeup.sirp.applicants import  ApplicantsContainer, Applicant
    3839from waeup.sirp.applicants.authentication import (
    3940    ApplicantsAuthenticatorPlugin, WAeUPApplicantCredentialsPlugin,
    4041    ApplicantCredentials, AuthenticatedApplicantPrincipalFactory,
    41     ApplicantPrincipalInfo, ApplicantPrincipal,)
     42    ApplicantPrincipalInfo, ApplicantPrincipal, ApplicantsAuthUtility)
    4243
    4344
     
    367368        return
    368369
    369 def test_suite():
    370     suite = unittest.TestSuite()
    371     for testcase in [
    372         AuthenticatorPluginTest, CredentialsPluginTest,
    373         ApplicantCredentialsTest, PrincipalFactoryTest,
    374         PAUSetupTest,
    375         ]:
    376         suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
    377                 testcase
    378                 )
    379         )
    380     return suite
     370class FakePAU(object):
     371    credentialsPlugins = ()
     372    authenticatorPlugins = ()
     373
     374class ApplicantsAuthUtilityTests(FunctionalTestCase):
     375
     376    layer = FunctionalLayer
     377
     378    def test_ifaces(self):
     379        # make sure we fullfill the interface promises
     380        obj = ApplicantsAuthUtility()
     381        verify.verifyClass(IAuthPluginUtility, ApplicantsAuthUtility)
     382        verify.verifyObject(IAuthPluginUtility, obj)
     383        return
     384
     385    def test_utility_available(self):
     386        # we can get an applicant auth utility by lookup
     387        util = queryUtility(IAuthPluginUtility,
     388                            name='applicants_auth_setup')
     389        self.assertTrue(util is not None)
     390        return
     391
     392    def test_register(self):
     393        # make sure we can register additional components
     394        pau = FakePAU()
     395        result = ApplicantsAuthUtility().register(pau)
     396        self.assertEqual(
     397            result.credentialsPlugins, ('applicant_credentials',))
     398        self.assertEqual(
     399            result.authenticatorPlugins, ('applicants',))
     400        return
     401
     402    def test_unregister(self):
     403        # make sure we can unregister applicant auth components
     404        pau = FakePAU()
     405        util = ApplicantsAuthUtility()
     406        pau = util.register(pau)
     407        result = util.unregister(pau)
     408        self.assertEqual(
     409            result.credentialsPlugins, ())
     410        self.assertEqual(
     411            result.authenticatorPlugins, ())
     412        return
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py

    r6816 r7063  
    2828from datetime import datetime, date, timedelta
    2929from mechanize import LinkNotFoundError
    30 from zope.component import createObject
     30from zope.component import createObject, getUtility
    3131from zope.component.hooks import setSite, clearSite
    3232from zope.security.interfaces import Unauthorized
     
    3737from waeup.sirp.applicants.container import ApplicantsContainer
    3838from waeup.sirp.applicants.applicant import Applicant
     39from waeup.sirp.interfaces import IExtFileStore, IFileStoreNameChooser
    3940from waeup.sirp.university.faculty import Faculty
    4041from waeup.sirp.university.department import Department
     
    350351        self.browser.getControl(name="SUBMIT").click()
    351352        pin = self.pins[2]
    352         appl = self.getRootFolder()['app']['applicants']['app2009']
    353         appl = appl[pin]
    354         passp = appl.passport
    355         passp_len = len(passp.file.read())
    356         self.assertEqual(passp_len, PH_LEN)
     353        #appl = self.getRootFolder()['app']['applicants']['app2009']
     354        #appl = appl[pin]
     355        #passp = appl.passport
     356        #passp_len = len(passp.file.read())
     357        #self.assertEqual(passp_len, PH_LEN)
    357358        #image_url = "%s/%s" % (self.browser.url, 'placeholder.jpg')
    358359        image_url = "%s/%s" % (self.browser.url, 'passport.jpg')
     
    639640        # There is a correct <img> link included
    640641        self.assertTrue(
    641             '<img src="placeholder_m.jpg" />' in self.browser.contents)
     642            '<img src="passport.jpg" />' in self.browser.contents)
    642643        # Browsing the link shows a real image
    643         self.browser.open(self.image_url('placeholder_m.jpg'))
     644        self.browser.open(self.image_url('passport.jpg'))
    644645        self.assertEqual(
    645646            self.browser.headers['content-type'], 'image/jpeg')
    646647        self.assertEqual(len(self.browser.contents), PH_LEN)
    647648
    648     def test_after_login_default_stored_in_imagestorage(self):
     649    def DISABLEDtest_after_login_default_stored_in_imagestorage(self):
    649650        # After login the applicants placeholder image is stored in
    650651        # an imagestorage
     
    672673        # There is a correct <img> link included
    673674        self.assertTrue(
    674             '<img src="placeholder_m.jpg" />' in self.browser.contents)
     675            '<img src="passport.jpg" />' in self.browser.contents)
    675676        # Browsing the link shows a real image
    676         self.browser.open(self.image_url('placeholder_m.jpg'))
     677        self.browser.open(self.image_url('passport.jpg'))
    677678        self.assertEqual(
    678679            self.browser.headers['content-type'], 'image/jpeg')
    679680        self.assertEqual(len(self.browser.contents), PH_LEN)
    680 
    681     def test_after_submit_default_stored_in_imagestorage(self):
    682         # After submitting an applicant form the default image is
    683         # correctly stored in an imagestorage
    684         self.login()
    685         self.browser.getControl("Save").click() # submit form
    686         storage = self.app['images']
    687         self.assertEqual(
    688             [x for x in storage.keys()],
    689             [u'b48a1d39bbcb32e955d9ff2dea4ed0e6'])
    690         file_id = self.applicant.passport.data
    691         self.assertEqual(
    692             file_id, u'b48a1d39bbcb32e955d9ff2dea4ed0e6-1')
    693         # The stored image can be fetched
    694         fd = storage.retrieveFile(file_id)
    695         file_len = len(fd.read())
    696         self.assertEqual(file_len, PH_LEN)
    697681
    698682    def test_uploaded_image_browsable_w_errors(self):
     
    709693        # There is a correct <img> link included
    710694        self.assertTrue(
    711             '<img src="myphoto.jpg" />' in self.browser.contents)
     695            '<img src="passport.jpg" />' in self.browser.contents)
    712696        # Browsing the link shows a real image
    713         self.browser.open(self.image_url('myphoto.jpg'))
     697        self.browser.open(self.image_url('passport.jpg'))
    714698        self.assertEqual(
    715699            self.browser.headers['content-type'], 'image/jpeg')
     
    726710        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
    727711        self.browser.getControl("Save").click() # submit form
    728         storage = self.app['images']
    729         self.assertTrue(
    730             u'18e57c7eac6ca7fb15b54b5b2bd4106d' in storage.keys())
    731         # The stored image can be fetched
    732         fd = storage.retrieveFile(u'18e57c7eac6ca7fb15b54b5b2bd4106d-1')
    733         file_len = len(fd.read())
    734         self.assertEqual(file_len, 31)
    735         # The image uploaded belongs to the applicant
    736         file_id = self.applicant.passport.data
     712        storage = getUtility(IExtFileStore)
     713        file_id = IFileStoreNameChooser(self.applicant).chooseName()
     714        pseudo_image.seek(0) # reset our file data source
    737715        self.assertEqual(
    738             file_id, u'18e57c7eac6ca7fb15b54b5b2bd4106d-1')
     716            storage.getFile(file_id).read(), pseudo_image.read())
     717        return
    739718
    740719    def test_uploaded_image_browsable_wo_errors(self):
     
    751730        # There is a correct <img> link included
    752731        self.assertTrue(
    753             '<img src="myphoto.jpg" />' in self.browser.contents)
     732            '<img src="passport.jpg" />' in self.browser.contents)
    754733        # Browsing the link shows a real image
    755         self.browser.open(self.image_url('myphoto.jpg'))
     734        self.browser.open(self.image_url('passport.jpg'))
    756735        self.assertEqual(
    757736            self.browser.headers['content-type'], 'image/jpeg')
     
    769748        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
    770749        self.browser.getControl("Save").click() # submit form
    771         storage = self.app['images']
    772         self.assertTrue(
    773             u'18e57c7eac6ca7fb15b54b5b2bd4106d' in storage.keys())
     750        storage = getUtility(IExtFileStore)
     751        file_id = IFileStoreNameChooser(self.applicant).chooseName()
    774752        # The stored image can be fetched
    775         fd = storage.retrieveFile(u'18e57c7eac6ca7fb15b54b5b2bd4106d-1')
    776         #fd = storage.retrieveFile(file_id)
     753        fd = storage.getFile(file_id)
    777754        file_len = len(fd.read())
    778755        self.assertEqual(file_len, 31)
    779         # The image uploaded belongs to the applicant
    780         file_id = self.applicant.passport.data
    781         self.assertEqual(
    782             file_id, u'18e57c7eac6ca7fb15b54b5b2bd4106d-1')
    783756
    784757    def test_uploaded_images_equal(self):
     
    797770        file_ctrl = ctrl.mech_control
    798771        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
    799         passport0 = self.applicant.passport
     772        file_id = IFileStoreNameChooser(self.applicant).chooseName()
     773        setSite(self.app)
     774        passport0 = getUtility(IExtFileStore).getFile(file_id)
    800775        self.browser.getControl("Save").click() # submit form with changed pic
    801         passport1 = self.applicant.passport
     776        passport1 = getUtility(IExtFileStore).getFile(file_id).read()
    802777        self.browser.getControl("Save").click() # submit form w/o changes
    803         passport2 = self.applicant.passport
     778        passport2 = getUtility(IExtFileStore).getFile(file_id).read()
     779        self.assertTrue(passport0 is None)
    804780        self.assertTrue(passport0 != passport1)
    805781        self.assertTrue(passport1 == passport2)
    806         self.assertTrue(passport1 is passport2)
    807782        return
    808783
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_interfaces.py

    r6340 r7063  
    2525import unittest
    2626from zc.sourcefactory.browser.source import FactoredTerms
    27 from zope.browser.interfaces import ITerms
    28 from zope.component import getMultiAdapter
    29 from zope.interface.verify import verifyClass, verifyObject
    3027from zope.publisher.browser import TestRequest
    31 from waeup.sirp.applicants import interfaces
    32 from waeup.sirp.applicants.vocabularies import (
    33     APPLICATION_TYPES, application_types_vocab, GenderSource,
    34     )
     28from waeup.sirp.applicants.vocabularies import application_types_vocab
     29from waeup.sirp.students.vocabularies import GenderSource
    3530
    3631class ApplicationCategoriesTestCase(unittest.TestCase):
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_root.py

    r6659 r7063  
    3939    )
    4040from waeup.sirp.testing import (
    41     FunctionalLayer, FunctionalTestCase, get_all_loggers, remove_new_loggers,
    42     remove_logger)
     41    FunctionalLayer, FunctionalTestCase, remove_logger)
    4342
    4443
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/vocabularies.py

    r6744 r7063  
    11"""Vocabularies and sources for the application section.
    22"""
    3 from datetime import datetime
    43from zope.component import getUtility
    54from zope.catalog.interfaces import ICatalog
    6 from zc.sourcefactory.basic import BasicSourceFactory
    7 from zc.sourcefactory.contextual import BasicContextualSourceFactory
    85from waeup.sirp.interfaces import SimpleWAeUPVocabulary
    9 from waeup.sirp.students.lgas import LGAS
    10 from waeup.sirp.students.vocabularies import (
    11     CertificateSource, GenderSource)
     6from waeup.sirp.students.vocabularies import CertificateSource
    127
    138#: Types of applications we support.
  • main/waeup.sirp/trunk/src/waeup/sirp/applicants/workflow.py

    r6644 r7063  
    22"""
    33import grok
    4 from datetime import datetime
    54from hurry.workflow.workflow import Transition, WorkflowState, NullCondition
    65from hurry.workflow.interfaces import IWorkflowState, IWorkflowTransitionEvent
  • main/waeup.sirp/trunk/src/waeup/sirp/browser/browser.txt

    r6917 r7063  
    10641064
    10651065  >>> sorted(os.listdir(uploadpath))
    1066   ['finished', 'logs', 'unfinished']
     1066  ['finished', 'logs', 'media', 'unfinished']
    10671067
    10681068
     
    11051105
    11061106  >>> sorted(os.listdir(uploadpath))
    1107   ['finished', 'logs', 'myfaculties_zope.mgr.csv', 'unfinished']
     1107  ['finished', 'logs', 'media', 'myfaculties_zope.mgr.csv', 'unfinished']
    11081108
    11091109We create and upload also a CSV file containing departments:
  • main/waeup.sirp/trunk/src/waeup/sirp/browser/resources.py

    r6894 r7063  
    3838#:
    3939#: * In the `update()` method of the responsible view/page/form
    40 #:   require the JavaScript code to be rendered into the page:
     40#:   require the JavaScript code to be rendered into the page::
    4141#:
    4242#:     from waeup.sirp.browser.resources import datepicker
    43 #:     ...
    44 #:     class MyPage(...):
    45 #:       ...
     43#:     # ...
     44#:     class MyPage(...):
     45#:       # ...
    4646#:       def update(self):
    4747#:         datepicker.need()
     
    5151#:
    5252#: * In your HTML code add some ``<input>`` or ``<div>`` tag with
    53 #:   ``class`` set to ``datepicker``, like this::
     53#:   ``class`` set to ``datepicker``, like this:
     54#:
     55#:   .. code-block:: html
    5456#:
    5557#:     <input type="text" name="entrydate" class="datepicker" />
     
    8587#: ``"datepicker-de-year"``
    8688#:    same but also adds a select field for year
    87 #: 
     89#:
    8890#: ``"datepicker-us"``
    8991#:    renders date in format ``MM/DD/YYYY`` (U.S. format)
     
    9799#: If you want generate alert or confirm messages then you have to perform
    98100#: two steps:
    99 #: * In the `update()` method of the responsible view/page/form
    100 #:   require the JavaScript code to be rendered into the page:
     101#:
     102#: * In the `update()` method of the responsible view/page/form
     103#:   require the JavaScript code to be rendered into the page::
    101104#:
    102105#:     from waeup.sirp.browser.resources import datepicker
    103 #:     ...
    104 #:     class MyPage(...):
    105 #:       ...
     106#:     # ...
     107#:     class MyPage(...):
     108#:       # ...
    106109#:       def update(self):
    107110#:         warning.need()
     
    109112#:   This way all required JavaScripts will be rendered correctly
    110113#:   into the HTML page generated.
    111 #: 
     114#:
    112115#:   In your HTML code add some <input> or <a> tag with onClick event:
    113116#:
    114 #:   <input type="submit" name="xyz" value="abc"/>
    115 #:          onclick="return confirmPost('Are you sure?')">
     117#:   .. code-block:: html
     118#:
     119#:        <input type="submit" name="xyz" value="abc"/>
     120#:               onclick="return confirmPost('Are you sure?')">
    116121warning = ResourceInclusion(waeup_sirp, 'warning.js')
    117122
    118123#: If you have many select boxes in a from which have to be selected at the same
    119 # time you have to perform
    120 #: two steps:
    121 #: * In the `update()` method of the responsible view/page/form
    122 #:   require the JavaScript code to be rendered into the page:
     124#: time you have to perform two steps:
     125#:
     126#: * In the `update()` method of the responsible view/page/form
     127#:   require the JavaScript code to be rendered into the page::
    123128#:
    124129#:     from waeup.sirp.browser.resources import toggleall
    125 #:     ...
    126 #:     class MyPage(...):
    127 #:       ...
     130#:     # ...
     131#:     class MyPage(...):
     132#:       # ...
    128133#:       def update(self):
    129134#:         toggleall.need()
     
    134139#:   In your HTML code add some <input> tag with onClick event:
    135140#:
    136 #:   <input type="checkbox" onClick="toggle(this, 'entries')" />
    137 
     141#:   .. code-block:: html
     142#:
     143#:    <input type="checkbox" onClick="toggle(this, 'entries')" />
    138144toggleall = ResourceInclusion(waeup_sirp, 'toggleall.js')
    139145
     
    141147#: ``tabs``.
    142148#:
    143 #:
    144149#: If you want jQuery tabs for some <div>s in your page then you
    145150#: have to perform two steps:
    146151#:
    147152#: * In the `update()` method of the responsible view/page/form
    148 #:   require the JavaScript code to be rendered into the page:
     153#:   require the JavaScript code to be rendered into the page::
    149154#:
    150155#:     from waeup.sirp.browser.resources import tabs
    151 #:     ...
    152 #:     class MyPage(...):
    153 #:       ...
     156#:     # ...
     157#:     class MyPage(...):
     158#:       # ...
    154159#:       def update(self):
    155160#:         tabs.need()
     
    159164#:
    160165#: * In your HTML code add some ``<div>`` tag with ``class`` set to
    161 #    ``tabs``, like this::
    162 #:   
     166#:   ``tabs``, like this:
     167#:
     168#:    .. code-block:: html
     169#:
    163170#:      <div id="tabs">
    164171#:        <ul>
     
    195202#: A stylesheet for datatables
    196203datatables_css = ResourceInclusion(waeup_sirp, 'datatables.css')
    197                                  
     204
    198205#: A resource that turns HTML tables into sortable, searchable and :
    199206#: multi-page widgets using jQuery and the dataTables plugin available
     
    204211#:
    205212#: * In the `update()` method of the responsible view/page/form
    206 #:   require the JavaScript code to be rendered into the page:
     213#:   require the JavaScript code to be rendered into the page::
    207214#:
    208215#:     from waeup.sirp.browser.resources import datatable
    209 #:     ...
    210 #:     class MyPage(...):
    211 #:       ...
     216#:     # ...
     217#:     class MyPage(...):
     218#:       # ...
    212219#:       def update(self):
    213220#:         datatable.need()
     
    217224#:
    218225#: * Assign a ``display dataTable`` class to the HTML table you want to tune:
    219 #:   
     226#:
     227#:   .. code-block:: html
     228#:
    220229#:      <table class="display dataTable">
    221230#:        <thead>
     
    246255#: Register basic YUI based CSS as a resource.
    247256reset_fonts_grids = ResourceInclusion(
    248     waeup_sirp, 'yuirfg.css')   
    249    
     257    waeup_sirp, 'yuirfg.css')
     258
    250259yuiapp_css = ResourceInclusion(
    251260    waeup_sirp, 'yuiapp.css',
    252     depends=[reset_fonts_grids])     
     261    depends=[reset_fonts_grids])
    253262
    254263#: Register basic WAeUP base CSS (which is based on ``yuiapp.css`` as a
  • main/waeup.sirp/trunk/src/waeup/sirp/browser/theming.py

    r6894 r7063  
    8888    A named global utility implementing
    8989
    90       :class:`zope.schema.interfaces.IVocabularyFactory
     90      :class:`zope.schema.interfaces.IVocabularyFactory`
    9191
    9292    and registered under the name
    9393
    9494      'waeup.sirp.browser.theming.ThemesVocabulary'
    95    
     95
    9696    Interface fields that wish to provide a list of available themes
    9797    can require a 'named vocabulary', i.e. set:
     
    107107    grok.implements(IVocabularyFactory)
    108108    grok.name('waeup.sirp.browser.theming.ThemesVocabulary')
    109    
     109
    110110    def __call__(self, context):
    111111        """Deliver a vocabulary of available themes.
  • main/waeup.sirp/trunk/src/waeup/sirp/imagestorage.py

    r6980 r7063  
    2121##
    2222"""A storage for image files.
     23
     24A few words about storing files with ``waeup.sirp``. The need for this
     25feature arised initially from the need to store passport files for
     26applicants and students. These files are dynamic (can be changed
     27anytime), mean a lot of traffic and cost a lot of memory/disk space.
     28
     29**Design Basics**
     30
     31While one *can* store images and similar 'large binary objects' aka
     32blobs in the ZODB, this approach quickly becomes cumbersome and
     33difficult to understand. The worst approach here would be to store
     34images as regular byte-stream objects. ZODB supports this but
     35obviously access is slow (data must be looked up in the one
     36``Data.fs`` file, each file has to be sent to the ZEO server and back,
     37etc.).
     38
     39A bit less worse is the approach to store images in the ZODB but as
     40Blobs. ZODB supports storing blobs in separate files in order to
     41accelerate lookup/retrieval of these files. The files, however, have
     42to be sent to the ZEO server (and back on lookups) which means a
     43bottleneck and will easily result in an increased number of
     44``ConflictErrors`` even on simple reads.
     45
     46The advantage of both ZODB-geared approaches is, of course, complete
     47database consistency. ZODB will guarantee that your files are
     48available under some object name and can be handled as any other
     49Python object.
     50
     51Another approach is to leave the ZODB behind and to store images and
     52other files in filesystem directly. This is faster (no ZEO contacts,
     53etc.), reduces probability of `ConflictErrors`, keeps the ZODB
     54smaller, and enables direct access (over filesystem) to the
     55files. Furthermore steps might be better understandable for
     56third-party developers. We opted for this last option.
     57
     58**External File Store**
     59
     60Our implementation for storing-files-API is defined in
     61:class:`ExtFileStore`. An instance of this file storage (which is also
     62able to store non-image files) is available at runtime as a global
     63utility implementing :class:`waeup.sirp.interfaces.IExtFileStore`.
     64
     65The main task of this central component is to maintain a filesystem
     66root path for all files to be stored. It also provides methods to
     67store/get files under certain file ids which identify certain files
     68locally.
     69
     70So, to store a file away, you can do something like this:
     71
     72  >>> from StringIO import StringIO
     73  >>> from zope.component import getUtility
     74  >>> from waeup.sirp.interfaces import IExtFileStore
     75  >>> store = getUtility(IExtFileStore)
     76  >>> store.createFile('myfile.txt', StringIO('some file content'))
     77
     78All you need is a filename and the file-like object containing the
     79real file data.
     80
     81This will store the file somewhere (you shouldn't make too much
     82assumptions about the real filesystem path here).
     83
     84Later, we can get the file back like this:
     85
     86  >>> store.getFile('myfile.txt')
     87  <open file ...>
     88
     89What we get back is a file or file-like object already opened for
     90reading:
     91
     92  >>> store.getFile('myfile.txt').read()
     93  'some file content'
     94
     95**Handlers: Special Places for Special Files**
     96
     97The file store supports special handling for certain files. For
     98example we want applicant images to be stored in a different directory
     99than student images, etc. Because the file store cannot know all
     100details about these special tratment of certain files, it looks up
     101helpers (handlers) to provide the information it needs for really
     102storing the files at the correct location.
     103
     104That a file stored in filestore needs special handling can be
     105indicated by special filenames. These filenames start with a marker like
     106this::
     107
     108  __<MARKER-STRING>__real-filename.jpg
     109
     110Please note the double underscores before and after the marker
     111string. They indicate that all in between is a marker.
     112
     113If you store a file in file store with such a filename (we call this a
     114`file_id` to distuingish it from real world filenames), the file store
     115will look up a handler for ``<MARKER-STRING>`` and pass it the file to
     116store. The handler then will return the internal path to store the
     117file and possibly do additional things as well like validating the
     118file or similar.
     119
     120Examples for such a file store handler can be found in the
     121:mod:`waeup.sirp.applicants.applicant` module. Please see also the
     122:class:`DefaultFileStoreHandler` class below for more details.
     123
     124The file store looks up handlers by utility lookups: it looks for a
     125named utiliy providing
     126:class:`waeup.sirp.interfaces.IFileStoreHandler` and named like the
     127marker string (without leading/trailing underscores) in lower
     128case. For example if the file id would be
     129
     130  ``__IMG_USER__manfred.jpg``
     131
     132then the looked up utility should be registered under name
     133
     134  ``img_user``
     135
     136and provide :class:`waeup.sirp.interfaces.IFileStoreHandler`. If no
     137such utility can be found, a default handler is used instead
     138(see :class:`DefaultFileStoreHandler`).
     139
     140**Context Adapters: Knowing Your Family**
     141
     142Often the internal filename or file id of a file depends on a
     143context. For example when we store passport photographs of applicants,
     144then each image belongs to a certain applicant instance. It is not
     145difficult to maintain such a connection manually: Say every applicant
     146had an id, then we could put this id into the filename as well and
     147would build the filename to store/get the connected file by using that
     148filename. You then would create filenames of a format like this::
     149
     150  __<MARKER-STRING>__applicant0001.jpg
     151
     152where ``applicant0001`` would tell exactly which applicant you can see
     153on the photograph. You notice that the internal file id might have
     154nothing to do with once uploaded filenames. The id above could have
     155been uploaded with filename ``manfred.jpg`` but with the new file id
     156we are able to find the file again later.
     157
     158Unfortunately it might soon get boring or cumbersome to retype this
     159building of filenames for a certain type of context, especially if
     160your filenames take more of the context into account than only a
     161simple id.
     162
     163Therefore you can define filename building for a context as an adapter
     164that then could be looked up by other components simply by doing
     165something like:
     166
     167  >>> from waeup.sirp.interfaces import IFileStoreNameChooser
     168  >>> file_id = IFileStoreNameChooser(my_context_obj)
     169
     170If you later want to change the way file ids are created from a
     171certain context, you only have to change the adapter implementation
     172accordingly.
     173
     174Note, that this is only a convenience component. You don't have to
     175define context adapters but it makes things easier for others if you
     176do, as you don't have to remember the exact file id creation method
     177all the time and can change things quick and in only one location if
     178you need to do so.
     179
     180Please see the :class:`FileStoreNameChooser` default implementation
     181below for details.
     182
    23183"""
    24184import grok
    25 import hashlib
    26185import os
    27 import transaction
    28 import warnings
    29 from StringIO import StringIO
    30 from ZODB.blob import Blob
    31 from persistent import Persistent
     186import tempfile
     187from hurry.file import HurryFile
    32188from hurry.file.interfaces import IFileRetrieval
    33 from waeup.sirp.image import WAeUPImageFile
    34 from waeup.sirp.utils.helpers import cmp_files
    35 
    36 def md5digest(fd):
    37     """Get an MD5 hexdigest for the file stored in `fd`.
    38 
    39     `fd`
    40       a file object open for reading.
    41 
     189from zope.component import queryUtility
     190from zope.interface import Interface
     191from waeup.sirp.interfaces import (
     192    IFileStoreNameChooser, IExtFileStore, IFileStoreHandler,)
     193
     194class FileStoreNameChooser(grok.Adapter):
     195    """Default file store name chooser.
     196
     197    File store name choosers pick a file id, a string, for a certain
     198    context object. They are normally registered as adapters for a
     199    certain content type and know how to build the file id for this
     200    special type of context.
     201
     202    Provides the :class:`waeup.sirp.interfaces.IFileStoreNameChooser`
     203    interface.
     204
     205    This default file name chosser accepts almost every name as long
     206    as it is a string or unicode object.
    42207    """
    43     return hashlib.md5(fd.read()).hexdigest()
    44 
    45 class Basket(grok.Container):
    46     """A basket holds a set of image files with same hash.
     208    grok.context(Interface)
     209    grok.implements(IFileStoreNameChooser)
     210
     211    def checkName(self, name):
     212        """Check whether an object name is valid.
     213
     214        Raises a user error if the name is not valid.
     215
     216        For the default file store name chooser any name is valid.
     217        """
     218        if isinstance(name, basestring):
     219            return True
     220        return False
     221
     222    def chooseName(self, name):
     223        """Choose a unique valid name for the object.
     224
     225        The given name and object may be taken into account when
     226        choosing the name.
     227
     228        chooseName is expected to always choose a valid name (that
     229        would pass the checkName test) and never raise an error.
     230
     231        For this default name chooser we return the given name if it
     232        is valid or ``unknown_file`` else.
     233        """
     234        if self.checkName(name):
     235            return name
     236        return u'unknown_file'
     237
     238class ExtFileStore(object):
     239    """External file store.
     240
     241    External file stores are meant to store files 'externally' of the
     242    ZODB, i.e. in filesystem.
     243
     244    Most important attribute of the external file store is the `root`
     245    path which gives the path to the location where files will be
     246    stored within.
     247
     248    By default `root` is a ``'media/'`` directory in the root of the
     249    datacenter root of a site.
     250
     251    The `root` attribute is 'read-only' because you normally don't
     252    want to change this path -- it is dynamic. That means, if you call
     253    the file store from 'within' a site, the root path will be located
     254    inside this site (a :class:`waeup.sirp.University` instance). If
     255    you call it from 'outside' a site some temporary dir (always the
     256    same during lifetime of the file store instance) will be used. The
     257    term 'temporary' tells what you can expect from this path
     258    persistence-wise.
     259
     260    If you insist, you can pass a root path on initialization to the
     261    constructor but when calling from within a site afterwards, the
     262    site will override your setting for security measures. This way
     263    you can safely use one file store for different sites in a Zope
     264    instance simultanously and files from one site won't show up in
     265    another.
     266
     267    An ExtFileStore instance is available as a global utility
     268    implementing :class:`waeup.sirp.interfaces.IExtFileStore`.
     269
     270    To add and retrieve files from the storage, use the appropriate
     271    methods below.
    47272    """
    48273
    49     def _del(self):
    50         """Remove temporary files associated with local blobs.
    51 
    52         A basket holds files as Blob objects. Unfortunately, if a
    53         basket was not committed (put into ZODB), those blobs linger
    54         around as real files in some temporary directory and won't be
    55         removed.
    56 
    57         This is a helper function to remove all those uncommitted
    58         blobs that has to be called explicitly, for instance in tests.
    59         """
    60         key_list = list(self.keys())
    61         for key in key_list:
    62             item = self[key]
    63             if getattr(item, '_p_oid', None):
    64                 # Don't mess around with blobs in ZODB
    65                 continue
    66             fd = item.open('r')
    67             name = getattr(fd, 'name', None)
    68             fd.close()
    69             if name is not None and os.path.exists(name):
    70                 os.unlink(name)
    71             del self[key]
     274    grok.implements(IExtFileStore)
     275
     276    _root = None
     277
     278    @property
     279    def root(self):
     280        """Root dir of this storage.
     281
     282        The root dir is a readonly value determined dynamically. It
     283        holds media files for sites or other components.
     284
     285        If a site is available we return a ``media/`` dir in the
     286        datacenter storage dir.
     287
     288        Otherwise we create a temporary dir which will be remembered
     289        on next call.
     290
     291        If a site exists and has a datacenter, it has always
     292        precedence over temporary dirs, also after a temporary
     293        directory was created.
     294
     295        Please note that retrieving `root` is expensive. You might
     296        want to store a copy once retrieved in order to minimize the
     297        number of calls to `root`.
     298
     299        """
     300        site = grok.getSite()
     301        if site is not None:
     302            root = os.path.join(site['datacenter'].storage, 'media')
     303            return root
     304        if self._root is None:
     305            self._root = tempfile.mkdtemp()
     306        return self._root
     307
     308    def __init__(self, root=None):
     309        self._root = root
    72310        return
    73311
    74     def getInternalId(self, fd):
    75         """Get the basket-internal id for the file stored in `fd`.
    76 
    77         `fd` must be a file open for reading. If an (byte-wise) equal
    78         file can be found in the basket, its internal id (basket id)
    79         is returned, ``None`` otherwise.
    80         """
    81         fd.seek(0)
    82         for key, val in self.items():
    83             fd_stored = val.open('r')
    84             file_len = os.stat(fd_stored.name)[6]
    85             if file_len == 0:
    86                 # Nasty workaround. Blobs seem to suffer from being emptied
    87                 # accidentally.
    88                 site = grok.getSite()
    89                 if site is not None:
    90                     site.logger.warn(
    91                         'Empty Blob detected: %s' % fd_stored.name)
    92                 warnings.warn("EMPTY BLOB DETECTED: %s" % fd_stored.name)
    93                 fd_stored.close()
    94                 val.open('w').write(fd.read())
    95                 return key
    96             fd_stored.seek(0)
    97             if cmp_files(fd, fd_stored):
    98                 fd_stored.close()
    99                 return key
    100             fd_stored.close()
    101         return None
    102 
    103     @property
    104     def curr_id(self):
    105         """The current basket id.
    106 
    107         An integer number which is not yet in use. If there are
    108         already `maxint` entries in the basket, a :exc:`ValueError` is
    109         raised. The latter is _highly_ unlikely. It would mean to have
    110         more than 2**32 hash collisions, i.e. so many files with the
    111         same MD5 sum.
    112         """
    113         num = 1
    114         while True:
    115             if str(num) not in self.keys():
    116                 return str(num)
    117             num += 1
    118             if num <= 0:
    119                 name = getattr(self, '__name__', None)
    120                 raise ValueError('Basket full: %s' % name)
    121 
    122     def storeFile(self, fd, filename):
    123         """Store the file in `fd` into the basket.
    124 
    125         The file will be stored in a Blob.
    126         """
    127         fd.seek(0)
    128         internal_id = self.getInternalId(fd) # Moves file pointer!
    129         if internal_id is None:
    130             internal_id = self.curr_id
    131             fd.seek(0)
    132             self[internal_id] = Blob()
    133             transaction.commit() # Urgently needed to make the Blob
    134                                  # persistent. Took me ages to find
    135                                  # out that solution, which makes some
    136                                  # design flaw in ZODB Blobs likely.
    137             self[internal_id].open('w').write(fd.read())
    138             fd.seek(0)
    139             self._p_changed = True
    140         return internal_id
    141 
    142     def retrieveFile(self, basket_id):
    143         """Retrieve a file open for reading with basket id `basket_id`.
    144 
    145         If there is no such id, ``None`` is returned. It is the
    146         callers responsibility to close the open file.
    147         """
    148         if basket_id in self.keys():
    149             return self[basket_id].open('r')
    150         return None
    151 
    152 class ImageStorage(grok.Container):
    153     """A container for image files.
     312    def getFile(self, file_id):
     313        """Get a file stored under file ID `file_id`.
     314
     315        Returns a file already opened for reading.
     316
     317        If the file cannot be found ``None`` is returned.
     318
     319        This methods takes into account registered handlers for any
     320        marker put into the file_id.
     321
     322        .. seealso:: :class:`DefaultFileStoreHandler`
     323        """
     324        marker, filename, base, ext = self.extractMarker(file_id)
     325        handler = queryUtility(IFileStoreHandler, name=marker,
     326                               default=DefaultFileStoreHandler())
     327        path = handler.pathFromFileID(self, self.root, file_id)
     328        if not os.path.exists(path):
     329            return None
     330        fd = open(path, 'rb')
     331        return fd
     332
     333    def getFileByContext(self, context):
     334        """Get a file for given context.
     335
     336        Returns a file already opened for reading.
     337
     338        If the file cannot be found ``None`` is returned.
     339
     340        This method takes into account registered handlers and file
     341        name choosers for context types.
     342
     343        This is a convenience method that internally calls
     344        :meth:`getFile`.
     345
     346        .. seealso:: :class:`FileStoreNameChooser`,
     347                     :class:`DefaultFileStoreHandler`.
     348        """
     349        file_id = IFileStoreNameChooser(context).chooseName()
     350        return self.getFile(file_id)
     351
     352    def createFile(self, filename, f):
     353        """Store a file.
     354        """
     355        file_id = filename
     356        root = self.root # Calls to self.root are expensive
     357        marker, filename, base, ext = self.extractMarker(file_id)
     358        handler = queryUtility(IFileStoreHandler, name=marker,
     359                               default=DefaultFileStoreHandler())
     360        f, path, file_obj = handler.createFile(
     361            self, root, file_id, filename, f)
     362        dirname = os.path.dirname(path)
     363        if not os.path.exists(dirname):
     364            os.makedirs(dirname, 0755)
     365        open(path, 'wb').write(f.read())
     366        return file_obj
     367
     368    def extractMarker(self, file_id):
     369        """split filename into marker, filename, basename, and extension.
     370
     371        A marker is a leading part of a string of form
     372        ``__MARKERNAME__`` followed by the real filename. This way we
     373        can put markers into a filename to request special processing.
     374
     375        Returns a quadruple
     376
     377          ``(marker, filename, basename, extension)``
     378
     379        where ``marker`` is the marker in lowercase, filename is the
     380        complete trailing real filename, ``basename`` is the basename
     381        of the filename and ``extension`` the filename extension of
     382        the trailing filename. See examples below.
     383
     384        Example:
     385
     386           >>> extractMarker('__MaRkEr__sample.jpg')
     387           ('marker', 'sample.jpg', 'sample', '.jpg')
     388
     389        If no marker is contained, we assume the whole string to be a
     390        real filename:
     391
     392           >>> extractMarker('no-marker.txt')
     393           ('', 'no-marker.txt', 'no-marker', '.txt')
     394
     395        Filenames without extension give an empty extension string:
     396
     397           >>> extractMarker('no-marker')
     398           ('', 'no-marker', 'no-marker', '')
     399
     400        """
     401        if not isinstance(file_id, basestring) or not file_id:
     402            return ('', '', '', '')
     403        parts = file_id.split('__', 2)
     404        marker = ''
     405        if len(parts) == 3 and parts[0] == '':
     406            marker = parts[1].lower()
     407            file_id = parts[2]
     408        basename, ext = os.path.splitext(file_id)
     409        return (marker, file_id, basename, ext)
     410
     411grok.global_utility(ExtFileStore, provides=IExtFileStore)
     412
     413class DefaultStorage(ExtFileStore):
     414    """Default storage for files.
     415
     416    Registered globally as utility for
     417    :class:`hurry.file.interfaces.IFileRetrieval`.
    154418    """
    155     def _del(self):
    156         for basket in self.values():
    157             try:
    158                 basket._del()
    159             except:
    160                 pass
    161 
    162     def storeFile(self, fd, filename):
    163         fd.seek(0)
    164         digest = md5digest(fd)
    165         fd.seek(0)
    166         if not digest in self.keys():
    167             self[digest] = Basket()
    168         basket_id = self[digest].storeFile(fd, filename)
    169         full_id = "%s-%s" % (digest, basket_id)
    170         return full_id
    171 
    172     def retrieveFile(self, file_id):
    173         if not '-' in file_id:
    174             return None
    175         full_id, basket_id = file_id.split('-', 1)
    176         if not full_id in self.keys():
    177             return None
    178         return self[full_id].retrieveFile(basket_id)
    179 
    180 class ImageStorageFileRetrieval(Persistent):
    181     grok.implements(IFileRetrieval)
    182 
    183     def getImageStorage(self):
    184         site = grok.getSite()
    185         if site is None:
    186             return None
    187         return site.get('images', None)
    188 
    189     def isImageStorageEnabled(self):
    190         site = grok.getSite()
    191         if site is None:
    192             return False
    193         if site.get('images', None) is None:
    194             return False
    195         return True
    196 
    197     def getFile(self, data):
    198         # ImageStorage is disabled, so give fall-back behaviour for
    199         # testing without ImageStorage
    200         if not self.isImageStorageEnabled():
    201             return StringIO(data)
    202         storage = self.getImageStorage()
    203         if storage is None:
    204             raise ValueError('Cannot find an image storage')
    205         result = storage.retrieveFile(data)
    206         if result is None:
    207             return StringIO(data)
    208         return storage.retrieveFile(data)
    209 
    210     def createFile(self, filename, f):
    211         if not self.isImageStorageEnabled():
    212             return WAeUPImageFile(filename, f.read())
    213         storage = self.getImageStorage()
    214         if storage is None:
    215             raise ValueError('Cannot find an image storage')
    216         file_id = storage.storeFile(f, filename)
    217         return WAeUPImageFile(filename, file_id)
     419    grok.provides(IFileRetrieval)
     420
     421grok.global_utility(DefaultStorage, provides=IFileRetrieval)
     422
     423class DefaultFileStoreHandler(grok.GlobalUtility):
     424    """A default handler for external file store.
     425
     426    This handler is the fallback called by external file stores when
     427    there is no or an unknown marker in the file id.
     428
     429    Registered globally as utility for
     430    :class:`waeup.sirp.interfaces.IFileStoreHandler`.
     431    """
     432    grok.implements(IFileStoreHandler)
     433
     434    def pathFromFileID(self, store, root, file_id):
     435        """Return the root path of external file store appended by file id.
     436        """
     437        return os.path.join(root, file_id)
     438
     439    def createFile(self, store, root, filename, file_id, f):
     440        """Infos about what to store exactly and where.
     441
     442        When a file should be handled by an external file storage, it
     443        looks up any handlers (like this one), passes runtime infos
     444        like the storage object, root path, filename, file_id, and the
     445        raw file object itself.
     446
     447        The handler can then change the file, raise exceptions or
     448        whatever and return the result.
     449
     450        This handler returns the input file as-is, a path returned by
     451        :meth:`pathFromFileID` and an instance of
     452        :class:`hurry.file.HurryFile` for further operations.
     453
     454        Please note: although a handler has enough infos to store the
     455        file itself, it should leave that task to the calling file
     456        store.
     457        """
     458        path = self.pathFromFileID(store, root, file_id)
     459        return f, path, HurryFile(filename, file_id)
  • main/waeup.sirp/trunk/src/waeup/sirp/interfaces.py

    r7022 r7063  
    33import os
    44from datetime import datetime
     5from hurry.file.interfaces import IFileRetrieval
    56from hurry.workflow.interfaces import IWorkflow, IWorkflowInfo
    67from zc.sourcefactory.basic import BasicSourceFactory
     
    89from zope.component import getUtility
    910from zope.component.interfaces import IObjectEvent
     11from zope.container.interfaces import INameChooser
    1012from zope.interface import Interface, Attribute, implements
    1113from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
     
    591593        """Unregister a logger.
    592594        """
     595
     596#
     597# External File Storage and relatives
     598#
     599class IFileStoreNameChooser(INameChooser):
     600    """See zope.container.interfaces.INameChooser for base methods.
     601    """
     602    def checkName(name):
     603        """Check whether an object name is valid.
     604
     605        Raises a user error if the name is not valid.
     606        """
     607
     608    def chooseName(name):
     609        """Choose a unique valid name for the object.
     610
     611        The given name and object may be taken into account when
     612        choosing the name.
     613
     614        chooseName is expected to always choose a valid name (that would pass
     615        the checkName test) and never raise an error.
     616        """
     617
     618class IExtFileStore(IFileRetrieval):
     619    """A file storage that stores files in filesystem (not as blobs).
     620    """
     621    root = schema.TextLine(
     622        title = u'Root path of file store.',
     623        )
     624
     625    def getFile(file_id):
     626        """Get raw file data stored under file with `file_id`.
     627
     628        Returns a file descriptor open for reading or ``None`` if the
     629        file cannot be found.
     630        """
     631
     632    def getFileByContext(context):
     633        """Get raw file data stored for the given context.
     634
     635        Returns a file descriptor open for reading or ``None`` if no
     636        such file can be found.
     637
     638        This is a convenience method.
     639        """
     640
     641    def createFile(filename, f):
     642        """Create file given by f with filename `filename`
     643
     644        Returns a hurry.file.File-based object.
     645        """
     646
     647class IFileStoreHandler(Interface):
     648    """Filestore handlers handle specific files for file stores.
     649
     650    If a file to store/get provides a specific filename, a file store
     651    looks up special handlers for that type of file.
     652
     653    """
     654    def pathFromFileID(store, root, filename):
     655        """Turn file id into path to store.
     656
     657        Returned path should be absolute.
     658        """
     659
     660    def createFile(store, root, filename, file_id, file):
     661        """Return some hurry.file based on `store` and `file_id`.
     662
     663        Some kind of callback method called by file stores to create
     664        file objects from file_id.
     665
     666        Returns a tuple ``(raw_file, path, file_like_obj)`` where the
     667        ``file_like_obj`` should be a HurryFile, a WAeUPImageFile or
     668        similar. ``raw_file`` is the (maybe changed) input file and
     669        ``path`` the relative internal path to store the file at.
     670
     671        Please make sure the ``raw_file`` is opened for reading and
     672        the file descriptor set at position 0 when returned.
     673
     674        This method also gets the raw input file object that is about
     675        to be stored and is expected to raise any exceptions if some
     676        kind of validation or similar fails.
     677        """
  • main/waeup.sirp/trunk/src/waeup/sirp/tests/test_app.py

    r6593 r7063  
    66from zope.interface.verify import verifyClass, verifyObject
    77from waeup.sirp.app import University
    8 from waeup.sirp.imagestorage import ImageStorageFileRetrieval
    98from waeup.sirp.interfaces import IUniversity
    109from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
     
    3231        return
    3332
    34     def test_images(self):
    35         # Make sure we have a image container in a university
    36         assert 'images' in self.app.keys()
    37         return
    38 
    3933    def test_IFileRetrieval_utility(self):
    4034        # Make sure we can get a local IFileRetrieval utility
     
    4337        assert result is not None
    4438        assert IFileRetrieval.providedBy(result)
    45         assert isinstance(result, ImageStorageFileRetrieval)
    4639        return
    4740
  • main/waeup.sirp/trunk/src/waeup/sirp/tests/test_imagestorage.py

    r6529 r7063  
    11import os
    2 import sys
    32import tempfile
    43import shutil
    54import unittest
    65from StringIO import StringIO
     6from hurry.file import HurryFile
    77from hurry.file.interfaces import IFileRetrieval
     8from zope.component import (
     9    getUtility, provideUtility, queryUtility, provideAdapter)
    810from zope.component.hooks import setSite
    911from zope.interface.verify import verifyClass, verifyObject
    1012from waeup.sirp.app import University
    1113from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
    12 from waeup.sirp.image import createWAeUPImageFile
    1314from waeup.sirp.imagestorage import (
    14     md5digest, Basket, ImageStorage, ImageStorageFileRetrieval,)
     15    FileStoreNameChooser, ExtFileStore, DefaultFileStoreHandler,
     16    DefaultStorage)
     17from waeup.sirp.interfaces import (
     18    IFileStoreNameChooser, IExtFileStore, IFileStoreHandler,)
    1519
    1620class HelperFuncsTests(unittest.TestCase):
     
    2226        shutil.rmtree(self.workdir)
    2327
    24     def test_md5digest(self):
    25         samplefile = os.path.join(self.workdir, 'sample')
    26         open(samplefile, 'wb').write('blah')
    27         fp = open(samplefile, 'r')
    28         digest = md5digest(fp)
    29         self.assertEqual(digest, '6f1ed002ab5595859014ebf0951522d9')
    30 
    31 class BasketTests(FunctionalTestCase):
     28class FileStoreNameChooserTests(FunctionalTestCase):
    3229
    3330    layer = FunctionalLayer
    3431
     32    def test_iface(self):
     33        # we provide the interfaces we promise to do
     34        obj = FileStoreNameChooser(None)
     35        verifyClass(IFileStoreNameChooser, FileStoreNameChooser)
     36        verifyObject(IFileStoreNameChooser, obj)
     37        return
     38
     39    def test_accessible_as_adapter(self):
     40        # we can get a file name chooser via adapter
     41        chooser = IFileStoreNameChooser(object())
     42        self.assertTrue(
     43            isinstance(chooser, FileStoreNameChooser))
     44        return
     45
     46    def test_check_name(self):
     47        # default file name choosers accept any string
     48        chooser = FileStoreNameChooser(object())
     49        self.assertEqual(chooser.checkName('Hi there!'), True)
     50        self.assertEqual(chooser.checkName(None), False)
     51        return
     52
     53    def test_choose_name(self):
     54        # we get a simple string if we do not pass in a valid string
     55        chooser = FileStoreNameChooser(object())
     56        self.assertEqual(chooser.chooseName('myname'), 'myname')
     57        self.assertEqual(chooser.chooseName(None), u'unknown_file')
     58        return
     59
     60class ExtFileStoreTests(unittest.TestCase):
     61    # Test external file store (non-functional mode)
     62
    3563    def setUp(self):
    36         super(BasketTests, self).setUp()
     64        self.workdir = tempfile.mkdtemp()
     65        self.root = None
     66        return
     67
     68    def tearDown(self):
     69        shutil.rmtree(self.workdir)
     70        if self.root is not None:
     71            shutil.rmtree(self.root)
     72        return
     73
     74    def test_iface(self):
     75        obj = ExtFileStore(None)
     76        verifyClass(IExtFileStore, ExtFileStore)
     77        verifyObject(IExtFileStore, obj)
     78        return
     79
     80    def test_root_setup_wo_site(self):
     81        # if no site is available we can use a temporary root
     82        fs = ExtFileStore()
     83        self.root = fs.root
     84        self.assertTrue(isinstance(self.root, basestring))
     85        self.assertTrue(os.path.exists(self.root))
     86        return
     87
     88    def test_create_instance(self):
     89        storage1 = ExtFileStore()
     90        storage2 = ExtFileStore(root=self.workdir)
     91        self.root = storage1.root
     92        self.assertTrue(storage1.root is not None)
     93        self.assertTrue(storage1.root != storage2.root)
     94        self.assertEqual(storage2.root, self.workdir)
     95        return
     96
     97    def test_create_file(self):
     98        # We can store files
     99        storage = ExtFileStore(root=self.workdir)
     100        dummy_file = StringIO('sample file')
     101        image_file = storage.createFile('mysample', dummy_file)
     102        self.assertTrue('mysample' in os.listdir(storage.root))
     103        self.assertEqual('mysample', image_file.data)
     104        return
     105
     106    def test_get_file(self):
     107        # We can get files after having them stored
     108        storage = ExtFileStore(root=self.workdir)
     109        dummy_file = StringIO('sample file')
     110        image_file = storage.createFile('mysample', dummy_file)
     111        result = storage.getFile(image_file.data)
     112        self.assertEqual(result.read(), 'sample file')
     113        return
     114
     115    def test_extract_marker(self):
     116        # file stores support extracting markers from filenames
     117        storage = ExtFileStore(root=self.workdir)
     118        result1 = storage.extractMarker(None)
     119        result2 = storage.extractMarker('')
     120        result3 = storage.extractMarker('no-marker')
     121        result4 = storage.extractMarker('no-marker.txt')
     122        result5 = storage.extractMarker('__MARKER__foo.jpg')
     123        result6 = storage.extractMarker('__MaRkEr__foo.jpg')
     124        result7 = storage.extractMarker('__THE_MARKER__foo.jpg')
     125        result8 = storage.extractMarker('__A_MARK__my__foo.jpg')
     126
     127        self.assertEqual(result1, ('', '', '', ''))
     128        self.assertEqual(result2, ('', '', '', ''))
     129        self.assertEqual(result3, ('', 'no-marker', 'no-marker', ''))
     130        self.assertEqual(result4, ('', 'no-marker.txt', 'no-marker', '.txt'))
     131        self.assertEqual(result5, ('marker', 'foo.jpg', 'foo', '.jpg'))
     132        self.assertEqual(result6, ('marker', 'foo.jpg', 'foo', '.jpg'))
     133        self.assertEqual(result7, ('the_marker', 'foo.jpg', 'foo', '.jpg'))
     134        self.assertEqual(result8, ('a_mark', 'my__foo.jpg', 'my__foo', '.jpg'))
     135        return
     136
     137class DefaultFileStoreHandlerTests(unittest.TestCase):
     138
     139    def test_iface(self):
     140        obj = DefaultFileStoreHandler()
     141        verifyClass(IFileStoreHandler, DefaultFileStoreHandler)
     142        verifyObject(IFileStoreHandler, obj)
     143        return
     144
     145class CustomizedFileHandler(object):
     146    def pathFromFileID(self, store, root, file_id):
     147        return os.path.join(root, file_id[12:])
     148
     149    def createFile(self, store, root, file_id, filename, f):
     150        path = self.pathFromFileID(store, root, file_id)
     151        return f, path, HurryFile(filename, file_id)
     152
     153class CustomContext(object):
     154    pass
     155
     156class CustomContextFileChooser(object):
     157    def __init__(self, context):
     158        self.context = context
     159
     160    def chooseName(self, name=None):
     161        return '__mymarker__mysample.txt'
     162
     163class FunctionalExtFileStoreTests(FunctionalTestCase):
     164
     165    layer = FunctionalLayer
     166
     167    def setUp(self):
     168        super(FunctionalExtFileStoreTests, self).setUp()
    37169        self.workdir = tempfile.mkdtemp()
    38170        self.samplefile = os.path.join(self.workdir, 'sample')
     
    40172        open(self.samplefile, 'wb').write('Hi there!')
    41173        open(self.otherfile, 'wb').write('Hi from other!')
    42         self.basket = Basket()
    43174        self.fd = open(self.samplefile, 'r')
    44175        self.fd2 = open(self.otherfile, 'r')
    45         self.stderr = StringIO()
    46         self.old_stderr = sys.stderr
     176        self.getRootFolder()['app'] = University()
     177        self.app = self.getRootFolder()['app']
     178        self.app['datacenter'].setStoragePath(self.workdir)
     179        # register a custom filename mangler
     180        provideUtility(
     181            CustomizedFileHandler(), IFileStoreHandler, name=u'mymarker')
     182        # register a file chooser adapter for CustomContext
     183        provideAdapter(
     184            CustomContextFileChooser,
     185            (CustomContext,), IFileStoreNameChooser)
     186        return
     187
    47188
    48189    def tearDown(self):
    49         sys.stderr = self.old_stderr
    50         super(BasketTests, self).tearDown()
    51         self.fd.close()
    52         shutil.rmtree(self.workdir)
    53         self.basket._del() # Remove subojects explicitly
    54         del self.basket
    55         return
    56 
    57     def test_ifaces(self):
    58         pass
    59 
    60     def test_curr_id_empty(self):
    61         curr_id = self.basket.curr_id
    62         self.assertEqual(curr_id, '1')
    63 
    64     def test_getInternalId_empty(self):
    65         basket_id = self.basket.getInternalId(self.fd)
    66         self.assertTrue(basket_id is None)
    67 
    68     def test_storeFile_single(self):
    69         basket_id = self.basket.storeFile(self.fd, 'sample')
    70         self.assertEqual(basket_id, '1')
    71         contents = self.basket['1'].open('r').read()
    72         self.assertEqual(contents, 'Hi there!')
    73 
    74     def test_storeFile_double(self):
    75         basket_id1 = self.basket.storeFile(self.fd, 'sample')
    76         basket_id2 = self.basket.storeFile(self.fd, 'sample')
    77         self.assertTrue(basket_id1 == basket_id2 == '1')
    78         contents = self.basket['1'].open('r').read()
    79         self.assertEqual(contents, 'Hi there!')
    80 
    81     def test_storeFile_multiple(self):
    82         basket_id1 = self.basket.storeFile(self.fd, 'sample')
    83         basket_id2 = self.basket.storeFile(self.fd2, 'sample')
    84         self.assertEqual(basket_id1, '1')
    85         self.assertEqual(basket_id2, '2')
    86         contents1 = self.basket['1'].open('r').read()
    87         contents2 = self.basket['2'].open('r').read()
    88         self.assertEqual(contents1, 'Hi there!')
    89         self.assertEqual(contents2, 'Hi from other!')
    90 
    91     def test_retrieveFile(self):
    92         basket_id = self.basket.storeFile(self.fd, 'sample')
    93         fd = self.basket.retrieveFile(basket_id)
    94         result = fd.read()
    95         self.assertEqual(result, 'Hi there!')
    96 
    97     def test_retrieveFile_not_existent(self):
    98         result = self.basket.retrieveFile('not-a-valid-basket-id')
    99         self.assertTrue(result is None)
    100 
    101     def test_detect_zero_length_blobs(self):
    102         # Ensure we get a warning when an empty Blob is found
    103         self.basket.storeFile(self.fd, 'sample')
    104         self.basket['1'].open('w').write('')
    105         self.fd.seek(0)
    106         sys.stderr = self.stderr         # Redirect stderr
    107         self.basket.storeFile(self.fd, 'sample')
    108         sys.stderr = self.old_stderr     # Restore stderr
    109         self.stderr.seek(0)
    110         self.assertTrue(
    111             "EMPTY BLOB DETECTED" in self.stderr.read())
    112 
    113     def test_refill_zero_length_blobs(self):
    114         # When we detect an empty Blob, it will be reused
    115         self.basket.storeFile(self.fd, 'sample')
    116         self.basket['1'].open('w').write('')
    117         self.fd.seek(0)
    118         sys.stderr = self.stderr         # Redirect stderr
    119         self.basket.storeFile(self.fd, 'sample')
    120         sys.stderr = self.old_stderr     # Restore stderr
    121         contents = self.basket['1'].open('r').read()
    122         self.assertEqual(contents, 'Hi there!')
    123 
    124 
    125 class ImageStorageTests(FunctionalTestCase):
    126 
    127     layer = FunctionalLayer
    128 
    129     def setUp(self):
    130         super(ImageStorageTests, self).setUp()
    131         self.workdir = tempfile.mkdtemp()
    132         self.samplefile = os.path.join(self.workdir, 'sample')
    133         self.otherfile = os.path.join(self.workdir, 'other')
    134         open(self.samplefile, 'wb').write('Hi there!')
    135         open(self.otherfile, 'wb').write('Hi from other!')
    136         self.storage = ImageStorage()
    137         self.fd = open(self.samplefile, 'r')
    138         self.fd2 = open(self.otherfile, 'r')
    139 
    140     def tearDown(self):
    141         super(ImageStorageTests, self).tearDown()
     190        super(FunctionalExtFileStoreTests, self).tearDown()
    142191        self.fd.close()
    143192        self.fd2.close()
    144193        shutil.rmtree(self.workdir)
    145         self.storage._del() # Remove subojects explicitly
    146         del self.storage
    147         return
    148 
    149     def test_ifaces(self):
    150         pass
    151 
    152     def test_storeFile(self):
    153         full_id = self.storage.storeFile(self.fd, 'sample.txt')
    154         self.assertEqual(full_id, '396199333edbf40ad43e62a1c1397793-1')
    155 
    156     def test_storeFile_duplicate(self):
    157         full_id1 = self.storage.storeFile(self.fd, 'sample1.txt')
    158         full_id2 = self.storage.storeFile(self.fd, 'sample2.txt')
    159         full_id3 = self.storage.storeFile(self.fd, 'sample1.txt')
    160         self.assertEqual(full_id1, '396199333edbf40ad43e62a1c1397793-1')
    161         self.assertEqual(full_id2, '396199333edbf40ad43e62a1c1397793-1')
    162         self.assertEqual(full_id3, '396199333edbf40ad43e62a1c1397793-1')
    163         contents = self.storage.retrieveFile(
    164             '396199333edbf40ad43e62a1c1397793-1').read()
    165         self.assertEqual(contents, 'Hi there!')
    166 
    167     def test_storeFile_multiple(self):
    168         full_id1 = self.storage.storeFile(self.fd, 'sample1.txt')
    169         full_id2 = self.storage.storeFile(self.fd2, 'sample1.txt')
    170         self.assertEqual(full_id1, '396199333edbf40ad43e62a1c1397793-1')
    171         self.assertEqual(full_id2, '6936fcf8d564f1c5be5a017e650c5e8f-1')
    172 
    173     def test_retrieveFile_not_existent(self):
    174         result = self.storage.retrieveFile('not-existent')
    175         self.assertTrue(result is None)
    176 
    177     def test_retrieveFile_illegal_marker(self):
    178         result1 = self.storage.retrieveFile('really-not-existent')
    179         result2 = self.storage.retrieveFile('notexistent')
    180         self.assertTrue(result1 is result2 is None)
    181 
    182     def test_retrieveFile(self):
    183         full_id = self.storage.storeFile(self.fd, 'sample.txt')
    184         result = self.storage.retrieveFile(full_id)
    185         content = result.read()
    186         self.assertEqual(content, 'Hi there!')
    187 
    188     def test_retrieveFile_multiple(self):
    189         full_id1 = self.storage.storeFile(self.fd, 'sample.txt')
    190         full_id2 = self.storage.storeFile(self.fd2, 'other.txt')
    191         result1 = self.storage.retrieveFile(full_id1)
    192         result2 = self.storage.retrieveFile(full_id2)
    193         content1 = result1.read()
    194         content2 = result2.read()
    195         self.assertEqual(content1, 'Hi there!')
    196         self.assertEqual(content2, 'Hi from other!')
    197 
    198 class ImageStorageFileRetrievalTests(FunctionalTestCase):
    199 
    200     layer = FunctionalLayer
    201 
    202     def setUp(self):
    203         super(ImageStorageFileRetrievalTests, self).setUp()
    204         self.workdir = tempfile.mkdtemp()
    205         self.samplefile = os.path.join(self.workdir, 'sample')
    206         self.otherfile = os.path.join(self.workdir, 'other')
    207         open(self.samplefile, 'wb').write('Hi there!')
    208         open(self.otherfile, 'wb').write('Hi from other!')
    209         self.storage = ImageStorage()
    210         self.fd = open(self.samplefile, 'r')
    211         self.fd2 = open(self.otherfile, 'r')
    212         # Set up a single image storage in a site
    213         self.getRootFolder()['app'] = University()
    214         self.app = self.getRootFolder()['app']
    215         if not 'images' in self.app.keys():
    216             self.app['images'] = ImageStorage()
    217         self.storage = self.app['images']
    218         return
    219 
    220 
    221     def tearDown(self):
    222         super(ImageStorageFileRetrievalTests, self).tearDown()
    223         self.fd.close()
    224         self.fd2.close()
    225         shutil.rmtree(self.workdir)
    226         self.storage._del() # Remove subojects explicitly
    227         return
    228 
    229     def test_ifaces(self):
    230         retrieval = ImageStorageFileRetrieval()
    231         assert verifyClass(IFileRetrieval, ImageStorageFileRetrieval)
    232         assert verifyObject(IFileRetrieval, retrieval)
    233         return
    234 
    235     def test_getImageStorage_nosite(self):
    236         retrieval = ImageStorageFileRetrieval()
    237         storage = retrieval.getImageStorage()
    238         self.assertTrue(storage is None)
    239         return
    240 
    241     def test_getImageStorage(self):
     194        return
     195
     196    def test_root_setup_w_site(self):
     197        # if a site is available we use it to determine the root dir
     198        fs = ExtFileStore()
    242199        setSite(self.app)
    243         retrieval = ImageStorageFileRetrieval()
    244         storage = retrieval.getImageStorage()
    245         self.assertTrue(storage is self.storage)
    246         return
    247 
    248     def test_isImageStorageEnabled_nosite(self):
    249         retrieval = ImageStorageFileRetrieval()
    250         self.assertTrue(retrieval.isImageStorageEnabled() is False)
    251         return
    252 
    253     def test_isImageStorageEnabled(self):
    254         setSite(self.app)
    255         retrieval = ImageStorageFileRetrieval()
    256         self.assertTrue(retrieval.isImageStorageEnabled() is True)
    257         return
    258 
    259     def test_getFile_nosite(self):
    260         retrieval = ImageStorageFileRetrieval()
    261         f = retrieval.getFile('Hi there!')
    262         self.assertEqual(f.read(), 'Hi there!')
    263         return
    264 
    265     def test_getFile(self):
    266         setSite(self.app)
    267         retrieval = ImageStorageFileRetrieval()
    268         waeup_image = retrieval.createFile('sample.txt', self.fd)
    269         full_id = waeup_image.data
    270         result = retrieval.getFile(full_id)
    271         self.assertEqual(result.read(), 'Hi there!')
    272         return
    273 
    274     def test_createFile_nosite(self):
    275         retrieval = ImageStorageFileRetrieval()
    276         waeup_image = retrieval.createFile('sample.txt', self.fd)
    277         self.assertEqual(waeup_image.data, 'Hi there!')
    278         return
    279 
    280     def test_createFile(self):
    281         # Ensure we can create WAeUPImageFiles when in site
    282         setSite(self.app)
    283         retrieval = ImageStorageFileRetrieval()
    284         waeup_image = retrieval.createFile('sample.txt', self.fd)
    285         full_id = waeup_image.data
    286         self.assertEqual(full_id, '396199333edbf40ad43e62a1c1397793-1')
    287         return
    288 
    289     def test_waeupimagefile(self):
    290         # Make sure WAeUPImageFile can use our file retrieval
    291         setSite(self.app)
    292         myfile = createWAeUPImageFile('sample.jpg', self.fd)
    293         contents = myfile.file.read()
    294         self.assertEqual(contents, 'Hi there!')
    295 
    296     def test_waeupimagefile_raw(self):
    297         # Make sure we can retrieve a file also if it was initialized
    298         # with no image storage available
    299         myfile = createWAeUPImageFile('sample.jpg', self.fd)
    300         setSite(self.app)
    301         contents = myfile.file.read()
    302         self.assertEqual(contents, 'Hi there!')
     200        self.root = fs.root
     201        expected_root = os.path.join(
     202            self.app['datacenter'].storage, 'media')
     203        self.assertTrue(isinstance(self.root, basestring))
     204        self.assertEqual(self.root, expected_root)
     205        return
     206
     207    def test_get_utility(self):
     208        # we can get an ExtFileStore by global utility lookup
     209        fs1 = getUtility(IExtFileStore)
     210        fs2 = getUtility(IExtFileStore)
     211        self.assertTrue(isinstance(fs1, ExtFileStore))
     212        self.assertTrue(fs1 is fs2)
     213        return
     214
     215    def test_default_handler_create_file(self):
     216        # we can use the default handler to store files
     217        fs = ExtFileStore()
     218        result = fs.createFile('sample.txt', StringIO('sample text'))
     219        self.assertEqual(result.data, 'sample.txt')
     220        self.assertTrue('sample.txt' in os.listdir(fs.root))
     221        return
     222
     223    def test_default_handler_get_file(self):
     224        # we can get files stored by the default handler
     225        fs = ExtFileStore()
     226        fs.createFile('sample.txt', StringIO('sample text'))
     227        result1 = fs.getFile('sample.txt')
     228        result2 = fs.getFile('not-existent')
     229        self.assertEqual(result1.read(), 'sample text')
     230        self.assertTrue(result2 is None)
     231        return
     232
     233    def test_customized_handler_create_file(self):
     234        # we can use registered filename handlers
     235        fs = ExtFileStore()
     236        result = fs.createFile(
     237            '__MYMARKER__sample.txt', StringIO('sample text'))
     238        self.assertEqual(result.data, '__MYMARKER__sample.txt')
     239        self.assertTrue('sample.txt' in os.listdir(fs.root))
     240        return
     241
     242    def test_customized_handler_get_file(self):
     243        # we consider registered filename handlers when asking for
     244        # stored files.
     245        fs = ExtFileStore()
     246        fs.createFile('__MYMARKER__sample.txt', StringIO('sample text'))
     247        result1 = fs.getFile('__MYMARKER__sample.txt')
     248        result2 = fs.getFile('__MYMARKER__not-existent')
     249        result3 = fs.getFile('not-existent')
     250        self.assertEqual(result1.read(), 'sample text')
     251        self.assertTrue(result2 is None)
     252        self.assertTrue(result3 is None)
     253        return
     254
     255    def test_get_file_by_context(self):
     256        # if we register a file name chooser, we can also get a file
     257        # by context
     258        fs = ExtFileStore()
     259        context = CustomContext()
     260        file_id = IFileStoreNameChooser(context).chooseName()
     261        fs = ExtFileStore()
     262        fs.createFile(file_id, StringIO('my sample'))
     263        result = fs.getFileByContext(context)
     264        self.assertEqual(
     265            file_id, '__mymarker__mysample.txt')
     266        self.assertEqual(
     267            result.read(), 'my sample')
     268        return
     269
     270    def test_get_default_handler(self):
     271        # we can get a default handler
     272        result = queryUtility(IFileStoreHandler)
     273        self.assertTrue(
     274            isinstance(result, DefaultFileStoreHandler))
     275        return
     276
     277    def test_get_default_file_retrieval(self):
     278        # we get a file store when requesting a file retrieval
     279        result = queryUtility(IFileRetrieval)
     280        self.assertTrue(
     281            isinstance(result, DefaultStorage))
  • main/waeup.sirp/trunk/src/waeup/sirp/university/certificatecontainer.py

    r6243 r7063  
    2626
    2727    Each :class:`CertificateContainer` provides
    28     :iface:`ICertificateContainer`.
     28    :class:`ICertificateContainer`.
    2929    """
    3030    grok.implements(ICertificateContainer)
     
    3535
    3636        The `certificate` must be an object implementing
    37         :iface:`waeup.sirp.university.interfaces.ICertificate`. If
     37        :class:`waeup.sirp.university.interfaces.ICertificate`. If
    3838        not, a :exc:`TypeError` is raised.
    3939
     
    7575
    7676        The certificate must be an object implementing
    77         :iface:`waeup.sirp.university.interfaces.ICertificate`. If
     77        :class:`waeup.sirp.university.interfaces.ICertificate`. If
    7878        not, a :exc:`TypeError` is raised.
    7979
Note: See TracChangeset for help on using the changeset viewer.