source: main/trunk/openPLM/plmapp/controllers/document.py @ 400

Revision 400, 15.0 KB checked in by pcosquer, 8 years ago (diff)

models: store relative paths

Line 
1############################################################################
2# openPLM - open source PLM
3# Copyright 2010 Philippe Joulaud, Pierre Cosquer
4#
5# This file is part of openPLM.
6#
7#    openPLM is free software: you can redistribute it and/or modify
8#    it under the terms of the GNU General Public License as published by
9#    the Free Software Foundation, either version 3 of the License, or
10#    (at your option) any later version.
11#
12#    openPLM is distributed in the hope that it will be useful,
13#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#    GNU General Public License for more details.
16#
17#    You should have received a copy of the GNU General Public License
18#    along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
19#
20# Contact :
21#    Philippe Joulaud : ninoo.fr@gmail.com
22#    Pierre Cosquer : pierre.cosquer@insa-rennes.fr
23################################################################################
24
25"""
26"""
27
28import os
29import re
30import shutil
31
32import Image
33from django.conf import settings
34
35import openPLM.plmapp.models as models
36from openPLM.plmapp.exceptions import LockError, UnlockError, DeleteFileError
37from openPLM.plmapp.controllers.plmobject import PLMObjectController
38from openPLM.plmapp.controllers.base import get_controller
39
40class DocumentController(PLMObjectController):
41    """
42    A :class:`PLMObjectController` which manages
43    :class:`.Document`
44   
45    It provides methods to add or delete files, (un)lock them and attach a
46    :class:`.Document` to a :class:`.Part`.
47    """
48
49    def lock(self, doc_file):
50        """
51        Lock *doc_file* so that it can not be modified or deleted
52       
53        :exceptions raised:
54            * :exc:`ValueError` if *doc_file*.document is not self.object
55            * :exc:`.PermissionError` if :attr:`_user` is not the owner of
56              :attr:`object`
57            * :exc:`.PermissionError` if :attr:`object` is not editable.
58
59        :param doc_file:
60        :type doc_file: :class:`.DocumentFile`
61        """
62        self.check_permission("owner")
63        self.check_editable()
64        if doc_file.document.pk != self.object.pk:
65            raise ValueError("Bad file's document")
66        if not doc_file.locked:
67            doc_file.locked = True
68            doc_file.locker = self._user
69            doc_file.save()
70            self._save_histo("Locked",
71                             "%s locked by %s" % (doc_file.filename, self._user))
72        else:
73            raise LockError("File already locked")
74
75    def unlock(self, doc_file):
76        """
77        Unlock *doc_file* so that it can be modified or deleted
78       
79        :exceptions raised:
80            * :exc:`ValueError` if *doc_file*.document is not self.object
81            * :exc:`plmapp.exceptions.UnlockError` if *doc_file* is already
82              unlocked or *doc_file.locker* is not the current user
83
84        :param doc_file:
85        :type doc_file: :class:`.DocumentFile`
86        """
87
88        if doc_file.document.pk != self.object.pk:
89            raise ValueError("Bad file's document")
90        if not doc_file.locked:
91            raise UnlockError("File already unlocked")
92        if doc_file.locker != self._user:
93            raise UnlockError("Bad user")
94        doc_file.locked = False
95        doc_file.locker = None
96        doc_file.save()
97        self._save_histo("Locked",
98                         "%s unlocked by %s" % (doc_file.filename, self._user))
99
100    def add_file(self, f, update_attributes=True):
101        """
102        Adds file *f* to the document. *f* should be a :class:`~django.core.files.File`
103        with an attribute *name* (like an :class:`UploadedFile`).
104
105        If *update_attributes* is True (the default), :meth:`handle_added_file`
106        will be called with *f* as parameter.
107
108        :return: the :class:`.DocumentFile` created.
109        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
110              :attr:`object`
111        :raises: :exc:`.PermissionError` if :attr:`object` is not editable.
112        :raises: :exc:`ValueError` if the file size is superior to
113                 :attr:`settings.MAX_FILE_SIZE`
114        """
115        self.check_permission("owner")
116        self.check_editable()
117        if settings.MAX_FILE_SIZE != -1 and f.size > settings.MAX_FILE_SIZE:
118            raise ValueError("File too big, max size : %d bytes" % settings.MAX_FILE_SIZE)
119        f.name = f.name.encode("utf-8")
120        doc_file = models.DocumentFile.objects.create(filename=f.name, size=f.size,
121                        file=models.docfs.save(f.name, f), document=self.object)
122        self.save(False)
123        # set read only file
124        os.chmod(doc_file.file.path, 0400)
125        self._save_histo("File added", "file : %s" % f.name)
126        if update_attributes:
127            self.handle_added_file(doc_file)
128        return doc_file
129
130    def add_thumbnail(self, doc_file, thumbnail_file):
131        """
132        Sets *thumnail_file* as the thumbnail of *doc_file*. *thumbnail_file*
133        should be a :class:`~django.core.files.File` with an attribute *name*
134        (like an :class:`UploadedFile`).
135       
136        :exceptions raised:
137            * :exc:`ValueError` if *doc_file*.document is not self.objec
138            * :exc:`ValueError` if the file size is superior to
139              :attr:`settings.MAX_FILE_SIZE`
140            * :exc:`.PermissionError` if :attr:`_user` is not the owner of
141              :attr:`object`
142            * :exc:`.PermissionError` if :attr:`object` is not editable.
143        """
144        self.check_permission("owner")
145        self.check_editable()
146        if doc_file.document.pk != self.object.pk:
147            raise ValueError("Bad file's document")
148        if settings.MAX_FILE_SIZE != -1 and thumbnail_file.size > settings.MAX_FILE_SIZE:
149            raise ValueError("File too big, max size : %d bytes" % settings.MAX_FILE_SIZE)
150        basename = os.path.basename(thumbnail_file.name)
151        name = "%d%s" % (doc_file.id, os.path.splitext(basename)[1])
152        if doc_file.thumbnail:
153            doc_file.thumbnail.delete(save=False)
154        doc_file.thumbnail = models.thumbnailfs.save(name, thumbnail_file)
155        doc_file.save()
156        image = Image.open(doc_file.thumbnail.path)
157        image.thumbnail((150, 150), Image.ANTIALIAS)
158        image.save(doc_file.thumbnail.path)
159
160    def delete_file(self, doc_file):
161        """
162        Deletes *doc_file*, the file attached to *doc_file* is physically
163        removed.
164
165        :exceptions raised:
166            * :exc:`ValueError` if *doc_file*.document is not self.object
167            * :exc:`plmapp.exceptions.DeleteFileError` if *doc_file* is
168              locked
169            * :exc:`.PermissionError` if :attr:`_user` is not the owner of
170              :attr:`object`
171            * :exc:`.PermissionError` if :attr:`object` is not editable.
172
173        :param doc_file: the file to be deleted
174        :type doc_file: :class:`.DocumentFile`
175        """
176
177        self.check_permission("owner")
178        self.check_editable()
179        if doc_file.document.pk != self.object.pk:
180            raise ValueError("Bad file's document")
181        if doc_file.locked:
182            raise DeleteFileError("File is locked")
183        path = os.path.realpath(doc_file.file.path)
184        if not path.startswith(settings.DOCUMENTS_DIR):
185            raise DeleteFileError("Bad path : %s" % path)
186        os.chmod(path, 0700)
187        os.remove(path)
188        if doc_file.thumbnail:
189            doc_file.thumbnail.delete(save=False)
190        self._save_histo("File deleted", "file : %s" % doc_file.filename)
191        doc_file.delete()
192
193    def handle_added_file(self, doc_file):
194        """
195        Method called when adding a file (method :meth:`add_file`) with
196        *updates_attributes* true.
197
198        This method may be overridden to updates attributes with data from
199        *doc_file*. The default implementation does nothing.
200       
201        :param doc_file:
202        :type doc_file: :class:`.DocumentFile`
203        """
204        pass
205
206    def attach_to_part(self, part):
207        """
208        Links *part* (a :class:`.Part`) with
209        :attr:`~PLMObjectController.object`.
210        """
211
212        self.check_attach_part(part)       
213        if isinstance(part, PLMObjectController):
214            part = part.object
215        self.documentpartlink_document.create(part=part)
216        self._save_histo(models.DocumentPartLink.ACTION_NAME,
217                         "Part : %s - Document : %s" % (part, self.object))
218
219    def detach_part(self, part):
220        """
221        Delete link between *part* (a :class:`.Part`) and
222        :attr:`~PLMObjectController.object`.
223        """
224
225        if isinstance(part, PLMObjectController):
226            part = part.object
227        link = self.documentpartlink_document.get(part=part)
228        link.delete()
229        self._save_histo(models.DocumentPartLink.ACTION_NAME + " - delete",
230                         "Part : %s - Document : %s" % (part, self.object))
231
232    def get_attached_parts(self):
233        """
234        Returns all :class:`.Part` attached to
235        :attr:`~PLMObjectController.object`.
236        """
237        return self.object.documentpartlink_document.all()
238
239    def is_part_attached(self, part):
240        """
241        Returns True if *part* is attached to the current document.
242        """
243
244        if isinstance(part, PLMObjectController):
245            part = part.object
246        return bool(self.documentpartlink_document.filter(part=part))
247
248    def check_attach_part(self, part):
249        self.check_permission("owner")
250        if not (hasattr(part, "is_part") and part.is_part):
251            raise TypeError("%s is not a part" % part)
252
253        if isinstance(part, PLMObjectController):
254            part.check_readable()
255            part = part.object
256        else:
257            get_controller(part.type)(part, self._user).check_readable()
258        if self.is_part_attached(part):
259            raise ValueError("part is already attached.")
260   
261    def can_attach_part(self, part):
262        """
263        Returns True if *part* can be attached to the current document.
264        """
265        can_attach = False
266        try:
267            self.check_attach_part(part)
268            can_attach = True
269        except StandardError:
270            pass
271        return can_attach
272
273    def revise(self, new_revision):
274        # same as PLMObjectController + duplicate files (and their thumbnails)
275        rev = super(DocumentController, self).revise(new_revision)
276        for doc_file in self.object.files.all():
277            filename = doc_file.filename
278            path = models.docfs.get_available_name(filename)
279            shutil.copy(doc_file.file.path, models.docfs.path(path))
280            new_doc = models.DocumentFile.objects.create(file=path,
281                filename=filename, size=doc_file.size, document=rev.object)
282            new_doc.thumbnail = doc_file.thumbnail
283            if doc_file.thumbnail:
284                ext = os.path.splitext(doc_file.thumbnail.path)[1]
285                thumb = "%d%s" %(new_doc.id, ext)
286                thumb_path = re.sub(r"/\d+_*%s$" % ext, "/" + thumb,
287                                    doc_file.thumbnail.path)
288                shutil.copy(doc_file.thumbnail.path, thumb_path)
289                new_doc.thumbnail = os.path.basename(thumb_path)
290            new_doc.locked = False
291            new_doc.locker = None
292            new_doc.save()
293        return rev
294
295    def checkin(self, doc_file, new_file, update_attributes=True):
296        """
297        Updates *doc_file* with data from *new_file*. *doc_file*.thumbnail
298        is deleted if it is present.
299       
300        :exceptions raised:
301            * :exc:`ValueError` if *doc_file*.document is not self.object
302            * :exc:`ValueError` if the file size is superior to
303              :attr:`settings.MAX_FILE_SIZE`
304            * :exc:`plmapp.exceptions.UnlockError` if *doc_file* is locked
305              but *doc_file.locker* is not the current user
306            * :exc:`.PermissionError` if :attr:`_user` is not the owner of
307              :attr:`object`
308            * :exc:`.PermissionError` if :attr:`object` is not editable.
309
310        :param doc_file:
311        :type doc_file: :class:`.DocumentFile`
312        :param new_file: file with new data, same parameter as *f*
313                         in :meth:`add_file`
314        :param update_attributes: True if :meth:`handle_added_file` should be
315                                  called
316        """
317        self.check_permission("owner")
318        self.check_editable()
319        if doc_file.document.pk != self.object.pk:
320            raise ValueError("Bad file's document")
321        if doc_file.filename != new_file.name:
322            raise ValueError("Checkin document and document already in plm have different names")
323        if settings.MAX_FILE_SIZE != -1 and new_file.size > settings.MAX_FILE_SIZE:
324            raise ValueError("File too big, max size : %d bytes" % settings.MAX_FILE_SIZE)
325        if doc_file.locked:
326            self.unlock(doc_file)   
327        os.chmod(doc_file.file.path, 0700)
328        os.remove(doc_file.file.path)
329        doc_file.filename = new_file.name
330        doc_file.size = new_file.size
331        doc_file.file = models.docfs.save(new_file.name, new_file)
332        os.chmod(doc_file.file.path, 0400)
333        if doc_file.thumbnail:
334            doc_file.thumbnail.delete(save=False)
335        doc_file.save()
336        self._save_histo("Check-in", doc_file.filename)
337        if update_attributes:
338            self.handle_added_file(doc_file)
339           
340    def update_rel_part(self, formset):
341        u"""
342        Updates related part informations with data from *formset*
343       
344        :param formset:
345        :type formset: a modelfactory_formset of
346                        :class:`~plmapp.forms.ModifyRelPartForm`
347        """
348        if formset.is_valid():
349            for form in formset.forms:
350                document = form.cleaned_data["document"]
351                if document.pk != self.document.pk:
352                    raise ValueError("Bad document %s (%s expected)" % (document, self.object))
353                delete = form.cleaned_data["delete"]
354                part = form.cleaned_data["part"]
355                if delete:
356                    self.detach_part(part)
357
358    def update_file(self, formset):
359        u"""
360        Updates uploaded file informations with data from *formset*
361       
362        :param formset:
363        :type formset: a modelfactory_formset of
364                        :class:`~plmapp.forms.ModifyFileForm`
365        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
366              :attr:`object`
367        :raises: :exc:`.PermissionError` if :attr:`object` is not editable.
368        """
369       
370        self.check_permission("owner")
371        self.check_editable()
372        if formset.is_valid():
373            for form in formset.forms:
374                document = form.cleaned_data["document"]
375                if document.pk != self.document.pk:
376                    raise ValueError("Bad document %s (%s expected)" % (document, self.object))
377                delete = form.cleaned_data["delete"]
378                filename = form.cleaned_data["id"]
379                if delete:
380                    self.delete_file(filename)
381
Note: See TracBrowser for help on using the repository browser.