source: main/waeup.ikoba/trunk/src/waeup/ikoba/documents/document.py @ 12646

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

We need to provide unique filenames. Let's use the document id for the filename and not the download_name of the respective viewlet.

  • Property svn:keywords set to Id
File size: 9.9 KB
Line 
1## $Id: document.py 12444 2015-01-11 22:43:35Z 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"""
19These are the public documents.
20"""
21import os
22import grok
23from grok import index
24from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
25from zope.event import notify
26from zope.component import getUtility
27from zope.component.interfaces import IFactory
28from zope.interface import implementedBy
29from zope.i18n import translate
30
31from waeup.ikoba.image import IkobaImageFile
32from waeup.ikoba.imagestorage import DefaultFileStoreHandler
33from waeup.ikoba.interfaces import (
34    IIkobaUtils, IObjectHistory,
35    IFileStoreNameChooser, IFileStoreHandler, IExtFileStore)
36from waeup.ikoba.interfaces import MessageFactory as _
37from waeup.ikoba.utils.helpers import attrs_to_fields, get_current_principal
38from waeup.ikoba.documents.interfaces import (
39    IDocument, IPublicDocument, IDocumentsUtils,
40    IPDFDocument, IHTMLDocument, IRESTDocument)
41
42
43@grok.subscribe(IDocument, grok.IObjectRemovedEvent)
44def handle_document_removed(document, event):
45    store = getUtility(IExtFileStore)
46    for filename in document.filenames:
47        store.deleteFileByContext(document, attr=filename)
48    return
49
50class Document(grok.Container):
51    """This is a document.
52    """
53    grok.implements(IDocument)
54    grok.provides(IDocument)
55    grok.baseclass()
56
57    form_fields_interface = None
58
59    # Kofa can store any number of files per Document object.
60    # However, we highly recommend to associate and store
61    # only one file per Document object. Thus the following
62    # tuple should contain only a single filename string.
63    filenames = ()
64
65    local_roles = [
66        'waeup.local.DocumentManager',
67        ]
68
69    user_id = None
70    state = None
71    translated_state = None
72    translated_class_name = None
73
74    @property
75    def history(self):
76        history = IObjectHistory(self)
77        return history
78
79    @property
80    def class_name(self):
81        return self.__class__.__name__
82
83    @property
84    def formatted_transition_date(self):
85        try:
86            return self.history.messages[-1].split(' - ')[0]
87        except IndexError:
88            return
89
90    @property
91    def connected_files(self):
92        return
93
94    @property
95    def is_verifiable(self):
96        return True, None
97
98    def setMD5(self):
99        """Determine md5 checksum of all files and store checksums as
100        document attributes.
101        """
102        return
103       
104class PublicDocumentBase(Document):
105    """This is a customer document baseclass.
106    """
107    grok.implements(IPublicDocument)
108    grok.provides(IPublicDocument)
109    grok.baseclass()
110
111    @property
112    def state(self):
113        state = IWorkflowState(self).getState()
114        return state
115
116    @property
117    def translated_state(self):
118        try:
119            TRANSLATED_STATES = getUtility(
120                IDocumentsUtils).TRANSLATED_DOCUMENT_STATES
121            return TRANSLATED_STATES[self.state]
122        except KeyError:
123            return
124
125    @property
126    def translated_class_name(self):
127        try:
128            DOCTYPES_DICT = getUtility(IDocumentsUtils).DOCTYPES_DICT
129            return DOCTYPES_DICT[self.class_name]
130        except KeyError:
131            return
132
133    def writeLogMessage(self, view, message):
134        ob_class = view.__implemented__.__name__.replace('waeup.ikoba.','')
135        self.__parent__.__parent__.logger.info(
136            '%s - %s - %s' % (ob_class, self.__name__, message))
137        return
138
139
140class PDFDocument(PublicDocumentBase):
141    """This is a  document for a single pdf upload file.
142    """
143    grok.implements(IPDFDocument)
144    grok.provides(IPDFDocument)
145
146    form_fields_interface = IPDFDocument
147
148    filenames = ('file.pdf',)
149
150PDFDocument = attrs_to_fields(PDFDocument)
151
152
153class HTMLDocument(PublicDocumentBase):
154    """This is a  document to render html-coded text.
155    """
156    grok.implements(IHTMLDocument)
157    grok.provides(IHTMLDocument)
158
159    form_fields_interface = IHTMLDocument
160
161    def __init__(self, *args, **kw):
162        super(HTMLDocument, self).__init__(*args, **kw)
163        self.html_dict = {}
164
165HTMLDocument = attrs_to_fields(HTMLDocument)
166
167
168class RESTDocument(PublicDocumentBase):
169    """This is a  document to render html-coded text.
170    """
171    grok.implements(IRESTDocument)
172    grok.provides(IRESTDocument)
173
174    form_fields_interface = IRESTDocument
175
176    def __init__(self, *args, **kw):
177        super(RESTDocument, self).__init__(*args, **kw)
178        self.html_dict = {}
179
180RESTDocument = attrs_to_fields(RESTDocument)
181
182
183class PDFDocumentFactory(grok.GlobalUtility):
184    """A factory for documents.
185    """
186    grok.implements(IFactory)
187    grok.name(u'waeup.PDFDocument')
188    title = u"Create a new PDF document.",
189    description = u"This factory instantiates new PDF documents."
190
191    def __call__(self, *args, **kw):
192        return PDFDocument(*args, **kw)
193
194    def getInterfaces(self):
195        return implementedBy(PDFDocument)
196
197
198class HTMLDocumentFactory(grok.GlobalUtility):
199    """A factory for HTML documents.
200    """
201    grok.implements(IFactory)
202    grok.name(u'waeup.HTMLDocument')
203    title = u"Create a new HTML document.",
204    description = u"This factory instantiates new HTML documents."
205
206    def __call__(self, *args, **kw):
207        return HTMLDocument(*args, **kw)
208
209    def getInterfaces(self):
210        return implementedBy(HTMLDocument)
211
212
213class RESTDocumentFactory(grok.GlobalUtility):
214    """A factory for REST documents.
215    """
216    grok.implements(IFactory)
217    grok.name(u'waeup.RESTDocument')
218    title = u"Create a new REST document.",
219    description = u"This factory instantiates new REST documents."
220
221    def __call__(self, *args, **kw):
222        return RESTDocument(*args, **kw)
223
224    def getInterfaces(self):
225        return implementedBy(RESTDocument)
226
227
228#: The file id marker for files
229DOCUMENT_FILE_STORE_NAME = 'file-document'
230
231
232class DocumentFileNameChooser(grok.Adapter):
233    """A file id chooser for :class:`Document` objects.
234
235    `context` is an :class:`Document` instance.
236
237    The delivered file_id contains the file id marker for
238    :class:`Document` objects in the central :class:`DocumentsContainer`.
239
240    This chooser is registered as an adapter providing
241    :class:`waeup.ikoba.interfaces.IFileStoreNameChooser`.
242
243    File store name choosers like this one are only convenience
244    components to ease the task of creating file ids for customer document
245    objects. You are nevertheless encouraged to use them instead of
246    manually setting up filenames for customer documents.
247
248    .. seealso:: :mod:`waeup.ikoba.imagestorage`
249
250    """
251
252    grok.context(IDocument)
253    grok.implements(IFileStoreNameChooser)
254
255    def checkName(self, name=None, attr=None):
256        """Check whether the given name is a valid file id for the context.
257
258        Returns ``True`` only if `name` equals the result of
259        :meth:`chooseName`.
260
261        """
262        return name == self.chooseName()
263
264    def chooseName(self, attr, name=None):
265        """Get a valid file id for customer document context.
266
267        *Example:*
268
269        For document with id 'd123'
270        with attr ``'nice_image.jpeg'`` this chooser would create:
271
272          ``'__file-document__nice_image_d123.jpeg'``
273
274        meaning that the nice image of this document would be
275        stored in the site-wide file storage in path:
276
277          ``nice_image_d123.jpeg``
278
279        """
280        basename, ext = os.path.splitext(attr)
281        doc_id = self.context.document_id
282        marked_filename = '__%s__%s_%s%s' % (
283            DOCUMENT_FILE_STORE_NAME,
284            basename, doc_id, ext)
285        return marked_filename
286
287
288class DocumentFileStoreHandler(DefaultFileStoreHandler, grok.GlobalUtility):
289    """ Document specific file handling.
290
291    This handler knows in which path in a filestore to store document
292    files and how to turn this kind of data into some (browsable)
293    file object.
294
295    It is called from the global file storage, when it wants to
296    get/store a file with a file id starting with
297    ``__file-document__`` (the marker string for customer files).
298
299    Like each other file store handler it does not handle the files
300    really (this is done by the global file store) but only computes
301    paths and things like this.
302    """
303    grok.implements(IFileStoreHandler)
304    grok.name(DOCUMENT_FILE_STORE_NAME)
305
306    def pathFromFileID(self, store, root, file_id):
307        """All document files are put in directory ``documents``.
308        """
309        marker, filename, basename, ext = store.extractMarker(file_id)
310        sub_root = os.path.join(root, 'documents')
311        return super(DocumentFileStoreHandler, self).pathFromFileID(
312            store, sub_root, basename)
313
314    def createFile(self, store, root, filename, file_id, file):
315        """Create a browsable file-like object.
316        """
317        # call super method to ensure that any old files with
318        # different filename extension are deleted.
319        file, path, file_obj = super(
320            DocumentFileStoreHandler, self).createFile(
321            store, root,  filename, file_id, file)
322        return file, path, IkobaImageFile(
323            file_obj.filename, file_obj.data)
324
325
326@grok.subscribe(IDocument, grok.IObjectAddedEvent)
327def handle_document_added(document, event):
328    """If a document is added the transition create is fired.
329    The latter produces a logging message.
330    """
331    if IWorkflowState(document).getState() is None:
332        IWorkflowInfo(document).fireTransition('create')
333    return
Note: See TracBrowser for help on using the repository browser.