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

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

Set md5 attributes when verifying a document.

  • Property svn:keywords set to Id
File size: 9.8 KB
Line 
1## $Id: documents.py 12162 2014-12-07 15:13:26Z henrik $
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"""
21import os
22import grok
23from hashlib import md5
24from zope.component import queryUtility, getUtility
25from zope.component.interfaces import IFactory
26from zope.interface import implementedBy
27
28from waeup.ikoba.image import IkobaImageFile
29from waeup.ikoba.imagestorage import DefaultFileStoreHandler
30from waeup.ikoba.interfaces import MessageFactory as _
31from waeup.ikoba.interfaces import (
32    IFileStoreNameChooser, IFileStoreHandler,
33    IIkobaUtils, IExtFileStore)
34from waeup.ikoba.customers.interfaces import (
35    ICustomerDocumentsContainer, ICustomerNavigation, ICustomerDocument,
36    ICustomersUtils, ICustomerPDFDocument)
37from waeup.ikoba.documents import DocumentsContainer, Document
38from waeup.ikoba.documents.interfaces import IDocumentsUtils
39from waeup.ikoba.utils.helpers import attrs_to_fields
40
41from waeup.ikoba.customers.utils import path_from_custid
42
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
62class CustomerDocumentBase(Document):
63    """This is a customer document baseclass.
64    """
65    grok.implements(ICustomerDocument, ICustomerNavigation)
66    grok.provides(ICustomerDocument)
67    grok.baseclass()
68
69    filenames = ()
70
71    @property
72    def customer(self):
73        try:
74            return self.__parent__.__parent__
75        except AttributeError:
76            return None
77
78    @property
79    def user_id(self):
80        if self.customer is not None:
81            return self.customer.customer_id
82        return
83
84    def writeLogMessage(self, view, message):
85        return self.__parent__.__parent__.writeLogMessage(view, message)
86
87    @property
88    def is_editable(self):
89        try:
90            # Customer must be approved
91            cond1 = self.customer.state in getUtility(
92                ICustomersUtils).DOCMANAGE_CUSTOMER_STATES
93            # Document must be in state created
94            cond2 = self.state in getUtility(
95                ICustomersUtils).DOCMANAGE_DOCUMENT_STATES
96            if not (cond1 and cond2):
97                return False
98        except AttributeError:
99            pass
100        return True
101
102    @property
103    def translated_class_name(self):
104        try:
105            DOCTYPES_DICT = getUtility(ICustomersUtils).DOCTYPES_DICT
106            return DOCTYPES_DICT[self.class_name]
107        except KeyError:
108            return
109
110    @property
111    def connected_files(self):
112        store = getUtility(IExtFileStore)
113        files = []
114        try:
115            for filename in self.filenames:
116                attrname = filename.replace('.','_')
117                file = store.getFileByContext(self, attr=filename)
118                files.append((attrname, file))
119        except AttributeError:
120            # In unit tests we don't have a customer to
121            # determine the file path.
122            return
123        return files
124
125    def setMD5(self):
126        """Set md5 checksum attribute for all files connected to this document.
127        """
128        connected_files = self.connected_files
129        if connected_files:
130            for file in self.connected_files:
131                attrname = '%s_md5' % file[0]
132                checksum = md5(file[1].read()).hexdigest()
133                setattr(self, attrname, checksum)
134        return
135
136
137class CustomerSampleDocument(CustomerDocumentBase):
138    """This is a sample customer document.
139    """
140
141    filenames = ('sample',)
142
143CustomerSampleDocument = attrs_to_fields(CustomerSampleDocument)
144
145
146class CustomerPDFDocument(CustomerDocumentBase):
147    """This is a customer document for a single pdf upload file.
148    """
149    grok.implements(ICustomerPDFDocument, ICustomerNavigation)
150    grok.provides(ICustomerPDFDocument)
151
152CustomerPDFDocument = attrs_to_fields(CustomerPDFDocument)
153
154
155# Customer documents must be importable. So we might need a factory.
156class CustomerDocumentFactory(grok.GlobalUtility):
157    """A factory for customer documents.
158    """
159    grok.implements(IFactory)
160    grok.name(u'waeup.CustomerSampleDocument')
161    title = u"Create a new document.",
162    description = u"This factory instantiates new sample document instances."
163
164    def __call__(self, *args, **kw):
165        return CustomerSampleDocument(*args, **kw)
166
167    def getInterfaces(self):
168        return implementedBy(CustomerSampleDocument)
169
170# Customer documents must be importable. So we might need a factory.
171class CustomerPDFDocumentFactory(grok.GlobalUtility):
172    """A factory for customer pdf documents.
173    """
174    grok.implements(IFactory)
175    grok.name(u'waeup.CustomerPDFDocument')
176    title = u"Create a new document.",
177    description = u"This factory instantiates new pdf document instances."
178
179    def __call__(self, *args, **kw):
180        return CustomerPDFDocument(*args, **kw)
181
182    def getInterfaces(self):
183        return implementedBy(CustomerPDFDocument)
184
185#: The file id marker for customer files
186CUSTOMERDOCUMENT_FILE_STORE_NAME = 'file-customerdocument'
187
188
189class CustomerDocumentFileNameChooser(grok.Adapter):
190    """A file id chooser for :class:`CustomerDocument` objects.
191
192    `context` is an :class:`CustomerDocument` instance.
193
194    The :class:`CustomerDocumentFileNameChooser` can build/check file ids for
195    :class:`Customer` objects suitable for use with
196    :class:`ExtFileStore` instances. The delivered file_id contains
197    the file id marker for :class:`CustomerDocument` object and the customer id
198    of the context customer.
199
200    This chooser is registered as an adapter providing
201    :class:`waeup.ikoba.interfaces.IFileStoreNameChooser`.
202
203    File store name choosers like this one are only convenience
204    components to ease the task of creating file ids for customer document
205    objects. You are nevertheless encouraged to use them instead of
206    manually setting up filenames for customer documents.
207
208    .. seealso:: :mod:`waeup.ikoba.imagestorage`
209
210    """
211
212    grok.context(ICustomerDocument)
213    grok.implements(IFileStoreNameChooser)
214
215    def checkName(self, name=None, attr=None):
216        """Check whether the given name is a valid file id for the context.
217
218        Returns ``True`` only if `name` equals the result of
219        :meth:`chooseName`.
220
221        """
222        return name == self.chooseName()
223
224    def chooseName(self, attr, name=None):
225        """Get a valid file id for customer document context.
226
227        *Example:*
228
229        For a customer with customer id ``'A123456'``
230        and document with id 'd123'
231        with attr ``'nice_image.jpeg'`` stored in
232        the customers container this chooser would create:
233
234          ``'__file-customerdocument__customers/A/A123456/nice_image_d123_A123456.jpeg'``
235
236        meaning that the nice image of this customer document would be
237        stored in the site-wide file storage in path:
238
239          ``customers/A/A123456/nice_image_d123_A123456.jpeg``
240
241        """
242        basename, ext = os.path.splitext(attr)
243        cust_id = self.context.customer.customer_id
244        doc_id = self.context.document_id
245        marked_filename = '__%s__%s/%s_%s_%s%s' % (
246            CUSTOMERDOCUMENT_FILE_STORE_NAME, path_from_custid(cust_id),
247            basename, doc_id, cust_id, ext)
248        return marked_filename
249
250
251class CustomerDocumentFileStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
252    """Customer document specific file handling.
253
254    This handler knows in which path in a filestore to store customer document
255    files and how to turn this kind of data into some (browsable)
256    file object.
257
258    It is called from the global file storage, when it wants to
259    get/store a file with a file id starting with
260    ``__file-customerdocument__`` (the marker string for customer files).
261
262    Like each other file store handler it does not handle the files
263    really (this is done by the global file store) but only computes
264    paths and things like this.
265    """
266    grok.implements(IFileStoreHandler)
267    grok.name(CUSTOMERDOCUMENT_FILE_STORE_NAME)
268
269    def pathFromFileID(self, store, root, file_id):
270        """All customer document files are put in directory ``customers``.
271        """
272        marker, filename, basename, ext = store.extractMarker(file_id)
273        sub_root = os.path.join(root, 'customers')
274        return super(CustomerDocumentFileStoreHandler, self).pathFromFileID(
275            store, sub_root, basename)
276
277    def createFile(self, store, root, filename, file_id, file):
278        """Create a browsable file-like object.
279        """
280        # call super method to ensure that any old files with
281        # different filename extension are deleted.
282        file, path, file_obj = super(
283            CustomerDocumentFileStoreHandler, self).createFile(
284            store, root,  filename, file_id, file)
285        return file, path, IkobaImageFile(
286            file_obj.filename, file_obj.data)
287
Note: See TracBrowser for help on using the repository browser.