source: main/trunk/openPLM/plmapp/controllers/part.py @ 1439

Revision 1439, 31.5 KB checked in by pcosquer, 9 years ago (diff)

doc3D: clean up

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 openPLM.  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 datetime
29from collections import namedtuple
30
31from django.db.models.query import Q
32
33import openPLM.plmapp.models as models
34from openPLM.plmapp.units import DEFAULT_UNIT
35from openPLM.plmapp.controllers.plmobject import PLMObjectController
36from openPLM.plmapp.controllers.base import get_controller
37from openPLM.plmapp.cadformats import is_cad_file
38
39from openPLM.plmapp.exceptions import PermissionError
40
41Child = namedtuple("Child", "level link")
42Parent = namedtuple("Parent", "level link")
43
44class PartController(PLMObjectController):
45    u"""
46    Controller for :class:`.Part`.
47
48    This controller adds methods to manage Parent-Child links between two
49    Parts.
50    """
51
52    def check_add_child(self, child):
53        """
54        Checks if *child"* can be added to *self*.
55        If *child* can not be added, an exception is raised.
56       
57        :param child: child to be added
58        :type child: :class:`.Part`
59       
60        :raises: :exc:`ValueError` if *child* is already a child or a parent.
61        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
62            :attr:`object`.   
63        """
64        self.check_permission("owner")
65        self.check_editable()
66        if child.is_cancelled:
67            raise ValueError("Can not add child: child is cancelled.")
68        if child.is_deprecated:
69            raise ValueError("Can not add child: child is deprecated.")
70        if not child.is_part:
71            raise TypeError("Can not add child: not a Part")
72        # check if child is not a parent
73        if child.id == self.object.id:
74            raise ValueError("Can not add child: child is current object")
75        get_controller(child.type)(child, self._user).check_readable()
76        if self.is_ancestor(child):
77            raise ValueError("Can not add child %s to %s, it is a parent" %
78                                (child, self.object))
79        link = self.parentchildlink_parent.filter(child=child, end_time=None)
80        if link.exists():
81            raise ValueError("Can not add child, %s is already a child of %s" %
82                                (child, self.object))
83
84    def can_add_child(self, child):
85        """
86        Returns True if *child* can be added to *self*.
87        """
88
89        can_add = False
90        try:
91            self.check_add_child(child)
92            can_add = True
93        except StandardError:
94            pass
95        return can_add
96
97    def add_child(self, child, quantity, order, unit=DEFAULT_UNIT, **extension_data):
98        """
99        Adds *child* to *self*.
100
101        :param child: added child
102        :type child: :class:`.Part`
103        :param quantity: amount of *child*
104        :type quantity: positive float
105        :param order: order
106        :type order: positive int
107        :param unit: a valid unit
108       
109        :raises: :exc:`ValueError` if *child* is already a child or a parent.
110        :raises: :exc:`ValueError` if *quantity* or *order* are negative.
111        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
112            :attr:`object`.
113        :raises: :exc:`.PermissionError` if :attr:`object` is not editable.
114        """
115
116        if isinstance(child, PLMObjectController):
117            child = child.object
118        self.check_add_child(child)
119        if order < 0 or quantity < 0:
120            raise ValueError("Quantity or order is negative")
121        # data are valid : create the link
122        link = models.ParentChildLink()
123        link.parent = self.object
124        link.child = child
125        link.quantity = quantity
126        link.order = order
127        link.unit = unit
128        link.save()
129        # handle plces
130        for PCLE in models.get_PCLEs(self.object):
131            name = PCLE._meta.module_name
132            if name in extension_data and PCLE.one_per_link():
133                ext = PCLE(link=link, **extension_data[name])
134                ext.save()
135        # records creation in history
136        self._save_histo(link.ACTION_NAME,
137                         "parent : %s\nchild : %s" % (self.object, child))
138        return link
139
140    def delete_child(self, child):
141        u"""
142        Deletes *child* from current children and records this action in the
143        history.
144
145        .. note::
146            The link is not destroyed: its end_time is set to now.
147       
148        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
149            :attr:`object`.
150        :raises: :exc:`.PermissionError` if :attr:`object` is not editable.
151        """
152
153        self.check_permission("owner")
154        self.check_editable()
155        if isinstance(child, PLMObjectController):
156            child = child.object
157        link = self.parentchildlink_parent.get(child=child, end_time=None)
158        link.end_time = datetime.datetime.today()
159        link.save()
160        self._save_histo("Delete - %s" % link.ACTION_NAME, "child : %s" % child)
161
162    def modify_child(self, child, new_quantity, new_order, new_unit,
163            **extension_data):
164        """
165        Modifies information about *child*.
166
167        :param child: added child
168        :type child: :class:`.Part`
169        :param new_quantity: amount of *child*
170        :type new_quantity: positive float
171        :param new_order: order
172        :type new_order: positive int
173       
174        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
175            :attr:`object`.
176        :raises: :exc:`.PermissionError` if :attr:`object` is not editable.
177        """
178       
179        self.check_permission("owner")
180        self.check_editable()
181        if isinstance(child, PLMObjectController):
182            child = child.object
183        if new_order < 0 or new_quantity < 0:
184            raise ValueError("Quantity or order is negative")
185        link = models.ParentChildLink.objects.get(parent=self.object,
186                                                  child=child, end_time=None)
187        original_extension_data = link.get_extension_data()
188
189        if (link.quantity == new_quantity and link.order == new_order and
190            link.unit == new_unit and original_extension_data == extension_data):
191            # do not make an update if it is useless
192            return
193        link.end_time = datetime.datetime.today()
194        link.save()
195        # make a new link
196        link2, extensions = link.clone(quantity=new_quantity, order=new_order,
197                       unit=new_unit, end_time=None, extension_data=extension_data)
198        details = ""
199        if link.quantity != new_quantity:
200            details += "quantity changes from %f to %f\n" % (link.quantity, new_quantity)
201        if link.order != new_order:
202            details += "order changes from %d to %d" % (link.order, new_order)
203        if link.unit != new_unit:
204            details += "unit changes from %s to %s" % (link.unit, new_unit)
205
206        # TODO: details of extension changes
207
208        self._save_histo("Modify - %s" % link.ACTION_NAME, details)
209        link2.save(force_insert=True)
210        # save cloned extensions
211        for ext in extensions:
212            ext.link = link2
213            ext.save(force_insert=True)
214        # add new extensions
215        for PCLE in models.get_PCLEs(self.object):
216            name = PCLE._meta.module_name
217            if (name in extension_data and name not in original_extension_data
218                and PCLE.one_per_link()):
219                ext = PCLE(link=link2, **extension_data[name])
220                ext.save()
221        return link2
222
223    def replace_child(self, link, new_child):
224        """
225        Replaces a child by another one.
226
227        :param link: link being replaced, its data (extensions included)
228                     are copied
229        :type link: :class:`.ParentChildLink`
230        :param new_child: the new child
231        :type new_child: :class:`.Part`
232
233        :raises: :exc:`ValueError` if the link is invalid (already completed
234                 or its parent is not the current object)
235        :raises: all permissions raised by :meth:`check_add_child`
236        """
237        if link.end_time != None or link.parent_id != self.id:
238            raise ValueError("Invalid link")
239        if isinstance(new_child, PLMObjectController):
240            new_child = new_child.object
241        if link.child == new_child:
242            return link
243        self.check_add_child(new_child)
244        link.end_time = datetime.datetime.today()
245        link.save()
246        # make a new link
247        link2, extensions = link.clone(child=new_child, end_time=None)
248        details = u"Child changes from %s to %s" % (link.child, new_child)
249        self._save_histo("Modify - %s" % link.ACTION_NAME, details)
250        link2.save(force_insert=True)
251        # save cloned extensions
252        for ext in extensions:
253            ext.link = link2
254            ext.save(force_insert=True)
255        return link2       
256
257    def get_children(self, max_level=1, date=None,
258            related=("child", "child__state", "child__lifecycle"),
259            only_official=False, only=None):
260        """
261        Returns a list of all children at time *date*.
262       
263        :rtype: list of :class:`Child`
264        """
265       
266        objects = models.ParentChildLink.objects.order_by("-order")\
267                .select_related(*related)
268        if date is None:
269            links = objects.filter(end_time__exact=None)
270        else:
271            links = objects.filter(ctime__lte=date).exclude(end_time__lt=date)
272        if only is not None:
273            links = links.only(*only)
274        res = []
275        parents = [self.object.id]
276        level = 1
277        last_children = []
278        children_ids = []
279        while parents and (max_level < 0 or level <= max_level):
280            qs = links.filter(parent__in=parents)
281            parents = []
282            last = []
283            for link in qs.iterator():
284                parents.append(link.child_id)
285                child = Child(level, link)
286                last.append(child)
287                children_ids.append(link.child_id)
288                if level == 1:
289                    res.insert(0, child)
290                else:
291                    for c in last_children:
292                        if c.link.child_id == link.parent_id:
293                            res.insert(res.index(c) +1, child)
294                            break
295            last_children = last
296            level += 1
297        if only_official:
298            # retrieves all official children at *date* and then prunes the
299            # tree so that we only run one query
300            res2 = []
301            sh = models.StateHistory.objects.filter(plmobject__in=children_ids,
302                    state_category=models.StateHistory.OFFICIAL)
303            if date is None:
304                sh = sh.filter(end_time__exact=None)
305            else:
306                sh = sh.filter(start_time__lte=date).exclude(end_time__lt=date)
307            valid_children = set(sh.values_list("plmobject_id", flat=True))
308            # level_threshold is used to cut a "branch" of the tree
309            level_threshold = len(res) + 1 # all levels are inferior to this value
310            for child in res:
311                if child.level > level_threshold:
312                    continue
313                if child.link.child_id in valid_children:
314                    res2.append(child)
315                    level_threshold = len(res) + 1
316                else:
317                    level_threshold = child.level
318            res = res2
319        return res
320
321    def is_ancestor(self, part):
322        """
323        Returns True if *part* is an ancestor of the current object.
324        """
325        links = models.ParentChildLink.objects.filter(end_time__exact=None)
326        parents = [part.id]
327        last_children = []
328        while parents:
329            parents = links.filter(parent__in=parents).values_list("child",
330                    flat=True)
331            if self.id in parents:
332                return True
333        return False
334   
335    def get_parents(self, max_level=1, date=None,
336            related=("parent", "parent__state", "parent__lifecycle"),
337            only_official=False):
338        """
339        Returns a list of all parents at time *date*.
340       
341        :rtype: list of :class:`Parent`
342        """
343
344        objects = models.ParentChildLink.objects.order_by("-order")\
345                .select_related(*related)
346        if not date:
347            links = objects.filter(end_time__exact=None)
348        else:
349            links = objects.filter(ctime__lte=date).exclude(end_time__lt=date)
350        res = []
351        children = [self.object.id]
352        level = 1
353        last_parents = []
354        parents_ids = []
355        while children and (max_level < 0 or level <= max_level):
356            qs = links.filter(child__in=children)
357            children = []
358            last = []
359            for link in qs.iterator():
360                children.append(link.parent_id)
361                parent = Parent(level, link)
362                last.append(parent)
363                parents_ids.append(link.parent_id)
364                if level == 1:
365                    res.insert(0, parent)
366                else:
367                    for c in last_parents:
368                        if c.link.parent_id == link.child_id:
369                            res.insert(res.index(c) +1, parent)
370                            break
371            last_parents = last
372            level += 1
373        if only_official:
374            # retrieves all official children at *date* and then prunes the
375            # tree so that we only run one query
376            res2 = []
377            sh = models.StateHistory.objects.filter(plmobject__in=parents_ids,
378                    state_category=models.StateHistory.OFFICIAL)
379            if date is None:
380                sh = sh.filter(end_time__exact=None)
381            else:
382                sh = sh.filter(start_time__lte=date).exclude(end_time__lt=date)
383            valid_parents = set(sh.values_list("plmobject_id", flat=True))
384            # level_threshold is used to cut a "branch" of the tree
385            level_threshold = len(res) + 1 # all levels are inferior to this value
386            for parent in res:
387                if parent.level > level_threshold:
388                    continue
389                if parent.link.parent_id in valid_parents:
390                    res2.append(parent)
391                    level_threshold = len(res) + 1
392                else:
393                    level_threshold = parent.level
394            res = res2
395        return res
396
397    def update_children(self, formset):
398        u"""
399        Updates children informations with data from *formset*
400       
401        :param formset:
402        :type formset: a modelfactory_formset of
403                        :class:`~plmapp.forms.ModifyChildForm`
404       
405        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
406            :attr:`object`.
407        :raises: :exc:`.PermissionError` if :attr:`object` is not editable.
408        """
409
410        self.check_permission("owner")
411        self.check_editable()
412        if formset.is_valid():
413            for form in formset.forms:
414                parent = form.cleaned_data["parent"]
415                if parent.pk != self.object.pk:
416                    raise ValueError("Bad parent %s (%s expected)" % (parent, self.object))
417                delete = form.cleaned_data["delete"]
418                child = form.cleaned_data["child"]
419                if delete:
420                    self.delete_child(child)
421                else:
422                    quantity = form.cleaned_data["quantity"]
423                    order = form.cleaned_data["order"]
424                    unit = form.cleaned_data["unit"]
425                    self.modify_child(child, quantity, order, unit,
426                            **form.extensions)
427
428    def revise(self, new_revision, child_links=None, documents=(),
429            parents=()):
430        """
431        Revises the part. Does the same thing as :meth:`.PLMObjectController.revise`
432        and:
433           
434            * copies all :class:`.ParentChildLink` of *child_links*, with the
435              new revision as the new parent. If *child_links* is None (the
436              default), all current children are copied. If an empty sequence
437              is given, no links are copied.
438
439            * attaches all document of *documents*, by default, no documents
440              are attached. The method :meth:`get_suggested_documents` returns a
441              list of documents that should be interesting.
442
443            * replaces all parent links in *parents*. This arguments must be
444              a list of tuples (link (an instance of :class:`.ParentChildLink`),
445              parent (an instance of :class:`.PLMObject`)) where *parent* is
446              the parent whose the bom will be modified and *link* is the
447              source of data (quantity, unit, order...). *link* will be
448              ended if *parent* is a parent of the current part.
449              The method :meth:`get_suggested_parents` returns a list of
450              tuples that may interest the user who revises this part.
451        """
452        # same as PLMObjectController + add children
453        new_controller = super(PartController, self).revise(new_revision)
454        # adds the children
455        if child_links is None:
456            child_links = (x.link for x in self.get_children(1))
457        for link in child_links:
458            link.clone(save=True, parent=new_controller.object)
459        # attach the documents
460        for doc in documents:
461            models.DocumentPartLink.objects.create(part=new_controller.object,
462                    document=doc)
463        # for each parent, replace its child with the new revision
464        now = datetime.datetime.today()
465        for link, parent in parents:
466            link.clone(save=True, parent=parent, child=new_controller.object)
467            if link.parent_id == parent.id:
468                link.end_time = now
469                link.save()
470        return new_controller
471
472    def get_suggested_documents(self):
473        """
474        Returns a QuerySet of documents that should be suggested when the
475        user revises the part.
476
477        A document is suggested if:
478       
479            a. it is attached to the current part and:
480             
481                1. it is a *draft* and its superior revisions, if they exist,
482                   are *not* attached to the part
483
484                   or
485
486                2. it is *official* and its superior revisions, if they exist,
487                   are *not* attached to the part
488
489                   or
490
491                3. it is *official* and a superior revision is attached *and*
492                   another superior revision is not attached to the part
493
494            b. it is *not* attached to the current part, an inferior revision
495               is attached to the part and:
496
497                1. it is a draft
498
499                   or
500
501                2. it is official
502               
503        """
504        docs = []
505        links = self.get_attached_documents()
506        attached_documents = set(link.document_id for link in links)
507        for link in links:
508            document = link.document
509            ctrl = PLMObjectController(document, self._user)
510            revisions = ctrl.get_next_revisions()
511            attached_revisions = [d for d in revisions if d.id in attached_documents]
512            other_revisions = set(revisions).difference(attached_revisions)
513            if not attached_revisions:
514                if document.is_draft or document.is_official:
515                    docs.append(document.id)
516            else:
517                if document.is_official and not other_revisions:
518                    docs.append(document.id)
519            for rev in other_revisions:
520                if rev.is_official or rev.is_draft:
521                    docs.append(rev.id)
522        return models.Document.objects.filter(id__in=docs)
523
524    def get_suggested_parents(self):
525        """
526        Returns a list of suggested parents that should be suggested
527        when the part is revised.
528
529        This method returns a list of tuple (link (an instance of
530        :class:`.ParentChildLink`), parent (an instance of :class:`.PLMObject`)).
531        It does not returns a list of links, since it may suggest a part
532        that is not a parent but whose one of its previous revision is a parent.
533        We need a link to copy its data (order, quantity, unit and extensions).
534
535        A part is suggested as a parent if:
536
537            a. it is already a parent and:
538
539                1. no superior revisions are a parent and its state is draft
540                   or official
541   
542                   or
543               
544                2. no superior revisions exist and its state is proposed.
545
546            b. it is not a parent, a previous revision is a parent, its state
547               is a draft or a parent. In that case, the link of the most
548               superior parent revision is used.
549
550        """
551        parents = self.get_parents(1)
552        links = []
553        ids = set(p.link.parent_id for p in parents)
554        for level, link in parents:
555            parent = link.parent
556            ctrl = PLMObjectController(parent, self._user)
557            revisions = ctrl.get_next_revisions()
558            attached_revisions = [d for d in revisions if d.id in ids]
559            other_revisions = set(revisions).difference(attached_revisions)
560            if not attached_revisions:
561                if parent.is_draft or parent.is_official or \
562                    (parent.is_proposed and not other_revisions):
563                    links.append((link, parent))
564            for p in other_revisions:
565                if p.is_draft or p.is_official:
566                    links.append((link, p.part))
567        # it is possible that some parts are suggested twice or more
568        # if they are not a parent (they are a superior revision of a parent)
569        # so we must clean up links
570        links2 = dict() # id -> (link, parent)
571        for link, parent in links:
572            if parent.id in ids:
573                links2[parent.id] = (link, parent)
574            else:
575                # it is not a parent
576                try:
577                    l, p = links2[parent.id]
578                    if l.parent.ctime < link.parent.ctime:
579                        # true if parent is a superior revision
580                        links2[parent.id] = (link, parent)
581                except KeyError:
582                    links2[parent.id] = (link, parent)
583        return links2.values()
584
585    def attach_to_document(self, document):
586        """
587        Links *document* (a :class:`.Document`) with
588        :attr:`~PLMObjectController.object`.
589       
590        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
591            :attr:`object`.
592        """
593       
594        self.check_attach_document(document)
595        if isinstance(document, PLMObjectController):
596            document = document.object
597        self.documentpartlink_part.create(document=document)
598        self._save_histo(models.DocumentPartLink.ACTION_NAME,
599                         "Part : %s - Document : %s" % (self.object, document))
600
601    def detach_document(self, document):
602        """
603        Delete link between *document* (a :class:`.Document`)
604        and :attr:`~PLMObjectController.object`.
605       
606        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of
607            :attr:`object`.
608        """
609       
610        self.check_attach_document(document, True)
611        if isinstance(document, PLMObjectController):
612            document = document.object
613        link = self.documentpartlink_part.get(document=document)
614        link.delete()
615        self._save_histo(models.DocumentPartLink.ACTION_NAME + " - delete",
616                         "Part : %s - Document : %s" % (self.object, document))
617
618    def get_attached_documents(self):
619        """
620        Returns all :class:`.Document` attached to
621        :attr:`~PLMObjectController.object`.
622        """
623        return self.documentpartlink_part.all()
624
625    def get_detachable_documents(self):
626        """
627        Returns all attached documents the user can detach.
628        """
629        links = []
630        for link in self.get_attached_documents().select_related("document"):
631            doc = link.document
632            if self.can_detach_document(doc):
633                links.append(link.id)
634        return self.documentpartlink_part.filter(id__in=links)
635     
636    def is_document_attached(self, document):
637        """
638        Returns True if *document* is attached to the current part.
639        """
640
641        if isinstance(document, PLMObjectController):
642            document = document.object
643        return self.documentpartlink_part.filter(document=document).exists()
644   
645    def check_attach_document(self, document, detach=False):
646        if not hasattr(document, "is_document") or not document.is_document:
647            raise TypeError("%s is not a document" % document)
648        self.check_contributor()
649        if not (self.is_draft or document.is_draft):
650            raise ValueError("Can not attach: one of the part or document's state must be draft.")
651        if self.is_cancelled:
652            raise ValueError("Can not attach: part is cancelled.")
653        if self.is_deprecated:
654            raise ValueError("Can not attach: part is deprecated.")
655        if document.is_cancelled:
656            raise ValueError("Can not attach: document is cancelled.")
657        if document.is_deprecated:
658            raise ValueError("Can not attach: document is deprecated.")
659        if self.is_proposed:
660            raise ValueError("Can not attach: part's state is %s" % self.state.name)
661        if isinstance(document, PLMObjectController):
662            document.check_readable()
663            ctrl = document
664            document = document.object
665        else:
666            ctrl = get_controller(document.type)(document, self._user)
667            ctrl.check_readable()
668        self.check_readable()
669        if document.is_draft and self.is_draft:
670            owner_ok = True
671        elif document.is_draft or document.is_proposed:
672            owner_ok = ctrl.check_permission("owner", raise_=False)
673        else:
674            self.check_editable()
675            owner_ok = False
676        if not owner_ok:
677            self.check_permission("owner")
678       
679        if self.is_document_attached(document):
680            if not detach:
681                raise ValueError("Document is already attached to the part.")
682        elif detach:
683            raise ValueError("Document is not attached to the part.")
684
685    def can_attach_document(self, document):
686        """
687        Returns True if *document* can be attached to the current part.
688        """
689        can_attach = False
690        try:
691            self.check_attach_document(document)
692            can_attach = True
693        except StandardError:
694            pass
695        return can_attach
696
697    def can_detach_document(self, document):
698        """
699        Returns True if *document* can be detached.
700        """
701        can_detach = False
702        try:
703            self.check_attach_document(document, True)
704            can_detach = True
705        except StandardError:
706            pass
707        return can_detach
708
709    def update_doc_cad(self, formset):
710        u"""
711        Updates doc_cad informations with data from *formset*
712       
713        :param formset:
714        :type formset: a modelfactory_formset of
715                        :class:`~plmapp.forms.ModifyChildForm`
716       
717        :raises: :exc:`ValueError` if one of the document is not detachable.
718        """
719         
720        docs = set()
721        if formset.is_valid():
722            for form in formset.forms:
723                part = form.cleaned_data["part"]
724                if part.pk != self.object.pk:
725                    raise ValueError("Bad part %s (%s expected)" % (part, self.object))
726                delete = form.cleaned_data["delete"]
727                document = form.cleaned_data["document"]
728                if delete:
729                    docs.add(document)
730            if docs:
731                for doc in docs:
732                    self.check_attach_document(doc, True)
733                ids = (d.id for d in docs)
734                self.documentpartlink_part.filter(document__in=ids).delete()
735
736    def cancel(self):
737        """
738        Cancels the object:
739
740            * calls :meth:`.PLMObjectController.cancel`
741            * removes all :class:`.DocumentPartLink` related to the object
742            * removes all children/parents link (set their end_time)
743        """
744        super(PartController, self).cancel()
745        self.get_attached_documents().delete()
746        q = Q(parent=self.object) | Q(child=self.object)
747        now = datetime.datetime.today()
748        models.ParentChildLink.objects.filter(q, end_time=None).update(end_time=now)
749       
750    def check_cancel(self,raise_=True):
751        res = super(PartController, self).check_cancel(raise_=raise_)
752        if res :
753            q = Q(parent=self.object) | Q(child=self.object)
754            res = res and not models.ParentChildLink.objects.filter(q, end_time=None).exists()
755            if (not res) and raise_ :
756                raise PermissionError("This part is related to an other part.")
757            res = res and not self.get_attached_documents()
758            if (not res) and raise_ :
759                raise PermissionError("This part has a document related to it.")
760        return res
761
762    def clone(self,form, user, child_links, documents, block_mails=False, no_index=False):
763        new_ctrl = super(PartController, self).clone(form, user, block_mails, no_index)
764        if child_links :
765            for link in child_links:
766                link.clone(save=True, parent=new_ctrl.object)
767        if documents :
768            for doc in documents:
769                models.DocumentPartLink.objects.create(part=new_ctrl.object,
770                    document=doc)
771        return new_ctrl
772
773    def has_links(self):
774        """
775        Return true if the part :
776            * is a parent or a child
777            * is attached to at least one document
778        """
779        q = Q(parent=self.object) | Q(child=self.object)
780        res = not models.ParentChildLink.objects.filter(q, end_time=None).exists()
781        res = res and not self.get_attached_documents().exists()
782        return res
783
784   
785    def get_cad_files(self):
786        """
787        Returns an iterable of all :class:`.DocumentFile` related
788        to *part* that contain a CAD file. It retrieves all non deprecated
789        files of all documents parts to *part* and its children and
790        filters these files according to their extension (see :meth:`.is_cad_file`).
791        """
792        children = self.get_children(-1, related=("child",))
793        children_ids = set(c.link.child_id for c in children)
794        children_ids.add(self.id)
795        links = models.DocumentPartLink.objects.filter(part__in=children_ids)
796        docs = links.values_list("document", flat=True)
797        d_o_u = "document__owner__username"
798        files = models.DocumentFile.objects.filter(deprecated=False,
799                    document__in=set(docs))
800        # XXX : maybe its faster to build a complex query than retrieving
801        # each file and testing their extension
802        return (df for df in files.select_related(d_o_u) if is_cad_file(df.filename))
803
Note: See TracBrowser for help on using the repository browser.