source: main/waeup.ikoba/trunk/src/waeup/ikoba/customers/documents.py @ 12166

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

We need to protect also the manage form page of documents. Officers are only allowed to edit documents in state created.

  • Property svn:keywords set to Id
File size: 11.0 KB
RevLine 
[12015]1## $Id: documents.py 12166 2014-12-07 22:24:03Z henrik $
[11989]2##
3## Copyright (C) 2014 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Customer document components.
20"""
[12035]21import os
[11989]22import grok
[12161]23from hashlib import md5
[12018]24from zope.component import queryUtility, getUtility
[11989]25from zope.component.interfaces import IFactory
26from zope.interface import implementedBy
[12035]27
28from waeup.ikoba.image import IkobaImageFile
29from waeup.ikoba.imagestorage import DefaultFileStoreHandler
[11989]30from waeup.ikoba.interfaces import MessageFactory as _
[12035]31from waeup.ikoba.interfaces import (
32    IFileStoreNameChooser, IFileStoreHandler,
33    IIkobaUtils, IExtFileStore)
[11989]34from waeup.ikoba.customers.interfaces import (
[12018]35    ICustomerDocumentsContainer, ICustomerNavigation, ICustomerDocument,
[12053]36    ICustomersUtils, ICustomerPDFDocument)
[11989]37from waeup.ikoba.documents import DocumentsContainer, Document
[12018]38from waeup.ikoba.documents.interfaces import IDocumentsUtils
[11989]39from waeup.ikoba.utils.helpers import attrs_to_fields
40
[12035]41from waeup.ikoba.customers.utils import path_from_custid
42
[11989]43class CustomerDocumentsContainer(DocumentsContainer):
44    """This is a container for customer documents.
45    """
46    grok.implements(ICustomerDocumentsContainer, ICustomerNavigation)
47    grok.provides(ICustomerDocumentsContainer)
48
49    def __init__(self):
50        super(CustomerDocumentsContainer, self).__init__()
51        return
52
53    @property
54    def customer(self):
55        return self.__parent__
56
57    def writeLogMessage(self, view, message):
58        return self.__parent__.writeLogMessage(view, message)
59
60CustomerDocumentsContainer = attrs_to_fields(CustomerDocumentsContainer)
61
[12057]62class CustomerDocumentBase(Document):
63    """This is a customer document baseclass.
[11989]64    """
65    grok.implements(ICustomerDocument, ICustomerNavigation)
66    grok.provides(ICustomerDocument)
[12057]67    grok.baseclass()
[11989]68
[12164]69    # Ikoba can store any number of files per Document object.
70    # However, we highly recommend to associate and store
71    # only one file per Document object. Thus the following
72    # tuple should contain only a single filename string.
[12161]73    filenames = ()
74
[11989]75    @property
76    def customer(self):
77        try:
78            return self.__parent__.__parent__
79        except AttributeError:
80            return None
81
[12128]82    @property
83    def user_id(self):
84        if self.customer is not None:
85            return self.customer.customer_id
86        return
87
[11989]88    def writeLogMessage(self, view, message):
89        return self.__parent__.__parent__.writeLogMessage(view, message)
90
[12018]91    @property
[12166]92    def is_editable_by_customer(self):
[12018]93        try:
94            # Customer must be approved
95            cond1 = self.customer.state in getUtility(
[12088]96                ICustomersUtils).DOCMANAGE_CUSTOMER_STATES
[12018]97            # Document must be in state created
98            cond2 = self.state in getUtility(
[12088]99                ICustomersUtils).DOCMANAGE_DOCUMENT_STATES
[12018]100            if not (cond1 and cond2):
101                return False
102        except AttributeError:
103            pass
104        return True
105
[12053]106    @property
[12166]107    def is_editable_by_manager(self):
108        try:
109            # Document must be in state created
110            cond = self.state in getUtility(
111                ICustomersUtils).DOCMANAGE_DOCUMENT_STATES
112            if not cond:
113                return False
114        except AttributeError:
115            pass
116        return True
117
118    @property
[12056]119    def translated_class_name(self):
[12053]120        try:
121            DOCTYPES_DICT = getUtility(ICustomersUtils).DOCTYPES_DICT
[12056]122            return DOCTYPES_DICT[self.class_name]
[12053]123        except KeyError:
124            return
125
[12161]126    @property
127    def connected_files(self):
128        store = getUtility(IExtFileStore)
129        files = []
130        try:
[12164]131            # Usually there is only a single element in self.filenames.
[12161]132            for filename in self.filenames:
133                attrname = filename.replace('.','_')
134                file = store.getFileByContext(self, attr=filename)
[12165]135                if file:
136                    files.append((attrname, file))
[12161]137        except AttributeError:
138            # In unit tests we don't have a customer to
139            # determine the file path.
140            return
141        return files
[11989]142
[12161]143    def setMD5(self):
[12164]144        """Set md5 checksum attribute for files connected to this document.
[12161]145        """
[12162]146        connected_files = self.connected_files
147        if connected_files:
148            for file in self.connected_files:
149                attrname = '%s_md5' % file[0]
150                checksum = md5(file[1].read()).hexdigest()
151                setattr(self, attrname, checksum)
[12161]152        return
153
154
[12057]155class CustomerSampleDocument(CustomerDocumentBase):
156    """This is a sample customer document.
[12053]157    """
[12057]158
[12164]159    # Ikoba can store any number of files per Document object.
160    # However, we highly recommend to associate and store
161    # only one file per Document object. Thus the following
162    # tuple should contain only a single filename string.
[12161]163    filenames = ('sample',)
164
[12057]165CustomerSampleDocument = attrs_to_fields(CustomerSampleDocument)
166
167
168class CustomerPDFDocument(CustomerDocumentBase):
169    """This is a customer document for a single pdf upload file.
170    """
[12053]171    grok.implements(ICustomerPDFDocument, ICustomerNavigation)
172    grok.provides(ICustomerPDFDocument)
[11989]173
[12164]174    # Ikoba can store any number of files per Document object.
175    # However, we highly recommend to associate and store
176    # only one file per Document object. Thus the following
177    # tuple should contain only a single filename string.
178    filenames = ('sample.pdf',)
179
[12053]180CustomerPDFDocument = attrs_to_fields(CustomerPDFDocument)
181
182
[12164]183# Customer documents must be importable. So we need a factory.
[11989]184class CustomerDocumentFactory(grok.GlobalUtility):
185    """A factory for customer documents.
186    """
187    grok.implements(IFactory)
[12057]188    grok.name(u'waeup.CustomerSampleDocument')
[11989]189    title = u"Create a new document.",
[12053]190    description = u"This factory instantiates new sample document instances."
[11989]191
192    def __call__(self, *args, **kw):
[12057]193        return CustomerSampleDocument(*args, **kw)
[11989]194
195    def getInterfaces(self):
[12057]196        return implementedBy(CustomerSampleDocument)
[12035]197
[12053]198# Customer documents must be importable. So we might need a factory.
199class CustomerPDFDocumentFactory(grok.GlobalUtility):
200    """A factory for customer pdf documents.
201    """
202    grok.implements(IFactory)
203    grok.name(u'waeup.CustomerPDFDocument')
204    title = u"Create a new document.",
205    description = u"This factory instantiates new pdf document instances."
206
207    def __call__(self, *args, **kw):
208        return CustomerPDFDocument(*args, **kw)
209
210    def getInterfaces(self):
211        return implementedBy(CustomerPDFDocument)
212
[12035]213#: The file id marker for customer files
214CUSTOMERDOCUMENT_FILE_STORE_NAME = 'file-customerdocument'
215
216
217class CustomerDocumentFileNameChooser(grok.Adapter):
218    """A file id chooser for :class:`CustomerDocument` objects.
219
220    `context` is an :class:`CustomerDocument` instance.
221
222    The :class:`CustomerDocumentFileNameChooser` can build/check file ids for
223    :class:`Customer` objects suitable for use with
224    :class:`ExtFileStore` instances. The delivered file_id contains
225    the file id marker for :class:`CustomerDocument` object and the customer id
226    of the context customer.
227
228    This chooser is registered as an adapter providing
229    :class:`waeup.ikoba.interfaces.IFileStoreNameChooser`.
230
231    File store name choosers like this one are only convenience
232    components to ease the task of creating file ids for customer document
233    objects. You are nevertheless encouraged to use them instead of
234    manually setting up filenames for customer documents.
235
236    .. seealso:: :mod:`waeup.ikoba.imagestorage`
237
238    """
239
240    grok.context(ICustomerDocument)
241    grok.implements(IFileStoreNameChooser)
242
243    def checkName(self, name=None, attr=None):
244        """Check whether the given name is a valid file id for the context.
245
246        Returns ``True`` only if `name` equals the result of
247        :meth:`chooseName`.
248
249        """
250        return name == self.chooseName()
251
252    def chooseName(self, attr, name=None):
253        """Get a valid file id for customer document context.
254
255        *Example:*
256
257        For a customer with customer id ``'A123456'``
258        and document with id 'd123'
259        with attr ``'nice_image.jpeg'`` stored in
260        the customers container this chooser would create:
261
262          ``'__file-customerdocument__customers/A/A123456/nice_image_d123_A123456.jpeg'``
263
264        meaning that the nice image of this customer document would be
265        stored in the site-wide file storage in path:
266
267          ``customers/A/A123456/nice_image_d123_A123456.jpeg``
268
269        """
270        basename, ext = os.path.splitext(attr)
271        cust_id = self.context.customer.customer_id
272        doc_id = self.context.document_id
273        marked_filename = '__%s__%s/%s_%s_%s%s' % (
274            CUSTOMERDOCUMENT_FILE_STORE_NAME, path_from_custid(cust_id),
275            basename, doc_id, cust_id, ext)
276        return marked_filename
277
278
279class CustomerDocumentFileStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
280    """Customer document specific file handling.
281
282    This handler knows in which path in a filestore to store customer document
283    files and how to turn this kind of data into some (browsable)
284    file object.
285
286    It is called from the global file storage, when it wants to
287    get/store a file with a file id starting with
288    ``__file-customerdocument__`` (the marker string for customer files).
289
290    Like each other file store handler it does not handle the files
291    really (this is done by the global file store) but only computes
292    paths and things like this.
293    """
294    grok.implements(IFileStoreHandler)
295    grok.name(CUSTOMERDOCUMENT_FILE_STORE_NAME)
296
297    def pathFromFileID(self, store, root, file_id):
298        """All customer document files are put in directory ``customers``.
299        """
300        marker, filename, basename, ext = store.extractMarker(file_id)
301        sub_root = os.path.join(root, 'customers')
302        return super(CustomerDocumentFileStoreHandler, self).pathFromFileID(
303            store, sub_root, basename)
304
305    def createFile(self, store, root, filename, file_id, file):
306        """Create a browsable file-like object.
307        """
308        # call super method to ensure that any old files with
309        # different filename extension are deleted.
310        file, path, file_obj = super(
311            CustomerDocumentFileStoreHandler, self).createFile(
312            store, root,  filename, file_id, file)
313        return file, path, IkobaImageFile(
314            file_obj.filename, file_obj.data)
315
Note: See TracBrowser for help on using the repository browser.