source: main/trunk/openPLM/plmapp/controllers/plmobject.py @ 402

Revision 402, 16.6 KB checked in by pcosquer, 9 years ago (diff)

controllers: add stuff to block mails sending

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 re
29
30from django.conf import settings
31from django.core.exceptions import ObjectDoesNotExist
32
33import openPLM.plmapp.models as models
34from openPLM.plmapp.exceptions import RevisionError, PermissionError,\
35    PromotionError
36from openPLM.plmapp.utils import level_to_sign_str
37from openPLM.plmapp.controllers.base import Controller, permission_required
38
39rx_bad_ref = re.compile(r"[?/#\n\t\r\f]|\.\.")
40class PLMObjectController(Controller):
41    u"""
42    Object used to manage a :class:`~plmapp.models.PLMObject` and store his
43    modification in an history
44   
45    :attributes:
46        .. attribute:: object
47
48            The :class:`.PLMObject` managed by the controller
49        .. attribute:: _user
50
51            :class:`~django.contrib.auth.models.User` who modifies ``object``
52
53    :param obj: managed object
54    :type obj: a subinstance of :class:`.PLMObject`
55    :param user: user who modifies *obj*
56    :type user: :class:`~django.contrib.auth.models.User`
57    """
58
59    HISTORY = models.History
60   
61    @classmethod
62    def create(cls, reference, type, revision, user, data={}, block_mails=False):
63        u"""
64        This method builds a new :class:`.PLMObject` of
65        type *class_* and return a :class:`PLMObjectController` associated to
66        the created object.
67
68        Raises :exc:`ValueError` if *reference*, *type* or *revision* are
69        empty. Raises :exc:`ValueError` if *type* is not valid.
70
71        :param reference: reference of the objet
72        :param type: type of the object
73        :param revision: revision of the object
74        :param user: user who creates/owns the object
75        :param data: a dict<key, value> with informations to add to the plmobject
76        :rtype: :class:`PLMObjectController`
77        """
78       
79        profile = user.get_profile()
80        if not (profile.is_contributor or profile.is_administrator):
81            raise PermissionError("%s is not a contributor" % user)
82        if not reference or not type or not revision:
83            raise ValueError("Empty value not permitted for reference/type/revision")
84        if rx_bad_ref.search(reference) or rx_bad_ref.search(revision):
85            raise ValueError("Reference or revision contains a '/' or a '..'")
86        try:
87            class_ = models.get_all_plmobjects()[type]
88        except KeyError:
89            raise ValueError("Incorrect type")
90        # create an object
91        obj = class_(reference=reference, type=type, revision=revision,
92                     owner=user, creator=user)
93        if data:
94            for key, value in data.iteritems():
95                if key not in ["reference", "type", "revision"]:
96                    setattr(obj, key, value)
97        obj.state = models.get_default_state(obj.lifecycle)
98        obj.save()
99        res = cls(obj, user)
100        if block_mails:
101            res.block_mails()
102        # record creation in history
103        infos = {"type" : type, "reference" : reference, "revision" : revision}
104        infos.update(data)
105        details = u",".join(u"%s : %s" % (k, v) for k, v in infos.items())
106        res._save_histo("Create", details)
107        # add links
108        models.PLMObjectUserLink.objects.create(plmobject=obj, user=user, role="owner")
109        try:
110            l = models.DelegationLink.objects.get(delegatee=user,
111                    role=models.ROLE_SPONSOR)
112            sponsor = l.delegator
113            if sponsor.username == settings.COMPANY:
114                sponsor = user
115        except models.DelegationLink.DoesNotExist:
116            sponsor = user
117        for i in range(len(obj.lifecycle.to_states_list()) - 1):
118            models.PLMObjectUserLink.objects.create(plmobject=obj, user=sponsor,
119                                                    role=level_to_sign_str(i))
120        return res
121       
122    @classmethod
123    def create_from_form(cls, form, user, block_mails=False):
124        u"""
125        Creates a :class:`PLMObjectController` from *form* and associates *user*
126        as the creator/owner of the PLMObject.
127       
128        This method raises :exc:`ValueError` if *form* is invalid.
129
130        :param form: a django form associated to a model
131        :param user: user who creates/owns the object
132        :rtype: :class:`PLMObjectController`
133        """
134        if form.is_valid():
135            ref = form.cleaned_data["reference"]
136            type = form.Meta.model.__name__
137            rev = form.cleaned_data["revision"]
138            obj = cls.create(ref, type, rev, user, form.cleaned_data, block_mails)
139            return obj
140        else:
141            raise ValueError("form is invalid")
142   
143    def promote(self):
144        u"""
145        Promotes :attr:`object` in his lifecycle and writes his promotion in
146        the history
147       
148        :raise: :exc:`.PromotionError` if :attr:`object` is not promotable
149        :raise: :exc:`.PermissionError` if the use can not sign :attr:`object`
150        """
151        if self.object.is_promotable():
152            state = self.object.state
153            lifecycle = self.object.lifecycle
154            lcl = lifecycle.to_states_list()
155            self.check_permission(level_to_sign_str(lcl.index(state.name)))
156            try:
157                new_state = lcl.next_state(state.name)
158                self.object.state = models.State.objects.get_or_create(name=new_state)[0]
159                self.object.save()
160                details = "change state from %(first)s to %(second)s" % \
161                                     {"first" :state.name, "second" : new_state}
162                self._save_histo("Promote", details, roles=["sign_"])
163                if self.object.state == lifecycle.official_state:
164                    cie = models.User.objects.get(username=settings.COMPANY)
165                    self.set_owner(cie)
166            except IndexError:
167                # FIXME raises it ?
168                pass
169        else:
170            raise PromotionError()
171
172    def demote(self):
173        u"""
174        Demotes :attr:`object` in his lifecycle and writes his demotion in the
175        history
176       
177        :raise: :exc:`.PermissionError` if the use can not sign :attr:`object`
178        """
179        if not self.is_editable:
180            raise PromotionError()
181        state = self.object.state
182        lifecycle = self.object.lifecycle
183        lcl = lifecycle.to_states_list()
184        try:
185            new_state = lcl.previous_state(state.name)
186            self.check_permission(level_to_sign_str(lcl.index(new_state)))
187            self.object.state = models.State.objects.get_or_create(name=new_state)[0]
188            self.object.save()
189            details = "change state from %(first)s to %(second)s" % \
190                    {"first" :state.name, "second" : new_state}
191            self._save_histo("Demote", details, roles=["sign_"])
192        except IndexError:
193            # FIXME raises it ?
194            pass
195
196    def _save_histo(self, action, details, blacklist=(), roles=(), users=()):
197        """
198        Records *action* with details *details* made by :attr:`_user` in
199        on :attr:`object` in the histories table.
200
201        *blacklist*, if given, should be a list of email whose no mail should
202        be sent (empty by default).
203
204        A mail is sent to all notified users. Moreover, more roles can be
205        notified by settings the *roles" argument.
206        """
207        roles = ["notified"] + list(roles)
208        super(PLMObjectController, self)._save_histo(action, details,
209                blacklist, roles, users)
210
211    def has_permission(self, role):
212        users = [self._user.id]
213        users.extend(models.DelegationLink.get_delegators(self._user, role))
214        qset = self.plmobjectuserlink_plmobject.filter(user__in=users,
215                                                          role=role)
216        return bool(qset)
217
218    def check_editable(self):
219        """
220        Raises a :exc:`.PermissionError` if :attr:`object` is not editable.
221        """
222        if not self.object.is_editable:
223            raise PermissionError("The object is not editable")
224
225    @permission_required(role="owner")
226    def revise(self, new_revision):
227        u"""
228        Makes a new revision : duplicates :attr:`object`. The duplicated
229        object's revision is *new_revision*.
230
231        Returns a controller of the new object.
232        """
233       
234        if not new_revision or new_revision == self.revision or \
235           rx_bad_ref.search(new_revision):
236            raise RevisionError("Bad value for new_revision")
237        if models.RevisionLink.objects.filter(old=self.object.pk):
238            raise RevisionError("a revision already exists for %s" % self.object)
239        data = {}
240        fields = self.get_modification_fields() + self.get_creation_fields()
241        for attr in fields:
242            if attr not in ("reference", "type", "revision"):
243                data[attr] = getattr(self.object, attr)
244        data["state"] = models.get_default_state(self.lifecycle)
245        new_controller = self.create(self.reference, self.type, new_revision,
246                                     self._user, data)
247        details = "old : %s, new : %s" % (self.object, new_controller.object)
248        self._save_histo(models.RevisionLink.ACTION_NAME, details)
249        models.RevisionLink.objects.create(old=self.object, new=new_controller.object)
250        return new_controller
251
252    def is_revisable(self, check_user=True):
253        """
254        Returns True if :attr:`object` is revisable : if :meth:`revise` can be
255        called safely.
256
257        If *check_user* is True (the default), it also checks if :attr:`_user` is
258        the *owner* of :attr:`object`.
259        """
260        # objects.get fails if a link does not exist
261        # we can revise if any links exist
262        try:
263            models.RevisionLink.objects.get(old=self.object.pk)
264            return False
265        except ObjectDoesNotExist:
266            return self.check_permission("owner", False)
267   
268    def get_previous_revisions(self):
269        try:
270            link = models.RevisionLink.objects.get(new=self.object.pk)
271            controller = type(self)(link.old, self._user)
272            return controller.get_previous_revisions() + [link.old]
273        except ObjectDoesNotExist:
274            return []
275
276    def get_next_revisions(self):
277        try:
278            link = models.RevisionLink.objects.get(old=self.object.pk)
279            controller = type(self)(link.new, self._user)
280            return [link.new] + controller.get_next_revisions()
281        except ObjectDoesNotExist:
282            return []
283
284    def get_all_revisions(self):
285        """
286        Returns a list of all revisions, ordered from less recent to most recent
287       
288        :rtype: list of :class:`.PLMObject`
289        """
290        return self.get_previous_revisions() + [self.object] +\
291               self.get_next_revisions()
292
293    def set_owner(self, new_owner):
294        """
295        Sets *new_owner* as current owner.
296       
297        :param new_owner: the new owner
298        :type new_owner: :class:`~django.contrib.auth.models.User`
299        :raise: :exc:`.PermissionError` if *new_owner* is not a contributor
300        """
301
302        self.check_contributor(new_owner)
303        links = models.PLMObjectUserLink.objects.filter(plmobject=self.object,
304                role="owner")
305        for link in links:
306            link.delete()
307        link = models.PLMObjectUserLink.objects.get_or_create(user=self.owner,
308               plmobject=self.object, role="owner")[0]
309        self.owner = new_owner
310        link.user = new_owner
311        link.save()
312        self.save()
313        # we do not need to write this event in an history since save() has
314        # already done it
315
316    def add_notified(self, new_notified):
317        """
318        Adds *new_notified* to the list of users notified when :attr:`object`
319        changes.
320       
321        :param new_notified: the new user who would be notified
322        :type new_notified: :class:`~django.contrib.auth.models.User`
323        :raise: :exc:`IntegrityError` if *new_notified* is already notified
324            when :attr:`object` changes
325        """
326        if new_notified != self._user:
327            self.check_permission("owner")
328        models.PLMObjectUserLink.objects.create(plmobject=self.object,
329            user=new_notified, role="notified")
330        details = "user: %s" % new_notified
331        self._save_histo("New notified", details)
332
333    def remove_notified(self, notified):
334        """
335        Removes *notified* to the list of users notified when :attr:`object`
336        changes.
337       
338        :param notified: the user who would be no more notified
339        :type notified: :class:`~django.contrib.auth.models.User`
340        :raise: :exc:`ObjectDoesNotExist` if *notified* is not notified
341            when :attr:`object` changes
342        """
343       
344        if notified != self._user:
345            self.check_permission("owner")
346        link = models.PLMObjectUserLink.objects.get(plmobject=self.object,
347                user=notified, role="notified")
348        link.delete()
349        details = "user: %s" % notified
350        self._save_histo("Notified removed", details)
351
352    def set_signer(self, signer, role):
353        """
354        Sets *signer* as current signer for *role*. *role* must be a valid
355        sign role (see :func:`.level_to_sign_str` to get a role from a
356        sign level (int))
357       
358        :param signer: the new signer
359        :type signer: :class:`~django.contrib.auth.models.User`
360        :param str role: the sign role
361        :raise: :exc:`.PermissionError` if *signer* is not a contributor
362        :raise: :exc:`.PermissionError` if *role* is invalid (level to high)
363        """
364        self.check_contributor(signer)
365        # remove old signer
366        old_signer = None
367        try:
368            link = models.PLMObjectUserLink.objects.get(plmobject=self.object,
369               role=role)
370            old_signer = link.user
371            link.delete()
372        except ObjectDoesNotExist:
373            pass
374        # check if the role is valid
375        max_level = len(self.lifecycle.to_states_list()) - 1
376        level = int(re.search(r"\d+", role).group(0))
377        if level > max_level:
378            # TODO better exception ?
379            raise PermissionError("bad role")
380        # add new signer
381        models.PLMObjectUserLink.objects.create(plmobject=self.object,
382                                                user=signer, role=role)
383        details = "signer: %s, level : %d" % (signer, level)
384        if old_signer:
385            details += ", old signer: %s" % old_signer
386        self._save_histo("New signer", details)
387
388    def set_role(self, user, role):
389        """
390        Sets role *role* (like `owner` or `notified`) for *user*
391
392        .. note::
393            If *role* is `owner` or a sign role, the old user who had
394            this role will lose it.
395
396            If *role* is notified, others roles are preserved.
397       
398        :raise: :exc:`ValueError` if *role* is invalid
399        :raise: :exc:`.PermissionError` if *user* is not allowed to has role
400            *role*
401        """
402        if role == "owner":
403            self.set_owner(user)
404        elif role == "notified":
405            self.add_notified(user)
406        elif role.startswith("sign"):
407            self.set_signer(user, role)
408        else:
409            raise ValueError("bad value for role")
410
411    def check_permission(self, role, raise_=True):
412        if not bool(self.group.user_set.filter(id=self._user.id)):
413            if raise_:
414                raise PermissionError("action not allowed for %s" % self._user)
415            else:
416                return False
417        return super(PLMObjectController, self).check_permission(role, raise_)
418
419    def check_readable(self, raise_=True):
420        if not self.is_editable:
421            return True
422        if bool(self.group.user_set.filter(id=self._user.id)):
423            return True
424        if raise_:
425            raise PermissionError("You can not see this object.")
426        return False
427
Note: See TracBrowser for help on using the repository browser.