#-!- coding:utf-8 -!-
############################################################################
# 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 datetime
import functools
import json
import traceback
import re
import sys
from django.conf import settings
from django.core.mail import mail_admins
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.auth.decorators import login_required
from django.contrib.sites.models import Site
from django.http import (HttpResponse, HttpResponseForbidden, Http404,
HttpResponseRedirect, HttpResponseServerError)
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.utils import timezone
from django.utils.translation import ugettext as _
from openPLM import get_version
import openPLM.plmapp.models as models
from openPLM.plmapp.controllers import get_controller
from openPLM.plmapp.exceptions import ControllerError
from openPLM.plmapp.forms import get_navigate_form, SimpleSearchForm
from openPLM.plmapp.navigate import NavigationGraph, OSR
from openPLM.plmapp.utils import can_generate_pdf
[docs]def get_obj(obj_type, obj_ref, obj_revi, user):
"""
Get type, reference and revision of an object and return the related controller
:param obj_type: :attr:`.PLMObject.type`
:type obj_type: str
:param obj_ref: :attr:`.PLMObject.reference`
:type obj_ref: str
:param obj_revi: :attr:`.PLMObject.revision`
:type obj_revi: str
:return: a :class:`PLMObjectController` or a :class:`UserController`
"""
controller_cls = get_controller(obj_type)
return controller_cls.load(obj_type, obj_ref, obj_revi, user)
# from http://www.redrobotstudios.com/blog/2009/02/18/securing-django-with-ssl/
[docs]def secure_required(view_func):
"""Decorator which makes sure URL is accessed over https."""
@functools.wraps(view_func)
def _wrapped_view_func(request, *args, **kwargs):
if not request.is_secure():
if getattr(settings, 'FORCE_HTTPS', False):
request_url = request.build_absolute_uri(request.get_full_path())
secure_url = request_url.replace('http://', 'https://')
return HttpResponseRedirect(secure_url)
return view_func(request, *args, **kwargs)
return _wrapped_view_func
[docs]def json_view(func, API_VERSION=""):
"""
Decorator which converts the result from *func* into a json response.
The result from *func* must be serializable by :mod:`json`
This decorator automatically adds a ``result`` field to the response if it
was not present. Its value is ``'ok'`` if no exception was raised, and else,
it is ``'error'``. In that case, a field ``'error'`` is added with a short
message describing the exception.
"""
@functools.wraps(func)
def wrapper(request, *a, **kw):
try:
response = dict(func(request, *a, **kw))
if 'result' not in response:
response['result'] = 'ok'
except KeyboardInterrupt:
# Allow keyboard interrupts through for debugging.
raise
except Exception, e:
#Mail the admins with the error
exc_info = sys.exc_info()
subject = 'JSON view error: %s' % request.path
try:
request_repr = repr(request)
except:
request_repr = 'Request repr() unavailable'
message = 'Traceback:\n%s\n\nRequest:\n%s' % (
'\n'.join(traceback.format_exception(*exc_info)),
request_repr,
)
mail_admins(subject, message, fail_silently=True)
#Come what may, we're returning JSON.
msg = _('Internal error') + ': ' + str(e)
response = {'result' : 'error', 'error' : msg}
response["api_version"] = API_VERSION
json_data = json.dumps(response, cls=DjangoJSONEncoder)
return HttpResponse(json_data, content_type='application/json')
return secure_required(wrapper)
[docs]def get_obj_by_id(obj_id, user):
u"""
Returns an adequate controller for the object identify by *obj_id*.
The returned controller is instanciate with *user* as the user
who modify the object.
:param obj_id: id of a :class:`.PLMObject`
:param user: a :class:`.django.contrib.auth.models.User`
:return: a subinstance of a :class:`.PLMObjectController`
"""
obj = get_object_or_404(models.PLMObject, id=obj_id)
obj = models.get_all_plmobjects()[obj.type].objects.get(id=obj_id)
return get_controller(obj.type)(obj, user)
[docs]def object_to_dict(plmobject):
"""
Returns a dictionary representing *plmobject*. The returned dictionary
respects the format described in :ref:`http-api-object`
"""
return dict(id=plmobject.id, name=plmobject.name, type=plmobject.type,
revision=plmobject.revision, reference=plmobject.reference)
[docs]def handle_errors(func=None, undo="..", restricted_access=True, no_cache=True):
"""
Decorators which ensures that the user is connected and handles exceptions
raised by a controller.
If an exception of type :exc:`.django.http.Http404` is raised, the exception
is re-raised.
If an exception of type :exc:`.ControllerError` is raised, a
:class:`.django.http.HttpResponse` is returned with an explanation message.
If :attr:`settings.DEBUG` is False and another exception is raised,
a :class:`.django.http.HttpResponseServerError` is returned.
"""
def decorator(f):
@functools.wraps(f)
@secure_required
@login_required
def wrapper(request, *args, **kwargs):
if request.method == "POST" and request.POST.get("_undo"):
return HttpResponseRedirect(undo)
if restricted_access and request.user.profile.restricted:
return HttpResponseForbidden()
try:
response = f(request, *args, **kwargs)
if no_cache:
response['Pragma'] = 'no-cache'
response['Cache-Control'] = 'no-cache must-revalidate proxy-revalidate'
return response
except (ControllerError, ValueError) as exc:
ctx = init_ctx("-", "-", "-")
ctx["message"] = _(str(exc))
return render_to_response("error.html", ctx, context_instance=RequestContext(request))
except Http404:
raise
except StandardError:
if settings.DEBUG:
raise
return HttpResponseServerError()
return wrapper
if func:
return decorator(func)
return decorator
_version = get_version()
[docs]def init_ctx(init_type_, init_reference, init_revision):
"""
Initiate the context dictionnary we used to transfer parameters to html pages.
Get type, reference and revision of an object and return a dictionnary with them
plus current time, :attr:`settings.THUMBNAILS_URL` the begining of the URL to
get thumbnail of a file and :attr:`settings.LANGUAGES`.
Example::
>>> init_ctx('BiosOs','BI-0044','1.4.3')
{'THUMBNAILS_URL': '/media/thumbnails',
'object_reference': 'BI-0044',
'object_revision': '1.4.3',
'object_type': 'BiosOs'}
:param init_type_: type of the object
:type init_type_: str
:param init_reference: reference of the object
:type init_reference: str
:param init_revision: revision of the object
:type init_revision: str
:return: a dictionnary
"""
return {
'object_reference': init_reference,
'object_revision': init_revision,
'object_type': init_type_,
'THUMBNAILS_URL' : settings.THUMBNAILS_URL,
'DOCUMENTATION_URL' : settings.DOCUMENTATION_URL,
'can_generate_pdf' : can_generate_pdf(),
'openPLM_version' : _version,
'site' : Site.objects.get_current(),
}
##########################################################################################
### Manage html code for Search and Results function and other global parameters ###
##########################################################################################
[docs]def update_navigation_history(request, obj, type_, reference, revision):
old_history = request.session.get("navigation_history", [])
links = tuple(obj.menu_items) + ("navigate",)
value = (obj.plmobject_url, type_, reference, revision, links)
history = list(old_history)
if value in history:
# move value at the end
history.remove(value)
history.append(value)
if len(history) > 7:
history = history[-7:]
if old_history != history:
request.session["navigation_history"] = history
return True
return False
_SEARCH_ID = "search_id_%s"
[docs]def get_generic_data(request, type_='-', reference='-', revision='-', search=True,
load_all=False):
"""
Get a request and return a controller, a context dictionnary with elements common to all pages
(search form, search data, search results, ...) and another dictionnary to update the
request.session dictionnary.
:param request: :class:`django.http.QueryDict`
:param type_: :attr:`.PLMObject.type`
:type type_: str
:param reference: :attr:`.PLMObject.reference`
:type reference: str
:param revision: :attr:`.PLMObject.revision`
:type revision: str
:return: a :class:`.PLMObjectController` or a :class:`.UserController`
:return: ctx
:type ctx: dic
:return: request.session
:type request.session: dic
"""
ctx = init_ctx(type_, reference, revision)
# This case happens when we create an object (and therefore can't get a controller)
save_session = False
profile = request.user.profile
restricted = profile.restricted
if type_ == reference == revision == '-':
obj = request.user
obj_url = profile.plmobject_url
else:
obj = get_obj(type_, reference, revision, request.user)
obj_url = obj.plmobject_url
if not restricted:
save_session = update_navigation_history(request, obj,
type_, reference, revision)
table = request.GET.get("table", "")
save_session = save_session or table != ""
if table == "1":
request.session["as_table"] = True
elif table == "0":
request.session["as_table"] = False
ctx["as_table"] = request.session.get("as_table")
if not restricted: # a restricted account can not perform a search
# Builds, update and treat Search form
search_needed = "results" not in request.session or load_all
if request.method == "GET" and "type" in request.GET:
search_form = SimpleSearchForm(request.GET, auto_id=_SEARCH_ID)
request.session["type"] = request.GET["type"]
request.session["q"] = request.GET.get("q", "")
request.session["search_official"] = request.GET.get("search_official", "")
search_needed = True
save_session = True
elif "type" in request.session:
search_form = SimpleSearchForm(request.session, auto_id=_SEARCH_ID)
else:
request.session['type'] = 'all'
search_form = SimpleSearchForm(auto_id=_SEARCH_ID)
save_session = True
if search and search_needed and search_form.is_valid():
search_query = search_form.cleaned_data["q"]
qset = search_form.search()
if load_all:
qset = qset.load_all()
request.session["search_query"] = search_query
search_official = ["", "1"][search_form.cleaned_data["search_official"]]
request.session["search_official"] = search_official
search_count = request.session["search_count"] = qset.count()
qset = qset[:30]
request.session["results"] = qset
save_session = True
else:
qset = request.session.get("results", [])
search_query = request.session.get("search_query", "")
search_count = request.session.get("search_count", 0)
search_official = request.session.get("search_official", "")
ctx.update({
'results' : qset,
'search_query' : search_query,
'search_count' : search_count,
'search_form' : search_form,
'navigation_history' : request.session.get("navigation_history", []),
'ctype': "Part" if request.session["type"] in ("all", "User") else request.session["type"],
})
ctx.update({
'link_creation' : False,
'attach' : (obj, False),
'obj' : obj,
'obj_url': obj_url,
'restricted' : restricted,
'is_contributor': profile.is_contributor,
})
if hasattr(obj, "menu_items"):
ctx['object_menu'] = obj.menu_items
if hasattr(obj, "check_permission"):
ctx["is_owner"] = obj.check_permission("owner", False)
if hasattr(obj, "check_readable"):
ctx["is_readable"] = readable = obj.check_readable(False)
if restricted and not readable:
raise Http404
else:
ctx["is_readable"] = True
# little hack to avoid a KeyError
# see https://github.com/toastdriven/django-haystack/issues/404
from haystack import site
for r in request.session.get("results", []):
r.searchsite = site
if save_session:
request.session.save()
return obj, ctx
coords_rx = re.compile(r'top:(\d+)px;left:(\d+)px;width:(\d+)px;height:(\d+)px;')
[docs]def get_navigate_data(request, obj_type, obj_ref, obj_revi):
obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
FilterForm = get_navigate_form(obj)
has_session = any(field in request.session for field in FilterForm.base_fields)
initial = dict((k, v.initial) for k, v in FilterForm.base_fields.items())
if request.method == 'POST' and request.POST:
form = FilterForm(request.POST)
if form.is_valid():
request.session.update(form.cleaned_data)
elif has_session:
request.session.update(dict(doc_parts = ""))
initial.update(request.session)
form = FilterForm(initial)
else:
form = FilterForm(initial)
request.session.update(initial)
if not form.is_valid():
options = initial
else:
options = form.cleaned_data
if options[OSR]:
results = [r.object for r in ctx.get("results", [])]
else:
results = []
graph = NavigationGraph(obj, results)
if options["update"]:
options["doc_parts"] = [int(o)
for o in options["doc_parts"].split("#")
if o.isnumeric()]
else:
options["doc_parts"] = []
request.session.update(dict(doc_parts = ""))
graph.set_options(options)
graph.create_edges()
map_string, edges = graph.render()
width, height = edges["width"], edges["height"]
past = graph.time and timezone.now() - graph.time > datetime.timedelta(minutes=5)
ctx.update({
'filter_object_form': form,
'map_areas': map_string,
'img_width' : width,
'img_height' : height,
'navigate_bool': True,
'edges' : edges,
'past' : past,
})
return ctx
_creation_views = {}
[docs]def register_creation_view(type_, view):
"""
Register a creation view for *type_* (a subclass of :class:`.PLMObject`).
Most of the applications does not need to call this function which is
available for special cases which cannot be handled by :func:`.create_object`.
.. note::
You must ensure that the module that calls this function has been imported.
For example, you can import it in your :file:`urls.py` file.
"""
_creation_views[type_] = view
[docs]def get_creation_view(type_):
"""
Returns a registed view for *type_* (a subclass of :class:`.PLMObject`)
or None if no views are registered.
"""
return _creation_views.get(type_)
[docs]def get_id_card_data(doc_ids):
"""
Get informations to display in the id-cards of all Document which id is in doc_ids
:param doc_ids: list of Document ids to treat
:return: a dictionary which contains the following data
* ``thumbnails``
list of tuple (document,thumbnail)
* ``num_files``
list of tuple (document, number of file)
"""
ctx = { "thumbnails" : {}, "num_files" : {} }
if doc_ids:
thumbnails = models.DocumentFile.objects.filter(deprecated=False,
document__in=doc_ids, thumbnail__isnull=False).exclude(thumbnail="")
ctx["thumbnails"].update(thumbnails.values_list("document", "thumbnail"))
num_files = dict.fromkeys(doc_ids, 0)
for doc_id in models.DocumentFile.objects.filter(deprecated=False,
document__in=doc_ids).values_list("document", flat=True):
num_files[doc_id] += 1
ctx["num_files"] = num_files
return ctx