source: main/trunk/openPLM/plmapp/controllers.py @ 65

Revision 65, 32.1 KB checked in by pjoulaud, 11 years ago (diff)

Addition of files upload/deletion in a document

Line 
1"""
2Introduction
3=============
4
5This module contains utilities to manage a :class:`~openPLM.plmapp.models.PLMObject`.
6It provides a new class, :class:`PLMObjectController`, which can be used to
7modify its attributes, promote/demote/revise it...
8
9All modifications are recorded in an history.
10
11How to use this module
12======================
13
14The controller for a ``PLMObject`` is :class:`PLMObjectController`.
15All subclasses of ``PLMObject`` may have their own controller to add
16functionalities or redefined default behaviors.
17
18To get a suitable controller for a ``PLMObject`` instances use :func:`get_controller`.
19For example, `get_controller('Part')` returns :class:`PartController`.
20
21If you have a ``PLMObject`` and an User, you can instanciate a controller.
22For example::
23
24    >>> # obj is a PLMObject and user an User
25    >>> controller_cls = get_controller(obj.type)
26    >>> controller = controller_cls(obj, user)
27
28Then you can modify/access the attributes of the PLMObject and save the
29modifications:
30
31    >>> controller.name = "New Name"
32    >>> "type" in controller.attributes
33    True
34    >>> controller.owner = user
35    >>> # as with django models, you should call *save* to register modifications
36    >>> controller.save()
37
38You can also promote/demote the ``PLMObject``:
39
40    >>> controller.state.name
41    'draft'
42    >>> controller.promote()
43    >>> controller.state.name
44    'official'
45    >>> controller.demote()
46    >>> controller.state.name
47    'draft'
48
49There are also two classmethods which can help to create a new ``PLMobject``:
50
51    * :meth:`~PLMObjectController.create`
52    * :meth:`~PLMObjectController.create_from_form`
53
54This two methods return an instance of :class:`PLMObjectController` (or one of
55its subclasses).
56
57Moreover, the method :meth:`~PLMObjectController.create_from_form` can be used
58to update informations from a form created with
59:func:`plmapp.forms.get_modification_form`.
60   
61
62.. _how-to-add-a-controller:
63
64How to add a controller
65=======================
66
67If you add a new model which inherits from :class:`~openPLM.plmapp.models.PLMObject`
68or one of its subclasses, you may want to add your own controller.
69
70You just have to declare a class which inherits (directly or not) from
71:class:`PLMObjectController`. To associate this class with your models, there
72are two possibilities:
73
74    * if your class has an attribute *MANAGED_TYPE*, its value (a class)
75      will be used.
76      For example::
77
78          class MyController(PLMObjectController):
79              MANAGED_TYPE = MyPart
80              ...
81             
82      *MyController* will be associated to *MyPart* and
83      ``get_controller("MyPart")`` will return *MyController*.
84    * if *MANAGED_TYPE* is not defined, the name class will be used: for
85      example, *MyPartController* will be associated to *MyPart*. The rule
86      is simple, it is just the name of the model followed by "Controller".
87      The model may not be defined when the controller is written.
88
89If a controller exploits none of theses possibilities, it will still
90work but it will not be associated to a type.
91
92.. note::
93
94    This association is possible without any registration because
95    :class:`PLMObjectController` metaclass is :class:`MetaController`.
96
97Classes and functions
98=====================
99
100This module defines several classes, here is a summary:
101
102    * metaclasses:
103        - :class:`MetaController`
104    * :class:`~collections.namedtuple` :
105        - :class:`Child`
106        - :class:`Parent`
107    * controllers:
108
109        ========================================= ============================
110                          Type                             Controller
111        ========================================= ============================
112        :class:`~openPLM.plmapp.models.PLMObject` :class:`PLMObjectController`
113        :class:`~openPLM.plmapp.models.Part`      :class:`PartController`
114        :class:`~openPLM.plmapp.models.Document`  :class:`DocumentController`
115        ========================================= ============================
116   
117    * functions:
118        :func:`get_controller`
119
120"""
121
122import os
123import re
124import shutil
125import datetime
126from collections import namedtuple
127
128from django.conf import settings
129from django.core.exceptions import ObjectDoesNotExist
130
131try:
132    import openPLM.plmapp.models as models
133    from openPLM.plmapp.exceptions import RevisionError, LockError, UnlockError, \
134        AddFileError, DeleteFileError
135except (ImportError, AttributeError):
136    import plmapp.models as models
137    from plmapp.exceptions import RevisionError, LockError, UnlockError, \
138        AddFileError, DeleteFileError
139
140_controller_rx = re.compile(r"(?P<type>\w+)Controller")
141
142class MetaController(type):
143    #: dict<type_name(str) : Controller(like :class:`PLMObjectController`)>
144    controllers_dict = {}
145
146    def __new__(mcs, name, bases, attrs):
147        cls = type.__new__(mcs, name, bases, attrs)
148        if "MANAGED_TYPE" in attrs:
149            managed = attrs["MANAGED_TYPE"].__name__
150        else:
151            m = _controller_rx.match(name)
152            if m:
153                managed = m.group("type")
154            else:
155                # the controller is not interresting
156                return cls
157        mcs.controllers_dict[managed] = cls
158        return cls
159
160    @classmethod
161    def get_controller(cls, type_name):
162        """
163        Returns the a controller (subclass of :class:`PLMObjectController`)
164        associated to *type_name* (a string).
165        """
166        if type_name in cls.controllers_dict:
167            return cls.controllers_dict[type_name]
168        else:
169            # get his model and return his parent controller
170            if type_name == "PLMObject":
171                # just a security to prevent an infinite recursion
172                return PLMObjectController
173            else:
174                model = models.get_all_plmobjects()[type_name]
175                parents = [p for p in model.__bases__
176                                if issubclass(p, models.PLMObject)]
177                return cls.get_controller(parents[0].__name__)
178
179#: shortcut for :meth:`MetaController.get_controller`
180get_controller = MetaController.get_controller
181
182class PLMObjectController(object):
183    u"""
184    Object used to manage a :class:`~plmapp.models.PLMObject` and store his
185    modification in an history
186   
187    :attributes:
188        .. attribute:: object
189
190            The :class:`~openPLM.plmapp.models.PLMObject` managed by the controller
191
192    :param obj: managed object
193    :type obj: a subinstance of :class:`~openPLM.plmapp.models.PLMObject`
194    :param user: user who modify *obj*
195    :type user: :class:`~django.contrib.auth.models.User`
196    """
197
198    __metaclass__ = MetaController
199
200    def __init__(self, obj, user):
201        self.object = obj
202        self._user = user
203        self.__histo = ""
204
205    @classmethod
206    def create(cls, reference, type, revision, user, data={}):
207        u"""
208        This method builds a new :class:`~openPLM.plmapp.models.PLMObject` of
209        type *class_* and return a :class:`PLMObjectController` associated to
210        the created object.
211
212        Raises :exc:`ValueError` if *reference*, *type* or *revision* are
213        empty. Raises :exc:`ValueError` if *type* is not valid.
214
215        :param reference: reference of the objet
216        :param type: type of the object
217        :param revision: revision of the object
218        :param user: user who creates/owns the object
219        :param data: a dict<key, value> with informations to add to the plmobject
220        :rtype: :class:`PLMObjectController`
221        """
222       
223        if not reference or not type or not revision:
224            raise ValueError("Empty value not permitted for reference/type/revision")
225        try:
226            class_ = models.get_all_plmobjects()[type]
227        except KeyError:
228            raise ValueError("Incorrect type")
229        # create an object
230        obj = class_()
231        obj.reference = reference
232        obj.type = type
233        obj.revision = revision
234        obj.owner = user
235        obj.creator = user
236        if data:
237            for key, value in data.iteritems():
238                if key not in ["reference", "type", "revision"]:
239                    setattr(obj, key, value)
240        obj.state = models.get_default_state(obj.lifecycle)
241        obj.save()
242        res = cls(obj, user)
243        # record ceation in history
244        infos = {"type" : type, "reference" : reference, "revision" : revision}
245        infos.update(data)
246        details = ",".join("%s : %s" % (k, v) for k, v in infos.items())
247        res._save_histo("Create", details)
248        return res
249       
250    @classmethod
251    def create_from_form(cls, form, user):
252        u"""
253        Creates a :class:`PLMObjectController` from *form* and associates *user*
254        as the creator/owner of the PLMObject.
255       
256        This method raises :exc:`ValueError` if *form* is invalid.
257
258        :param form: a django form associated to a model
259        :param user: user who creates/owns the object
260        :rtype: :class:`PLMObjectController`
261        """
262        if form.is_valid():
263            ref = form.cleaned_data["reference"]
264            type = form.Meta.model.__name__
265            rev = form.cleaned_data["revision"]
266            obj = cls.create(ref, type, rev, user, form.cleaned_data)
267            return obj
268        else:
269            raise ValueError("form is invalid")
270       
271    def update_from_form(self, form):
272        u"""
273        Updates :attr:`object` from data of *form*
274       
275        This method raises :exc:`ValueError` if *form* is invalid.
276        """
277        if form.is_valid():
278            need_save = False
279            for key, value in form.cleaned_data.iteritems():
280                if key not in ["reference", "type", "revision"]:
281                    setattr(self, key, value)
282                    need_save = True
283            if need_save:
284                self.save()
285        else:
286            raise ValueError("form is invalid")
287
288    def promote(self):
289        u"""
290        Promotes :attr:`object` in his lifecycle and writes his promotion in
291        the history
292        """
293        if self.object.is_promotable():
294            state = self.object.state
295            lifecycle = self.object.lifecycle
296            lcl = lifecycle.to_states_list()
297            try:
298                new_state = lcl.next_state(state.name)
299                self.object.state = models.State.objects.get_or_create(name=new_state)[0]
300                self.object.save()
301                self._save_histo("Promote",
302                                 "change state from %(first)s to %(second)s" % \
303                                     {"first" :state.name, "second" : new_state})
304
305            except IndexError:
306                # FIXME raises it ?
307                pass
308
309    def demote(self):
310        u"""
311        Demotes :attr:`object` in his lifecycle and writes his demotion in the
312        history
313        """
314        state = self.object.state
315        lifecycle = self.object.lifecycle
316        lcl = lifecycle.to_states_list()
317        try:
318            new_state = lcl.previous_state(state.name)
319            self.object.state = models.State.objects.get_or_create(name=new_state)[0]
320            self.object.save()
321            self._save_histo("Demote", "change state from %(first)s to %(second)s" % \
322                    {"first" :state.name, "second" : new_state})
323        except IndexError:
324            # FIXME raises it ?
325            pass
326
327    def __setattr__(self, attr, value):
328        if hasattr(self, "object") and hasattr(self.object, attr) and \
329           not attr in self.__dict__:
330            old_value = getattr(self.object, attr)
331            setattr(self.object, attr, value)
332            field = self.object._meta.get_field(attr).verbose_name
333            message = "%(field)s : changes from '%(old)s' to '%(new)s'" % \
334                    {"field" : field, "old" : old_value, "new" : value}
335            self.__histo += message + "\n"
336        else:
337            super(PLMObjectController, self).__setattr__(attr, value)
338
339    def __getattr__(self, attr):
340        obj = object.__getattribute__(self, "object")
341        if hasattr(self, "object") and hasattr(obj, attr) and \
342           not attr in self.__dict__:
343            return getattr(obj, attr)
344        else:
345            return object.__getattribute__(self, attr)
346
347    def save(self, with_history=True):
348        u"""
349        Saves :attr:`object` and records its history in the database.
350        If *with_history* is False, the history is not recorded.
351        """
352        self.object.save()
353        if self.__histo and with_history:
354            self._save_histo("Modify", self.__histo)
355            self.__histo = ""
356
357    def _save_histo(self, action, details):
358        histo = models.History()
359        histo.plmobject = self.object
360        histo.action = action
361        histo.details = details
362        histo.user = self._user
363        histo.save()
364
365    def revise(self, new_revision):
366        u"""
367        Makes a new revision : duplicates :attr:`object`. The duplicated
368        object's revision is *new_revision*.
369
370        Returns a controller of the new object.
371        """
372       
373        if not new_revision or new_revision == self.revision:
374            raise RevisionError("Bad value for new_revision")
375        if models.RevisionLink.objects.filter(old=self.object.pk):
376            raise RevisionError("a revision already exists for %s" % self.object)
377        data = {}
378        fields = self.get_modification_fields() + self.get_creation_fields()
379        for attr in fields:
380            if attr not in ("reference", "type", "revision"):
381                data[attr] = getattr(self.object, attr)
382        data["state"] = models.get_default_state(self.lifecycle)
383        new_controller = self.create(self.reference, self.type, new_revision, self._user,
384                                     data)
385        details = "old : %s, new : %s" % (self.object, new_controller.object)
386        self._save_histo(models.RevisionLink.ACTION_NAME, details)
387        models.RevisionLink.objects.create(old=self.object, new=new_controller.object)
388        return new_controller
389
390    def is_revisable(self):
391        """
392        Returns True if :attr:`object` is revisable : if :meth:`revise` can be
393        called safely
394        """
395        # objects.get fails if a link does not exist
396        # we can revise if any links exist
397        try:
398            models.RevisionLink.objects.get(old=self.object.pk)
399            return False
400        except ObjectDoesNotExist:
401            return True
402   
403    def get_previous_revisions(self):
404        try:
405            link = models.RevisionLink.objects.get(new=self.object.pk)
406            controller = type(self)(link.old, self._user)
407            return controller.get_previous_revisions() + [link.old]
408        except ObjectDoesNotExist:
409            return []
410
411    def get_next_revisions(self):
412        try:
413            link = models.RevisionLink.objects.get(old=self.object.pk)
414            controller = type(self)(link.new, self._user)
415            return [link.new] + controller.get_next_revisions()
416        except ObjectDoesNotExist:
417            return []
418
419    def get_all_revisions(self):
420        """
421        Returns a list of all revisions, ordered from less recent to most recent
422       
423        :rtype: list of :class:`~openPLM.plmapp.models.PLMObject`
424        """
425        return self.get_previous_revisions() + [self.object] +\
426               self.get_next_revisions()
427
428Child = namedtuple("Child", "level link")
429Parent = namedtuple("Parent", "level link")
430
431class PartController(PLMObjectController):
432    u"""
433    Controller for :class:`~openPLM.plmapp.models.Part`.
434
435    This controller adds methods to manage Parent-Child links between two
436    Parts.
437    """
438
439    def add_child(self, child, quantity, order):
440        """
441        Adds *child* to *self*.
442
443        :param child: added child
444        :type child: :class:`~openPLM.plmapp.models.Part`
445        :param quantity: amount of *child*
446        :type quantity: positive float
447        :param order: order
448        :type order: positive int
449       
450        Raises :exc:`ValueError` if *child* is already a child or a parent.
451        Raises :exc:`ValueError` if *quantity* or *order* are negative.
452        """
453
454        if isinstance(child, PLMObjectController):
455            child = child.object
456        # check if child is not a parent
457        if child == self.object:
458            raise ValueError("Can not add child : child is current object")
459        parents = (p.link.parent.pk for p in self.get_parents(-1))
460        if child.pk in parents:
461            raise ValueError("Can not add child %s to %s, it is a parent" %
462                                (child, self.object))
463        # check if child is not already a direct child
464        if child.pk in (c.link.child.pk for c in self.get_children(1)):
465            raise ValueError("%s is already a child of %s" % (child, self.object))
466        if order < 0 or quantity < 0:
467            raise ValueError("Quantity or order is negative")
468        # data are valid : create the link
469        link = models.ParentChildLink()
470        link.parent = self.object
471        link.child = child
472        link.quantity = quantity
473        link.order = order
474        link.save()
475        # records creation in history
476        self._save_histo(link.ACTION_NAME,
477                         "parent : %s\nchild : %s" % (self.object, child))
478
479    def delete_child(self, child):
480        u"""
481        Deletes *child* from current children and records this action in the
482        history.
483
484        .. note::
485            The link is not destroyed: its end_time is set to now.
486        """
487
488        if isinstance(child, PLMObjectController):
489            child = child.object
490        link = models.ParentChildLink.objects.get(parent=self.object,
491                                                  child=child, end_time=None)
492        link.end_time = datetime.datetime.today()
493        link.save()
494        self._save_histo("Delete - %s" % link.ACTION_NAME, "child : %s" % child)
495
496    def modify_child(self, child, new_quantity, new_order):
497        """
498        Modifies information about *child*.
499
500        :param child: added child
501        :type child: :class:`~openPLM.plmapp.models.Part`
502        :param new_quantity: amount of *child*
503        :type new_quantity: positive float
504        :param new_order: order
505        :type new_order: positive int
506        """
507        if isinstance(child, PLMObjectController):
508            child = child.object
509        if new_order < 0 or new_quantity < 0:
510            raise ValueError("Quantity or order is negative")
511        link = models.ParentChildLink.objects.get(parent=self.object,
512                                                  child=child, end_time=None)
513        if link.quantity == new_quantity and link.order == new_order:
514            # do not make an update if it is useless
515            return
516        link.end_time = datetime.datetime.today()
517        link.save()
518        # make a new link
519        link2 = models.ParentChildLink(parent=self.object, child=child,
520                                       quantity=new_quantity, order=new_order)
521        details = ""
522        if link.quantity != new_quantity:
523            details += "quantity changes from %d to %d\n" % (link.quantity, new_quantity)
524        if link.order != new_order:
525            details += "order changes from %d to %d" % (link.order, new_order)
526        self._save_histo("Modify - %s" % link.ACTION_NAME, details)
527        link2.save(force_insert=True)
528
529    def get_children(self, max_level=1, current_level=1, date=None):
530        """
531        Returns a list of all children at time *date*.
532       
533        :rtype: list of :class:`Child`
534        """
535
536        if max_level != -1 and current_level > max_level:
537            return []
538        if not date:
539            links = models.ParentChildLink.objects.filter(parent=self.object,
540                        end_time__exact=None)
541        else:
542            links = models.ParentChildLink.objects.filter(parent=self.object,
543                         ctime__lt=date).exclude(end_time__lt=date)
544        res = []
545        for link in links.order_by("order", "child__reference"):
546            res.append(Child(current_level, link))
547            pc = PartController(link.child, self._user)
548            res.extend(pc.get_children(max_level, current_level + 1, date))
549        return res
550   
551    def get_parents(self, max_level=1, current_level=1, date=None):
552        """
553        Returns a list of all parents at time *date*.
554       
555        :rtype: list of :class:`Parent`
556        """
557
558        if max_level != -1 and current_level > max_level:
559            return []
560        if not date:
561            links = models.ParentChildLink.objects.filter(child=self.object,
562                        end_time__exact=None)
563        else:
564            links = models.ParentChildLink.objects.filter(child=self.object,
565                         ctime__lt=date).exclude(end_time__lt=date)
566        res = []
567        for link in links:
568            res.append(Parent(current_level, link))
569            pc = PartController(link.parent, self._user)
570            res.extend(pc.get_parents(max_level, current_level + 1, date))
571        return res
572
573    def update_children(self, formset):
574        u"""
575        Updates children informations with data from *formset*
576       
577        :param formset:
578        :type formset: a modelfactory_formset of
579                        :class:`~plmapp.forms.ModifyChildForm`
580        """
581        if formset.is_valid():
582            for form in formset.forms:
583                parent = form.cleaned_data["parent"]
584                if parent.pk != self.object.pk:
585                    raise ValueError("Bad parent %s (%s expected)" % (parent, self.object))
586                delete = form.cleaned_data["delete"]
587                child = form.cleaned_data["child"]
588                if delete:
589                    self.delete_child(child)
590                else:
591                    quantity = form.cleaned_data["quantity"]
592                    order = form.cleaned_data["order"]
593                    self.modify_child(child, quantity, order)
594
595    def revise(self, new_revision):
596        # same as PLMOBjectController + add children
597        new_controller = super(PartController, self).revise(new_revision)
598        for level, link in self.get_children(1):
599            new_controller.add_child(link.child, link.quantity, link.order)
600        return new_controller
601
602    def attach_to_document(self, document):
603        """
604        Links *document* (a :class:`~openPLM.plmapp.models.Document`) with
605        :attr:`~PLMObjectController.object`.
606        """
607
608        if isinstance(document, PLMObjectController):
609            document = document.object
610        models.DocumentPartLink.objects.create(part=self.object, document=document)
611        self._save_histo(models.DocumentPartLink.ACTION_NAME,
612                         "Part : %s - Document : %s" % (self.object, document))
613
614    def detach_document(self, document):
615        """
616        Delete link between *document* (a :class:`~openPLM.plmapp.models.Document`)
617        and :attr:`~PLMObjectController.object`.
618        """
619
620        if isinstance(document, PLMObjectController):
621            document = document.object
622        link = models.DocumentPartLink.objects.get(document=document,
623                                                   part=self.object)
624        link.delete()
625        self._save_histo(models.DocumentPartLink.ACTION_NAME + " - delete",
626                         "Part : %s - Document : %s" % (self.object, document))
627
628    def get_attached_documents(self):
629        """
630        Returns all :class:`~openPLM.plmapp.models.Document` attached to
631        :attr:`~PLMObjectController.object`.
632        """
633        return models.DocumentPartLink.objects.filter(part=self.object)
634
635
636class DocumentController(PLMObjectController):
637    """
638    A :class:`PLMObjectController` which manages
639    :class:`~openPLM.plmapp.models.Document`
640   
641    It provides methods to add or delete files, (un)lock them and attach a
642    :class:`~openPLM.plmapp.models.Document` to a :class:`~openPLM.plmapp.models.Part`.
643    """
644
645    def lock(self, doc_file):
646        """
647        Lock *doc_file* so that it can not be modified or deleted
648       
649        :exceptions raised:
650            * :exc:`ValueError` if *doc_file*.document is not self.object
651
652        :param doc_file:
653        :type doc_file: :class:`~openPLM.plmapp.models.DocumentFile`
654        """
655        if doc_file.document.pk != self.object.pk:
656            raise ValueError("Bad file's document")
657        if not doc_file.locked:
658            doc_file.locked = True
659            doc_file.locker = self._user
660            doc_file.save()
661            self._save_histo("Locked",
662                             "%s locked by %s" % (doc_file.filename, self._user))
663        else:
664            raise LockError("File already locked")
665
666    def unlock(self, doc_file):
667        """
668        Unlock *doc_file* so that it can be modified or deleted
669       
670        :exceptions raised:
671            * :exc:`ValueError` if *doc_file*.document is not self.object
672            * :exc:`plmapp.exceptions.UnlockError` if *doc_file* is already
673              unlocked or *doc_file.locker* is not the current user
674
675        :param doc_file:
676        :type doc_file: :class:`~openPLM.plmapp.models.DocumentFile`
677        """
678
679        if doc_file.document.pk != self.object.pk:
680            raise ValueError("Bad file's document")
681        if not doc_file.locked:
682            raise UnlockError("File already unlocked")
683        if doc_file.locker != self._user:
684            raise UnlockError("Bad user")
685
686        doc_file.locked = False
687        doc_file.locker = None
688        doc_file.save()
689        self._save_histo("Locked",
690                         "%s unlocked by %s" % (doc_file.filename, self._user))
691
692    def add_file(self, f, update_attributes=True):
693        """
694        Adds file *f* to the document. *f* should be a :class:`~django.core.files.File`
695        with an attribute *name* (like an :class:`UploadedFile`).
696
697        If *update_attributes* is True (the default), :meth:`handle_added_file`
698        will be called with *f* as parameter.
699
700        :return: the :class:`~openPLM.plmapp.models.DocumentFile` created.
701        """
702        doc_file = models.DocumentFile()
703        doc_file.filename = f.name
704        doc_file.size = f.size
705        doc_file.file = models.docfs.save(f.name, f)
706        doc_file.document = self.object
707        doc_file.save()
708        self.save(False)
709        # set read only file
710        os.chmod(doc_file.file.path, 0400)
711        self._save_histo("File added", "file : %s" % f.name)
712        if update_attributes:
713            self.handle_added_file(doc_file)
714        return doc_file
715
716    def delete_file(self, doc_file):
717        """
718        Deletes *doc_file*, the file attached to *doc_file* is physically
719        removed.
720
721        :exceptions raised:
722            * :exc:`ValueError` if *doc_file*.document is not self.object
723            * :exc:`plmapp.exceptions.DeleteFileError` if *doc_file* is
724              locked
725
726        :param doc_file:
727        :type doc_file: :class:`~openPLM.plmapp.models.DocumentFile`
728        """
729
730        if doc_file.document.pk != self.object.pk:
731            raise ValueError("Bad file's document")
732        if doc_file.locked:
733            raise DeleteFileError("File is locked")
734        path = os.path.realpath(doc_file.file.path)
735        if not path.startswith(settings.DOCUMENTS_DIR):
736            raise DeleteFileError("Bad path : %s" % path)
737        os.chmod(path, 0700)
738        os.remove(path)
739        self._save_histo("File deleted", "file : %s" % doc_file.filename)
740        doc_file.delete()
741
742    def handle_added_file(self, doc_file):
743        """
744        Method called when adding a file (method :meth:`add_file`) with
745        *updates_attributes* true.
746
747        This method may be overridden to updates attributes with data from
748        *doc_file*. The default implementation does nothing.
749       
750        :param doc_file:
751        :type doc_file: :class:`~openPLM.plmapp.models.DocumentFile`
752        """
753        pass
754
755    def attach_to_part(self, part):
756        """
757        Links *part* (a :class:`~openPLM.plmapp.models.Part`) with
758        :attr:`~PLMObjectController.object`.
759        """
760
761        if isinstance(part, PLMObjectController):
762            part = part.object
763        models.DocumentPartLink.objects.create(document=self.object, part=part)
764        self._save_histo(models.DocumentPartLink.ACTION_NAME,
765                         "Part : %s - Document : %s" % (part, self.object))
766
767    def detach_part(self, part):
768        """
769        Delete link between *part* (a :class:`~openPLM.plmapp.models.Part`) and
770        :attr:`~PLMObjectController.object`.
771        """
772
773        if isinstance(part, PLMObjectController):
774            part = part.object
775        link = models.DocumentPartLink.objects.get(document=self.object,
776                                                   part=part)
777        link.delete()
778        self._save_histo(models.DocumentPartLink.ACTION_NAME + " - delete",
779                         "Part : %s - Document : %s" % (part, self.object))
780
781    def get_attached_parts(self):
782        """
783        Returns all :class:`~openPLM.plmapp.models.Part` attached to
784        :attr:`~PLMObjectController.object`.
785        """
786        return models.DocumentPartLink.objects.filter(document=self.object)
787
788    def revise(self, new_revision):
789        rev = super(DocumentController, self).revise(new_revision)
790        for doc_file in self.object.files.all():
791            filename = doc_file.filename
792            path = models.docfs.get_available_name(filename)
793            shutil.copy(doc_file.file.path, path)
794            new_doc = models.DocumentFile.objects.create(file=path,
795                filename=filename, size=doc_file.size, document=rev.object)
796            new_doc.locked = False
797            new_doc.locker = None
798            new_doc.save()
799        return rev
800
801    def checkin(self, doc_file, new_file, update_attributes=True):
802        """
803        Updates *doc_file* with data from *new_file*.
804       
805        :exceptions raised:
806            * :exc:`ValueError` if *doc_file*.document is not self.object
807            * :exc:`plmapp.exceptions.UnlockError` if *doc_file* is locked
808              but *doc_file.locker* is not the current user
809
810        :param doc_file:
811        :type doc_file: :class:`~openPLM.plmapp.models.DocumentFile`
812        :param new_file: file with new data, same parameter as *f*
813                         in :meth:`add_file`
814        :param update_attributes: True if :meth:`handle_added_file` should be
815                                  called
816        """
817        if doc_file.document.pk != self.object.pk:
818            raise ValueError("Bad file's document")
819        if doc_file.locked:
820            self.unlock(doc_file)   
821        os.chmod(doc_file.file.path, 0700)
822        os.remove(doc_file.file.path)
823        doc_file.filename = new_file.name
824        doc_file.size = new_file.size
825        doc_file.file = models.docfs.save(new_file.name, new_file)
826        os.chmod(doc_file.file.path, 0400)
827        doc_file.save()
828        self._save_histo("Check-in", doc_file.filename)
829        if update_attributes:
830            self.handle_added_file(doc_file)
831           
832    def update_rel_part(self, formset):
833        u"""
834        Updates related part informations with data from *formset*
835       
836        :param formset:
837        :type formset: a modelfactory_formset of
838                        :class:`~plmapp.forms.ModifyRelPartForm`
839        """
840        if formset.is_valid():
841            for form in formset.forms:
842                document = form.cleaned_data["document"]
843                if document.pk != self.document.pk:
844                    raise ValueError("Bad document %s (%s expected)" % (document, self.object))
845                delete = form.cleaned_data["delete"]
846                part = form.cleaned_data["part"]
847                if delete:
848                    self.detach_part(part)
849
850
851    def update_file(self, formset):
852        u"""
853        Updates uploaded file informations with data from *formset*
854       
855        :param formset:
856        :type formset: a modelfactory_formset of
857                        :class:`~plmapp.forms.ModifyFileForm`
858        """
859        if formset.is_valid():
860            for form in formset.forms:
861                document = form.cleaned_data["document"]
862                if document.pk != self.document.pk:
863                    raise ValueError("Bad document %s (%s expected)" % (document, self.object))
864                delete = form.cleaned_data["delete"]
865                filename = form.cleaned_data["id"]
866                if delete:
867                    self.delete_file(filename)
868
869                   
870
Note: See TracBrowser for help on using the repository browser.