source: main/waeup.kofa/trunk/src/waeup/kofa/documents/document.py @ 12441

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

Add handler for document removal. Files must be removed too.

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