############################################################################
# openPLM - open source PLM
# Copyright 2010 Philippe Joulaud, Pierre Cosquer
#
# This file is part of openPLM.
#
# openPLM is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# openPLM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with openPLM. If not, see <http://www.gnu.org/licenses/>.
#
# Contact :
# Philippe Joulaud : ninoo.fr@gmail.com
# Pierre Cosquer : pcosquer@linobject.com
################################################################################
"""
"""
import re
import difflib
import itertools
from functools import wraps
from collections import deque
from django.db.models.fields import FieldDoesNotExist
import openPLM.plmapp.models as models
from openPLM.plmapp.exceptions import PermissionError
from openPLM.plmapp.mail import send_histories_mail
_controller_rx = re.compile(r"(?P<type>\w+)Controller")
get_controller = MetaController.get_controller
[docs]def permission_required(func=None, role="owner"):
"""
Decorator for methods of :class:`PLMObjectController` which
raises :exc:`.PermissionError` if :attr:`PLMObjectController._user`
has not the role *role*
"""
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
args[0].check_permission(role)
return f(*args, **kwargs)
wrapper.__doc__ = "Permission required: `%s`\n%s" % (role, wrapper.__doc__)
return wrapper
if func:
return decorator(func)
return decorator
[docs]class Controller(object):
u"""
Object used to manage a :class:`~plmapp.models.PLMObject` and store his
modification in a history
:attributes:
.. attribute:: object
The :class:`.PLMObject` managed by the controller
.. attribute:: _user
:class:`~django.contrib.auth.models.User` who modifies ``object``
:param obj: managed object
:type obj: a subinstance of :class:`.PLMObject`
:param user: user who modifies *obj*
:type user: :class:`~django.contrib.auth.models.User`
"""
__metaclass__ = MetaController
__slots__ = ("object", "_user", "_histo", "_pending_mails", "_mail_blocked",
"__permissions", "__histo")
HISTORY = models.AbstractHistory
def __init__(self, obj, user, block_mails=False, no_index=False):
self._mail_blocked = block_mails
self._pending_mails = deque()
self._user = user
# variable to store attribute changes
self._histo = ""
# cache for permissions (dict(role->bool))
self.__permissions = {}
self.object = obj
if no_index:
obj.no_index = True
@classmethod
[docs] def load(cls, type, reference, revision, user):
raise NotImplemented
def __setattr__(self, attr, value):
# we override this method to make it to modify *object* directly
# if we modify *object*, we records the modification in **_histo*
if hasattr(self, "object") and hasattr(self.object, attr) and \
not attr in type(self).__slots__:
obj = object.__getattribute__(self, "object")
old_value = getattr(obj, attr)
setattr(obj, attr, value)
mfield = obj._meta.get_field(attr)
field = mfield.verbose_name.capitalize()
if old_value != value:
if getattr(mfield, "richtext", False):
# store a unified diff (shorter than storing both values)
message = u"{field}:\n".format(field=field)
diff = difflib.unified_diff(old_value.splitlines(), value.splitlines())
# skip the ---/+++ lines
message += u"\n".join(itertools.islice(diff, 3, None))
else:
message = u"%(field)s : changes from '%(old)s' to '%(new)s'" % \
{"field" : field, "old" : old_value, "new" : value}
self._histo += message + "\n"
else:
super(Controller, self).__setattr__(attr, value)
def __getattr__(self, attr):
# we override this method to get attributes from *object* directly
if attr in type(self).__slots__:
return object.__getattribute__(self, attr)
else:
obj = object.__getattribute__(self, "object")
return getattr(obj, attr)
[docs] def save(self, with_history=True):
u"""
Saves :attr:`object` and records its history in the database.
If *with_history* is False, the history is not recorded.
"""
self.object.save()
if self._histo and with_history:
self._save_histo("Modify", self._histo)
self._histo = ""
def _save_histo(self, action, details, blacklist=(), roles=(), users=()):
"""
Records *action* with details *details* made by :attr:`_user` in
on :attr:`object` in the user histories table.
"""
h = self.HISTORY.objects.create(plmobject=self.object, action=action,
details=details, user=self._user)
if self._user not in users:
blacklist += (self._user.email,)
roles = [models.ROLE_OWNER] + list(roles)
self._send_mail(send_histories_mail, self.object, roles, action, [h],
self._user, blacklist, users)
[docs] def get_verbose_name(self, attr_name):
"""
Returns a verbose name for *attr_name*.
Example::
>>> ctrl.get_verbose_name("ctime")
u'date of creation'
"""
try:
item = self.object._meta.get_field(attr_name).verbose_name
except FieldDoesNotExist:
item = attr_name
return item
[docs] def check_permission(self, role, raise_=True):
"""
This method checks if :attr:`_user` has permissions implied by *role*.
For example, *role* can be *owner* or *notified*.
If the check succeeds, **True** is returned. Otherwise, if *raise_* is
**True** (the default), a :exc:`.PermissionError` is raised and if
*raise_* is **False**, **False** is returned.
.. admonition:: Implementation details
This method keeps a cache, so that you dont have to worry about
multiple calls to this method.
"""
if role in self.__permissions:
ok = self.__permissions[role]
else:
ok = self.has_permission(role)
self.__permissions[role] = ok
if not ok and raise_:
raise PermissionError("action not allowed for %s" % self._user)
return ok
[docs] def clear_permissions_cache(self):
self.__permissions.clear()
[docs] def has_permission(self, role):
return False
[docs] def check_contributor(self, user=None):
"""
This method checks if *user* is a contributor. If not, it raises
:exc:`.PermissionError`.
If *user* is None (the default), :attr:`_user` is used.
"""
if not user:
user = self._user
if not user.is_active:
raise PermissionError(u"%s's account is inactive" % user)
profile = user.profile
if not (profile.is_contributor or profile.is_administrator):
raise PermissionError(u"%s is not a contributor" % user)
if profile.restricted:
# should not be possible, but an admin may have done a mistake
raise PermissionError(u"%s is not a contributor" % user)
[docs] def check_editable(self):
return True
[docs] def block_mails(self):
"""
Blocks mails sending. Call :meth:`unblock_mails` to send blocked mails.
"""
self._mail_blocked = True
[docs] def unblock_mails(self):
"""
Unblock mails sending. This sends all previously blocked mails.
"""
while self._pending_mails:
mail = self._pending_mails.popleft()
mail[0](*mail[1:])
self._mail_blocked = False
def _send_mail(self, func, *args):
if self._mail_blocked:
mail = (func,) + args
self._pending_mails.append(mail)
else:
func(*args)
@property
[docs] def histories(self):
return self.HISTORY.objects.filter(plmobject=self.object).\
order_by("-date").select_related("user")