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

Revision 367, 16.0 KB checked in by pcosquer, 8 years ago (diff)

check if the user can see and object
Some checks are not implemented, they will come later

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