source: main/trunk/openPLM/plmapp/views/main.py @ 1191

Revision 1191, 81.5 KB checked in by pcosquer, 7 years ago (diff)

docs: publication

  • Property svn:executable set to *
Line 
1#-!- coding:utf-8 -!-
2
3############################################################################
4# openPLM - open source PLM
5# Copyright 2010 Philippe Joulaud, Pierre Cosquer
6#
7# This file is part of openPLM.
8#
9#    openPLM is free software: you can redistribute it and/or modify
10#    it under the terms of the GNU General Public License as published by
11#    the Free Software Foundation, either version 3 of the License, or
12#    (at your option) any later version.
13#
14#    openPLM is distributed in the hope that it will be useful,
15#    but WITHOUT ANY WARRANTY; without even the implied warranty of
16#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17#    GNU General Public License for more details.
18#
19#    You should have received a copy of the GNU General Public License
20#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>.
21#
22# Contact :
23#    Philippe Joulaud : ninoo.fr@gmail.com
24#    Pierre Cosquer : pierre.cosquer@insa-rennes.fr
25################################################################################
26
27"""
28Introduction
29=============
30
31This module contains all views to display html pages.
32
33All URLs are linked with django's standard views or with plmapp view functions hereafter.
34Each of them receives an httprequest object.
35Then treat data with the help of different controllers and different models.
36Then adress a html template with a context dictionnary via an httpresponse.
37
38We have a view for each :class:`PLMObject` or :class:`UserProfile` :func:`menu_items`.
39We have some views which allow link creation between 2 instances of :class:`PLMObject` or between
40an instance of :class:`PLMObject` and an instance of :class:`UserProfile`.
41We have some views for link deletion.
42We have some views for link edition.
43We have views for :class:`PLMObject` creation and edition.
44Finaly we have :func:`navigate` which draw a picture with a central object and its related objects.
45
46"""
47
48import os
49import csv
50import datetime
51import tempfile
52import itertools
53from mimetypes import guess_type
54from collections import defaultdict
55
56from django.conf import settings
57from django.contrib import messages
58from django.contrib.auth.forms import PasswordChangeForm
59from django.contrib.auth.models import User
60from django.contrib.auth.views import redirect_to_login
61from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
62from django.db.models import F
63from django.forms import HiddenInput
64from django.http import HttpResponseRedirect, HttpResponse, Http404, \
65                        HttpResponsePermanentRedirect, HttpResponseForbidden
66from django.shortcuts import render_to_response
67from django.template import RequestContext
68from django.utils.encoding import iri_to_uri
69from django.utils.translation import ugettext_lazy as _
70from django.utils.decorators import method_decorator
71from django.utils import simplejson
72from django.views.i18n import set_language as dj_set_language
73from django.views.decorators.csrf import csrf_exempt, csrf_protect
74
75from haystack.views import SearchView
76
77import openPLM.plmapp.csvimport as csvimport
78import openPLM.plmapp.models as models
79import openPLM.plmapp.forms as forms
80from openPLM.plmapp.archive import generate_archive
81from openPLM.plmapp.base_views import init_ctx, get_obj, get_obj_from_form, \
82    get_obj_by_id, handle_errors, get_generic_data, get_navigate_data, \
83    get_creation_view, register_creation_view, secure_required
84from openPLM.plmapp.cadformats import is_cad_file
85from openPLM.plmapp.controllers import get_controller
86from openPLM.plmapp.decomposers.base import DecomposersManager
87from openPLM.plmapp.exceptions import ControllerError, PermissionError
88from openPLM.plmapp.utils import level_to_sign_str, get_next_revision
89from openPLM.plmapp.filehandlers.progressbarhandler import ProgressBarUploadHandler
90
91import glob
92
93def r2r(template, dictionary, request):
94    """
95    Shortcut for:
96   
97    ::
98       
99        render_to_response(template, dictionary,
100                              context_instance=RequestContext(request))
101    """
102    return render_to_response(template, dictionary,
103                              context_instance=RequestContext(request))
104
105
106def set_language(request):
107    """
108    A wrapper arround :func:`django.views.i18n.set_language` that
109    stores the language in the user profile.
110    """
111    response = dj_set_language(request)
112    if request.method == "POST" and request.user.is_authenticated():
113        language = request.session.get('django_language')
114        if language:
115            request.user.get_profile().language = language
116            request.user.get_profile().save()
117    return response
118
119##########################################################################################
120###                    Function which manage the html home page                        ###
121##########################################################################################
122
123def get_last_edited_objects(user):
124    """
125    Returns the 5 last objects edited by *user*. It returns a list of the most
126    recent history entries associated to these objects.
127    """
128    histories = []
129    plmobjects = []
130    r = ("plmobject__id", "plmobject__reference", "plmobject__revision", "plmobject__type")
131    qs = user.history_user.order_by("-date", "-pk").select_related("plmobject")
132    qs = qs.only("date", "action", "details", *r)
133    try:
134        h = qs[0]
135        histories.append(h)
136        plmobjects.append(h.plmobject_id)
137        for i in xrange(4):
138            h = qs.filter(date__lt=h.date).exclude(plmobject__in=plmobjects)[0]
139            histories.append(h)
140            plmobjects.append(h.plmobject_id)
141    except (models.History.DoesNotExist, IndexError):
142        pass # no more histories
143    return histories
144
145@handle_errors
146def display_home_page(request):
147    """
148    Home page view.
149
150    :url: :samp:`/home/`
151   
152    **Template:**
153   
154    :file:`home.html`
155
156    **Context:**
157
158    ``RequestContext``
159 
160    ``pending_invitations_owner``
161        QuerySet of pending invitations to groups owned by the user
162
163    ``pending_invitations_guest``
164        QuerySet of pending invitations to groups that the user can joined
165
166    """
167    obj, ctx = get_generic_data(request, "User", request.user.username)
168    del ctx["object_menu"]
169
170    pending_invitations_owner = obj.invitation_inv_owner. \
171            filter(state=models.Invitation.PENDING).order_by("group__name")
172    ctx["pending_invitations_owner"] = pending_invitations_owner
173    pending_invitations_guest = obj.invitation_inv_guest. \
174            filter(state=models.Invitation.PENDING).order_by("group__name")
175    ctx["pending_invitations_guest"] = pending_invitations_guest
176    ctx["display_group"] = True
177
178    return r2r("home.html", ctx, request)
179
180#############################################################################################
181###All functions which manage the different html pages related to a part, a doc and a user###
182#############################################################################################
183@handle_errors
184def display_object_attributes(request, obj_type, obj_ref, obj_revi):
185    """
186    Attributes view of the given object.
187
188    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/attributes/`
189
190    .. include:: views_params.txt
191
192    **Template:**
193   
194    :file:`attribute.html`
195
196    **Context:**
197
198    ``RequestContext``
199   
200    ``object_attributes``
201        list of tuples(verbose attribute name, value)
202       
203    """
204    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
205   
206    object_attributes_list = []
207    attrs = obj.attributes
208    if getattr(settings, "HIDE_EMAILS", False):
209        if not ctx["is_owner"]:
210            attrs = (attr for attr in attrs if attr != "email")
211    for attr in attrs:
212        item = obj.get_verbose_name(attr)
213        object_attributes_list.append((item, getattr(obj, attr)))
214    ctx.update({'current_page' : 'attributes',
215                'object_attributes': object_attributes_list})
216    return r2r('attributes.html', ctx, request)
217
218##########################################################################################
219@handle_errors
220def display_object(request, obj_type, obj_ref, obj_revi):
221    """
222    Generic object view.
223
224    Permanently redirects to the attribute page of the given object if it
225    is a part, an user or a group and to the files page if it is a document.
226
227    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/`
228    """
229     
230    if obj_type in ('User', 'Group'):
231        url = u"/%s/%s/attributes/" % (obj_type.lower(), obj_ref)
232    else:
233        model_cls = models.get_all_plmobjects()[obj_type]
234        page = "files" if issubclass(model_cls, models.Document) else "attributes"
235        url = u"/object/%s/%s/%s/%s/" % (obj_type, obj_ref, obj_revi, page)
236    return HttpResponsePermanentRedirect(iri_to_uri(url))
237
238##########################################################################################
239@handle_errors
240def display_object_lifecycle(request, obj_type, obj_ref, obj_revi):
241    """
242    Lifecycle data of the given object (a part or a document).
243 
244    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/lifecycle/[apply/]`
245   
246    .. include:: views_params.txt
247 
248    POST requests must have a "demote", "promote", "publish" or "unpublish"
249    key and must validate the :class:`.ConfirmPasswordForm` form.
250    If the form is valid, the object is promoted, demoted, published, unpublished
251    according to the request.
252
253    **Template:**
254   
255    :file:`lifecycle.html`
256
257    **Context:**
258
259    ``RequestContext``
260
261    ``action``
262        Only for unsuccessful POST requests.
263        Name of the action ("demote" or "promote") that the user tries to do.
264    """
265    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
266    if request.method == 'POST':
267        password_form = forms.ConfirmPasswordForm(request.user, request.POST)
268        actions = (("demote", obj.demote), ("promote", obj.promote),
269                   ("publish", obj.publish), ("unpublish", obj.unpublish,))
270        if password_form.is_valid():
271            for action_name, method in actions:
272                if action_name in request.POST:
273                    method()
274                    break
275            return HttpResponseRedirect("..")
276        for action_name, method in actions:
277            if action_name in request.POST:
278                ctx["action"] = action_name
279                break
280    else:
281        password_form = forms.ConfirmPasswordForm(request.user)
282    ctx.update({'password_form' : password_form,})
283    signer_list = get_management_data(request,obj,ctx)
284    get_lifecycle_data(signer_list,obj,ctx)
285    return r2r('lifecycle.html',ctx,request)
286   
287   
288def get_lifecycle_data(signers,obj,ctx):
289    """
290    Update lifecycle data for a given object
291   
292    ``object_lifecycle``
293        List of tuples (state name, *boolean*, signer role). The boolean is
294        True if the state name equals to the current state. The signer role
295        is a dict {"role" : name of the role, "user__username" : name of the
296        signer}
297
298    ``is_signer``
299        True if the current user has the permission to promote this object
300
301    ``is_signer_dm``
302        True if the current user has the permission to demote this object
303   
304    ``signers_data``
305        List of tuple (signer, nb_signer). The signer is a dict which contains
306        management data for the signer and indicates wether a signer exists or not.
307       
308    ``password_form``
309        A form to ask the user password
310
311    ``cancelled_revisions``
312        List of plmobjects that will be cancelled if the object is promoted
313   
314    ``deprecated_revisions``
315        List of plmobjects that will be deprecated if the object is promoted
316    """
317    state = obj.state.name
318    object_lifecycle = []
319    signers_data=[]
320    roles = dict(obj.plmobjectuserlink_plmobject.values_list("role", "user__username"))
321    lcs = obj.lifecycle.to_states_list()
322    for i, st in enumerate(lcs):
323        signer = roles.get(level_to_sign_str(i))
324        signer_data = signers.filter(role=level_to_sign_str(i))
325        if len(signer_data)==0:
326            signer_data=roles.get(level_to_sign_str(i))
327            nb_signer = 0
328        else:
329            nb_signer = len(signer)
330            signer_data = signer_data[0]
331        signers_data.append({"signer":signer_data,"nb_signer":nb_signer})
332        object_lifecycle.append((st, st == state, signer))
333    is_signer = obj.check_permission(obj.get_current_sign_level(), False)
334    is_signer_dm = obj.check_permission(obj.get_previous_sign_level(), False)
335
336    # warning if a previous revision will be cancelled/deprecated
337    cancelled = []
338    deprecated = []
339    if is_signer:
340        if lcs[-1] != state:
341            if lcs.next_state(state) == obj.lifecycle.official_state.name:
342                for rev in obj.get_previous_revisions():
343                    if rev.is_official:
344                        deprecated.append(rev)
345                    elif rev.is_draft or rev.is_proposed:
346                        cancelled.append(rev)
347    ctx["cancelled_revisions"] = cancelled
348    ctx["deprecated_revisions"] = deprecated
349
350    ctx.update({'current_page':'lifecycle',
351                'object_lifecycle': object_lifecycle,
352                'is_signer' : is_signer,
353                'is_signer_dm' : is_signer_dm,
354                'signers_data':signers_data
355                })
356   
357def get_management_data(request,obj,ctx):
358    """
359    Update context for html page which displays the Users who manage the selected object (:class:`PLMObjectUserLink`).
360    It computes a context dictionnary based on
361   
362    .. include:: views_params.txt
363    """
364    object_management_list = models.PLMObjectUserLink.objects.filter(plmobject=obj)
365    object_management_list = object_management_list.order_by("role")
366    levels =[]
367    lcs = obj.lifecycle.to_states_list()
368    for i, st in enumerate(lcs):
369        levels.append(level_to_sign_str(i))
370    signer_list = object_management_list.filter(role__in=levels)
371    notified_list = object_management_list.filter(role="notified")
372    owner_list = object_management_list.filter(role="owner")
373    if not ctx["is_owner"]:
374        link = object_management_list.filter(role="notified", user=request.user)
375        ctx["is_notified"] = bool(link)
376        if link:
377            ctx["remove_notify_link"] = link[0]
378        else:
379            if obj.check_in_group(request.user, False):
380                initial = { "type" : "User",
381                            "username" : request.user.username
382                          }
383                form = forms.SelectUserForm(initial=initial)
384                for field in ("type", "username"):
385                    form.fields[field].widget = HiddenInput()
386                ctx["notify_self_form"] = form
387                ctx["can_notify"] = True
388            else:
389                ctx["can_notify"] = False
390    ctx.update({'notified_list': notified_list,
391                'owner_list':owner_list})
392    return signer_list
393   
394
395@handle_errors
396def display_object_revisions(request, obj_type, obj_ref, obj_revi):
397    """
398    View that displays the revisions of the given object (a part or
399    a document) and shows a form to make a new revision.
400   
401    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/revisions/`
402   
403    .. include:: views_params.txt
404
405    This view returns the result of :func:`revise_document`
406    if the object is a document and the result of :func:`revise_part`
407    if the object is a part.
408    """
409    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
410    ctx["add_revision_form"] = None
411    if obj.is_document:
412        return revise_document(obj, ctx, request)
413    else:
414        return revise_part(obj, ctx, request)
415
416def revise_document(obj, ctx, request):
417    """
418    View to revise a document.
419
420    :param obj: displayed document
421    :type obj: :class:`.DocumentController`
422    :param ctx: initial context
423    :type ctx: dict
424    :param request: riven request
425 
426    This view can create a new revision of the document, it required
427    the following POST parameters:
428
429    :post params:
430        revision
431            new revision of the document
432        a valid :class:`.SelectPartFormset`
433            Only required if *confirmation* is True, see below.
434
435
436    A revised document may be attached to some parts.
437    These parts are given by :meth:`.DocumentController.get_suggested_parts`.
438    If there is at least one suggested part, a confirmation of which
439    parts will be attached to the new document is required.
440
441    **Template:**
442   
443    :file:`documents/revisions.html`
444
445    **Context:**
446
447    ``RequestContext``
448
449    ``confirmation``
450        True if a confirmation is required to revise the document.
451
452    ``revisions``
453        list of revisions of the document
454   
455    ``add_revision_form``
456        form to revise the document. Only set if the document is revisable.
457
458    ``part_formset``
459        a :class:`.SelectPartFormset` of parts that the new revision
460        may be attached to. Only set if *confirmation* is True.
461    """
462    confirmation = False
463    if obj.is_revisable():
464        parts = obj.get_suggested_parts()
465        confirmation = bool(parts)
466       
467        if request.method == "POST" and request.POST:
468            add_form = forms.AddRevisionForm(request.POST)
469            selected_parts = []
470            valid_forms = True
471            if confirmation:
472                part_formset = forms.SelectPartFormset(request.POST)
473                if part_formset.is_valid():
474                    for form in part_formset.forms:
475                        part = form.instance
476                        if part not in parts:
477                            # invalid data
478                            # an user should not be able to go here if he
479                            # does not write by hand its post request
480                            # so we do not need to generate an error message
481                            valid_forms = False
482                            break
483                        if form.cleaned_data["selected"]:
484                            selected_parts.append(part)
485                else:
486                    valid_forms = False
487            if add_form.is_valid() and valid_forms:
488                obj.revise(add_form.cleaned_data["revision"], selected_parts)
489                return HttpResponseRedirect(".")
490        else:
491            add_form = forms.AddRevisionForm({"revision" : get_next_revision(obj.revision)})
492            if confirmation:
493                ctx["part_formset"] = forms.SelectPartFormset(queryset=parts)
494        ctx["add_revision_form"] = add_form
495    ctx["confirmation"] = confirmation
496    revisions = obj.get_all_revisions()
497       
498    ctx["thumbnails"] = {}
499    ctx["num_files"] = {}
500   
501    ids=[]
502    for revision in revisions :
503        ids.append(revision.id)
504    #ids = revisions.values_list("id", flat=True)
505    thumbnails = models.DocumentFile.objects.filter(deprecated=False,
506                document__in=ids, thumbnail__isnull=False)
507    ctx["thumbnails"].update(dict(thumbnails.values_list("document", "thumbnail")))
508    num_files = dict.fromkeys(ids, 0)
509    for doc_id in models.DocumentFile.objects.filter(deprecated=False,
510        document__in=ids).values_list("document", flat=True):
511            num_files[doc_id] += 1
512            ctx["num_files"] = num_files
513       
514    ctx.update({'current_page' : 'revisions',
515                'revisions' : revisions,
516                })
517    return r2r('documents/revisions.html', ctx, request)
518
519def revise_part(obj, ctx, request):
520    """ View to revise a part.
521   
522    :param obj: displayed part
523    :type obj: :class:`.PartController`
524    :param ctx: initial context
525    :type ctx: dict
526    :param request: riven request
527 
528    This view can create a new revision of the part, it required
529    the following POST parameters:
530
531    :post params:
532        revision
533            new revision of the part
534        a valid :class:`.SelectParentFormset`
535            Only required if *confirmation* is True, see below.
536        a valid :class:`.SelectDocumentFormset`
537            Only required if *confirmation* is True, see below.
538        a valid :class:`.SelectParentFormset`
539            Only required if *confirmation* is True, see below.
540
541    A revised part may be attached to some documents.
542    These documents are given by :meth:`.PartController.get_suggested_documents`.
543    A revised part may also have some children from the original revision.
544    A revised part may also replace some parts inside a parent BOM.
545    These parents are given by :meth:`.PartController.get_suggested_parents`.
546
547    If there is at least one suggested object, a confirmation is required.
548
549    **Template:**
550   
551    :file:`parts/revisions.html`
552
553    **Context:**
554
555    ``RequestContext``
556
557    ``confirmation``
558        True if a confirmation is required to revise the part.
559
560    ``revisions``
561        list of revisions of the part
562   
563    ``add_revision_form``
564        form to revise the part. Only set if the document is revisable.
565
566    ``doc_formset``
567        a :class:`.SelectDocmentFormset` of documents that the new revision
568        may be attached to. Only set if *confirmation* is True.
569
570    ``children_formset``
571        a :class:`.SelectChildFormset` of parts that the new revision
572        will be composed of. Only set if *confirmation* is True.
573   
574    ``parents_formset``
575        a :class:`.SelectParentFormset` of parts that the new revision
576        will be added to, it will replace the previous revisions
577        in the parent's BOM.
578        Only set if *confirmation* is True.
579    """
580    confirmation = False
581    if obj.is_revisable():
582        children = [c.link for c in obj.get_children(1)]
583        parents = obj.get_suggested_parents()
584        documents = obj.get_suggested_documents()
585        confirmation = bool(children or parents or documents)
586
587        if request.method == "POST" and request.POST:
588            add_form = forms.AddRevisionForm(request.POST)
589            valid_forms = True
590            selected_children = []
591            selected_parents = []
592            selected_documents = []
593            if confirmation:
594                # children
595                children_formset = forms.SelectChildFormset(request.POST,
596                        prefix="children")
597                if children_formset.is_valid():
598                    for form in children_formset.forms:
599                        link = form.cleaned_data["link"]
600                        if link not in children:
601                            valid_forms = False
602                            break
603                        if form.cleaned_data["selected"]:
604                            selected_children.append(link)
605                else:
606                    valid_forms = False
607                if valid_forms:
608                    # documents
609                    doc_formset = forms.SelectDocumentFormset(request.POST,
610                            prefix="documents")
611                    if doc_formset.is_valid():
612                        for form in doc_formset.forms:
613                            doc = form.cleaned_data["document"]
614                            if doc not in documents:
615                                valid_forms = False
616                                break
617                            if form.cleaned_data["selected"]:
618                                selected_documents.append(doc)
619                    else:
620                        valid_forms = False
621                if valid_forms:
622                    # parents
623                    parents_formset = forms.SelectParentFormset(request.POST,
624                            prefix="parents")
625                    if parents_formset.is_valid():
626                        for form in parents_formset.forms:
627                            parent = form.cleaned_data["new_parent"]
628                            link = form.cleaned_data["link"]
629                            if (link, parent) not in parents:
630                                valid_forms = False
631                                break
632                            if form.cleaned_data["selected"]:
633                                selected_parents.append((link, parent))
634                    else:
635                        valid_forms = False
636            if add_form.is_valid() and valid_forms:
637                obj.revise(add_form.cleaned_data["revision"], selected_children,
638                        selected_documents, selected_parents)
639                return HttpResponseRedirect(".")
640        else:
641            add_form = forms.AddRevisionForm({"revision" : get_next_revision(obj.revision)})
642            if confirmation:
643                initial = [dict(link=link) for link in children]
644                ctx["children_formset"] = forms.SelectChildFormset(prefix="children",
645                        initial=initial)
646                initial = [dict(document=d) for d in documents]
647                ctx["doc_formset"] = forms.SelectDocumentFormset(prefix="documents",
648                        initial=initial)
649                initial = [dict(link=p[0], new_parent=p[1]) for p in parents]
650                ctx["parents_formset"] = forms.SelectParentFormset(prefix="parents",
651                        initial=initial)
652
653        ctx["add_revision_form"] = add_form
654
655    ctx["confirmation"] = confirmation
656    revisions = obj.get_all_revisions()
657    ctx.update({'current_page' : 'revisions',
658                'revisions' : revisions,
659                })
660    return r2r('parts/revisions.html', ctx, request)
661
662##########################################################################################
663@handle_errors
664def display_object_history(request, obj_type, obj_ref, obj_revi):
665    """
666    History view.
667   
668    This view displays an history of the selected object and its revisions.
669
670    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/history/`
671    :url: :samp:`/user/{username}/history/`
672    :url: :samp:`/group/{group_name}/history/`
673   
674    .. include:: views_params.txt
675
676    **Template:**
677   
678    :file:`attribute.html`
679
680    **Context:**
681
682    ``RequestContext``
683
684    ``object_history``
685        list of :class:`.AbstractHistory`
686
687    ``show_revisions``
688        True if the template should show the revision of each history row
689   
690    """
691    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
692    if hasattr(obj, "get_all_revisions"):
693        # display history of all revisions
694        objects = [o.id for o in obj.get_all_revisions()]
695        history = obj.HISTORY.objects.filter(plmobject__in=objects).order_by('-date')
696        history = history.select_related("user", "plmobject__revision")
697        ctx["show_revisions"] = True
698    else:
699        history = obj.HISTORY.objects.filter(plmobject=obj.object).order_by('-date')
700        ctx["show_revisions"] = False
701        history = history.select_related("user")
702    ctx.update({'current_page' : 'history',
703                'object_history' : list(history)})
704    return r2r('history.html', ctx, request)
705
706#############################################################################################
707###         All functions which manage the different html pages specific to part          ###
708#############################################################################################
709def get_children_data(obj, date, level, state):
710    max_level = 1 if level == "first" else -1
711    only_official = state == "official"
712    children = obj.get_children(max_level, date=date, only_official=only_official)
713    if level == "last" and children:
714        previous_level = 0
715        max_children = []
716        for c in children:
717            if max_children and c.level > previous_level:
718                del max_children[-1]
719            max_children.append(c)
720            previous_level = c.level
721        children = max_children
722    children = list(children)
723    # pcle
724    extra_columns = []
725    extension_data = defaultdict(dict)
726    for PCLE in models.get_PCLEs(obj.object):
727        fields = PCLE.get_visible_fields()
728        if fields:
729            extra_columns.extend((f, PCLE._meta.get_field(f).verbose_name)
730                    for f in fields)
731            pcles = PCLE.objects.filter(link__in=(c.link.id for c in children))
732            pcles = pcles.values("link_id", *fields)
733            for pcle in pcles:
734                extension_data[pcle["link_id"]].update(pcle)
735    return children, extra_columns, extension_data
736
737
738@handle_errors
739def display_object_child(request, obj_type, obj_ref, obj_revi):
740    """
741    BOM view.
742   
743    That views displays the children of the selected object that must be a part.
744   
745    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/bom-child/`
746   
747    .. include:: views_params.txt
748
749    **Template:**
750   
751    :file:`parts/bom.html`
752
753    **Context:**
754
755    ``RequestContext``
756   
757    ``children``
758        a list of :class:`.Child`
759
760    ``display_form``
761        a :class:`.DisplayChildrenForm`
762
763    ``extra_columns``
764        a list of extra columns that are displayed
765   
766    ``extension_data``
767
768    ``decomposition_msg``
769        a html message to decompose the part (may be empty)
770
771    ``decomposable_children``
772        a set of child part ids that are decomposable
773    """
774    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
775   
776    if not hasattr(obj, "get_children"):
777        # TODO
778        raise TypeError()
779    date = None
780    level = "first"
781    state = "all"
782    if request.GET:
783        display_form = forms.DisplayChildrenForm(request.GET)
784        if display_form.is_valid():
785            date = display_form.cleaned_data["date"]
786            level = display_form.cleaned_data["level"]
787            state = display_form.cleaned_data["state"]
788    else:
789        display_form = forms.DisplayChildrenForm(initial={"date" : datetime.datetime.now(),
790            "level" : "first", "state":"all"})
791    children, extra_columns, extension_data = get_children_data(obj, date, level, state)
792    # decomposition
793    if DecomposersManager.count() > 0:
794        children_ids = (c.link.child_id for c in children)
795        decomposable_children = DecomposersManager.get_decomposable_parts(children_ids)
796        decomposition_msg = DecomposersManager.get_decomposition_message(obj)
797    else:
798        decomposition_msg = ""
799        decomposable_children = []
800    ctx.update({'current_page' : 'BOM-child',
801                'children' : children,
802                'extra_columns' : extra_columns,
803                'extension_data' : extension_data,
804                'decomposition_msg' : decomposition_msg,
805                'decomposable_children' : decomposable_children,
806                "display_form" : display_form,
807                'level' : level,
808                })
809    return r2r('parts/bom.html', ctx, request)
810
811##########################################################################################
812@handle_errors(undo="..")
813def edit_children(request, obj_type, obj_ref, obj_revi):
814    """
815    Manage html page which edits the chidren of the selected object.
816    Possibility to modify the `.ParentChildLink.order`, the `.ParentChildLink.quantity` and to
817    desactivate the `.ParentChildLink`
818    It computes a context dictionnary based on
819   
820    .. include:: views_params.txt
821    """
822    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
823   
824    if not hasattr(obj, "get_children"):
825        # TODO
826        raise TypeError()
827    if request.method == "POST":
828        formset = forms.get_children_formset(obj, request.POST)
829        if formset.is_valid():
830            obj.update_children(formset)
831            return HttpResponseRedirect("..")
832    else:
833        formset = forms.get_children_formset(obj)
834    extra_columns = []
835    extra_fields = []
836    for PCLE in models.get_PCLEs(obj.object):
837        fields = PCLE.get_visible_fields()
838        if fields:
839            extra_columns.extend((f, PCLE._meta.get_field(f).verbose_name)
840                    for f in fields)
841            prefix = PCLE._meta.module_name
842            extra_fields.extend('%s_%s' % (prefix, f) for f in fields)
843    ctx.update({'current_page':'BOM-child',
844                'extra_columns' : extra_columns,
845                'extra_fields' : extra_fields,
846                'children_formset': formset, })
847    return r2r('parts/bom_edit.html', ctx, request)
848
849
850@handle_errors(undo="../..")
851def replace_child(request, obj_type, obj_ref, obj_revi, link_id):
852    """
853    View to replace a child by another one.
854
855    .. include:: views_params.txt
856    """
857    link_id = int(link_id)
858    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
859    link = models.ParentChildLink.objects.get(id=link_id)
860    if request.method == "POST":
861        form = forms.AddRelPartForm(request.POST)
862        if form.is_valid():
863            obj.replace_child(link, get_obj_from_form(form, request.user))
864            return HttpResponseRedirect("../..")
865    else:
866        form = forms.AddRelPartForm()
867    ctx["replace_child_form"] = form
868    ctx["link"] = link
869    ctx["attach"] = (obj, "add_child")
870    ctx["link_creation"] = True
871    return r2r("parts/bom_replace.html", ctx, request)
872
873##########################################################################################   
874@handle_errors
875def add_children(request, obj_type, obj_ref, obj_revi):
876    """
877    Manage html page for chidren creation of the selected object.
878    It computes a context dictionnary based on
879   
880    .. include:: views_params.txt
881    """
882    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
883   
884    if request.POST:
885        add_child_form = forms.AddChildForm(obj.object, request.POST)
886        if add_child_form.is_valid():
887            child_obj = get_obj_from_form(add_child_form, request.user)
888            obj.add_child(child_obj,
889                          add_child_form.cleaned_data["quantity"],
890                          add_child_form.cleaned_data["order"],
891                          add_child_form.cleaned_data["unit"],
892                          **add_child_form.extensions)
893            return HttpResponseRedirect(obj.plmobject_url + "BOM-child/")
894    else:
895        add_child_form = forms.AddChildForm(obj.object)
896        ctx['current_page'] = 'BOM-child'
897    ctx.update({'link_creation': True,
898                'add_child_form': add_child_form,
899                'attach' : (obj, "add_child")})
900    return r2r('parts/bom_add.html', ctx, request)
901   
902##########################################################################################   
903@handle_errors
904def display_object_parents(request, obj_type, obj_ref, obj_revi):
905    """
906    Manage html page which displays the parent of the selected object.
907    It computes a context dictionnary based on
908   
909    .. include:: views_params.txt
910    """
911    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
912   
913    if not hasattr(obj, "get_parents"):
914        # TODO
915        raise TypeError()
916    date = None
917    level = "first"
918    state = "all"
919    if request.GET:
920        display_form = forms.DisplayChildrenForm(request.GET)
921        if display_form.is_valid():
922            date = display_form.cleaned_data["date"]
923            level = display_form.cleaned_data["level"]
924            state = display_form.cleaned_data["state"]
925    else:
926        display_form = forms.DisplayChildrenForm(initial={"date" : datetime.datetime.now(),
927            "level" : "first", "state" : "all"})
928    max_level = 1 if level == "first" else -1
929    only_official = state == "official"
930    parents = obj.get_parents(max_level, date=date, only_official=only_official)
931    if level == "last" and parents:
932        previous_level = 0
933        max_parents = []
934        for c in parents:
935            if max_parents and c.level > previous_level:
936                del max_parents[-1]
937            max_parents.append(c)
938            previous_level = c.level
939        parents = max_parents
940    ctx.update({'current_page':'parents',
941                'parents' :  parents,
942                'display_form' : display_form, })
943    return r2r('parts/parents.html', ctx, request)
944
945##########################################################################################
946@handle_errors
947def display_object_doc_cad(request, obj_type, obj_ref, obj_revi):
948    """
949    Manage html page which displays the related documents and CAD of
950    the selected object.
951    It computes a context dictionnary based on
952   
953    .. include:: views_params.txt
954    """
955    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
956   
957    if not hasattr(obj, "get_attached_documents"):
958        # TODO
959        raise TypeError()
960    if request.method == "POST":
961        formset = forms.get_doc_cad_formset(obj, request.POST)
962        if formset.is_valid():
963            obj.update_doc_cad(formset)
964            return HttpResponseRedirect(".")
965    else:
966        formset = forms.get_doc_cad_formset(obj)
967    dforms = dict((form.instance.id, form) for form in formset.forms)
968    archive_form = forms.ArchiveForm()
969    ctx.update({'current_page':'doc-cad',
970                'all_docs': obj.get_attached_documents(),
971                'forms' : dforms,
972                'archive_form' : archive_form,
973                'docs_formset': formset})
974    return r2r('parts/doccad.html', ctx, request)
975
976
977##########################################################################################   
978@handle_errors
979def add_doc_cad(request, obj_type, obj_ref, obj_revi):
980    """
981    Manage html page for link creation (:class:`DocumentPartLink` link) between the selected object and some documents or CAD.
982    It computes a context dictionnary based on
983   
984    .. include:: views_params.txt
985    """
986    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
987   
988    if request.POST:
989        add_doc_cad_form = forms.AddDocCadForm(request.POST)
990        if add_doc_cad_form.is_valid():
991            doc_cad_obj = get_obj_from_form(add_doc_cad_form, request.user)
992            obj.attach_to_document(doc_cad_obj)
993            return HttpResponseRedirect(obj.plmobject_url + "doc-cad/")
994    else:
995        add_doc_cad_form = forms.AddDocCadForm()
996    ctx.update({'link_creation': True,
997                'add_doc_cad_form': add_doc_cad_form,
998                'attach' : (obj, "attach_doc")})
999    return r2r('parts/doccad_add.html', ctx, request)
1000   
1001#############################################################################################
1002###      All functions which manage the different html pages specific to documents        ###
1003#############################################################################################
1004@handle_errors
1005def display_related_part(request, obj_type, obj_ref, obj_revi):
1006    """
1007    Manage html page which displays the related part of (:class:`DocumentPartLink` with) the selected object.
1008    It computes a context dictionnary based on
1009   
1010    .. include:: views_params.txt
1011    """
1012    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1013   
1014    if not hasattr(obj, "get_attached_parts"):
1015        # TODO
1016        raise TypeError()
1017    if request.method == "POST":
1018        formset = forms.get_rel_part_formset(obj, request.POST)
1019        if formset.is_valid():
1020            obj.update_rel_part(formset)
1021            return HttpResponseRedirect(".")
1022    else:
1023        formset = forms.get_rel_part_formset(obj)
1024    rforms = dict((form.instance.id, form) for form in formset.forms)
1025
1026    ctx.update({'current_page':'parts',
1027                'all_parts': obj.get_attached_parts(),
1028                'forms' : rforms,
1029                'parts_formset': formset})
1030    return r2r('documents/parts.html', ctx, request)
1031
1032##########################################################################################   
1033@handle_errors
1034def add_rel_part(request, obj_type, obj_ref, obj_revi):
1035    """
1036    Manage html page for link creation (:class:`DocumentPartLink` link) between the selected object and some parts.
1037    It computes a context dictionnary based on
1038   
1039    .. include:: views_params.txt
1040    """
1041    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1042   
1043    if request.POST:
1044        add_rel_part_form = forms.AddRelPartForm(request.POST)
1045        if add_rel_part_form.is_valid():
1046            part_obj = get_obj_from_form(add_rel_part_form, request.user)
1047            obj.attach_to_part(part_obj)
1048            ctx.update({'add_rel_part_form': add_rel_part_form, })
1049            return HttpResponseRedirect(obj.plmobject_url + "parts/")
1050    else:
1051        add_rel_part_form = forms.AddRelPartForm()
1052    ctx.update({'link_creation': True,
1053                'add_rel_part_form': add_rel_part_form,
1054                'attach' : (obj, "attach_part") })
1055    return r2r('documents/parts_add.html', ctx, request)
1056
1057##########################################################################################
1058@handle_errors
1059def display_files(request, obj_type, obj_ref, obj_revi):
1060    """
1061    Manage html page which displays the files (:class:`DocumentFile`) uploaded in the selected object.
1062    It computes a context dictionnary based on
1063   
1064    .. include:: views_params.txt
1065    """
1066    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1067
1068    if not hasattr(obj, "files"):
1069        raise TypeError()
1070    if request.method == "POST":
1071        formset = forms.get_file_formset(obj, request.POST)
1072        if formset.is_valid():
1073            obj.update_file(formset)
1074            return HttpResponseRedirect(".")
1075    else:
1076        formset = forms.get_file_formset(obj)
1077    add_file_form = forms.AddFileForm()
1078    archive_form = forms.ArchiveForm()
1079   
1080    ctx.update({'current_page':'files',
1081                'file_formset': formset,
1082                'archive_form' : archive_form,
1083                'deprecated_files' : obj.deprecated_files,
1084                'add_file_form': add_file_form,
1085                'document_type': obj_type,
1086               })
1087    return r2r('documents/files.html', ctx, request)
1088
1089##########################################################################################
1090@csrf_protect
1091@handle_errors(undo="..")
1092def add_file(request, obj_type, obj_ref, obj_revi):
1093    """
1094    Manage html page for the files (:class:`DocumentFile`) addition in the selected object.
1095    It computes a context dictionnary based on
1096   
1097    .. include:: views_params.txt
1098    """
1099    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1100    if request.method == "POST":
1101        add_file_form = forms.AddFileForm(request.POST, request.FILES)
1102        if add_file_form.is_valid():
1103            for fkey, f in request.FILES.iteritems():
1104                obj.add_file(request.FILES[fkey])
1105            return HttpResponseRedirect(obj.plmobject_url + "files/")
1106    else:
1107        if 'file_name' in request.GET:
1108            f_name = request.GET['file_name'].encode("utf-8")
1109            if obj.has_standard_related_locked(f_name):
1110                return HttpResponse("true:Native file has a standard related locked file.")
1111            else:
1112                return HttpResponse("false:")
1113        add_file_form = forms.AddFileForm()
1114        files = forms.get_file_formset(obj)
1115        ctx['files_list'] =  files
1116    ctx.update({
1117        'add_file_form': add_file_form,
1118        'document_type': obj_type,
1119    })
1120    return r2r('documents/files_add_noscript.html', ctx, request)
1121
1122##########################################################################################
1123
1124@csrf_exempt
1125def up_file(request, obj_type, obj_ref, obj_revi):
1126    request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
1127    return _up_file(request, obj_type, obj_ref, obj_revi)
1128
1129@csrf_protect
1130@handle_errors
1131def _up_file(request, obj_type, obj_ref, obj_revi):
1132    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1133    if request.method == "POST":
1134        add_file_form = forms.AddFileForm(request.POST, request.FILES)
1135        if add_file_form.is_valid():
1136            for key, f_id in request.GET.iteritems():
1137                obj.add_file(request.FILES[key])
1138            return HttpResponse(".")
1139        else:
1140            return HttpResponse("failed")
1141
1142@handle_errors
1143@csrf_protect
1144def up_progress(request, obj_type, obj_ref, obj_revi):
1145    """
1146    Show upload progress for a given path
1147    """
1148    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1149    ret = ""
1150    p_id = request.GET['X-Progress-ID']
1151    tempdir = settings.FILE_UPLOAD_TEMP_DIR or tempfile.gettempdir()
1152    f = glob.glob(os.path.join(tempdir, "*%s_upload" % p_id))
1153    if f:
1154        ret = str(os.path.getsize(f[0]))
1155    if not ret:
1156        ret = "0:waiting"
1157    else:
1158        if ret == request.GET['f_size']:
1159            ret += ":linking"
1160        else:
1161            ret += ":writing"
1162    return HttpResponse(ret)
1163
1164
1165#############################################################################################
1166###    All functions which manage the different html pages specific to part and document  ###
1167#############################################################################################
1168
1169@handle_errors(undo="../../../lifecycle")
1170def replace_management(request, obj_type, obj_ref, obj_revi, link_id):
1171    """
1172    Manage html page for the modification of the Users who manage the selected object (:class:`PLMObjectUserLink`).
1173    It computes a context dictionnary based on
1174   
1175    .. include:: views_params.txt
1176    :param link_id: :attr:`.PLMObjectUserLink.id`
1177    :type link_id: str
1178    """
1179    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1180    link = models.PLMObjectUserLink.objects.get(id=int(link_id))
1181    if obj.object.id != link.plmobject.id:
1182        raise ValueError("Bad link id")
1183   
1184    if request.method == "POST":
1185        replace_management_form = forms.SelectUserForm(request.POST)
1186        if replace_management_form.is_valid():
1187            if replace_management_form.cleaned_data["type"] == "User":
1188                user_obj = get_obj_from_form(replace_management_form, request.user)
1189                obj.set_role(user_obj.object, link.role)
1190                if link.role == 'notified':
1191                    obj.remove_notified(link.user)
1192            return HttpResponseRedirect("../../../lifecycle")
1193    else:
1194        replace_management_form = forms.SelectUserForm()
1195   
1196    ctx.update({'current_page':'lifecycle',
1197                'replace_management_form': replace_management_form,
1198                'link_creation': True,
1199                'attach' : (obj, "delegate")})
1200    return r2r('management_replace.html', ctx, request)
1201
1202##########################################################################################   
1203@handle_errors(undo="../../lifecycle")
1204def add_management(request, obj_type, obj_ref, obj_revi):
1205    """
1206    Manage html page for the addition of a "notification" link
1207    (:class:`PLMObjectUserLink`) between some Users and the selected object.
1208    It computes a context dictionnary based on
1209   
1210    .. include:: views_params.txt
1211    """
1212    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1213   
1214    if request.method == "POST":
1215        add_management_form = forms.SelectUserForm(request.POST)
1216        if add_management_form.is_valid():
1217            if add_management_form.cleaned_data["type"] == "User":
1218                user_obj = get_obj_from_form(add_management_form, request.user)
1219                obj.set_role(user_obj.object, "notified")
1220            return HttpResponseRedirect("../../lifecycle")
1221    else:
1222        add_management_form = forms.SelectUserForm()
1223   
1224    ctx.update({'current_page':'lifecycle',
1225                'replace_management_form': add_management_form,
1226                'link_creation': True,
1227                "attach" : (obj, "delegate")})
1228    return r2r('management_replace.html', ctx, request)
1229
1230##########################################################################################   
1231@handle_errors
1232def delete_management(request, obj_type, obj_ref, obj_revi):
1233    """
1234    Manage html page for the deletion of a "notification" link (:class:`PLMObjectUserLink`) between some Users and the selected object.
1235    It computes a context dictionnary based on
1236   
1237    .. include:: views_params.txt
1238    """
1239    obj = get_obj(obj_type, obj_ref, obj_revi, request.user)
1240    if request.method == "POST":
1241        try:
1242            link_id = request.POST["link_id"]
1243            link = models.PLMObjectUserLink.objects.get(id=int(link_id))
1244            obj.remove_notified(link.user)
1245        except (KeyError, ValueError, ControllerError):
1246            return HttpResponseForbidden()
1247    return HttpResponseRedirect("../../lifecycle")
1248
1249##########################################################################################
1250###    Manage html pages for part / document creation and modification                 ###
1251##########################################################################################
1252
1253@handle_errors
1254def create_object(request, from_registered_view=False, creation_form=None):
1255    """
1256    Manage html page for the creation of an instance of `models.PLMObject` subclass.
1257    It computes a context dictionnary based on
1258   
1259    :param request: :class:`django.http.QueryDict`
1260    :return: a :class:`django.http.HttpResponse`
1261    """
1262
1263    obj, ctx = get_generic_data(request)
1264    Form = forms.TypeForm
1265    # it is possible that the created object must be attached to a part
1266    # or a document
1267    # related_doc and related_part should be a plmobject id
1268    # If the related_doc/part is not a doc/part, we let python raise
1269    # an AttributeError, since an user should not play with the URL
1270    # and openPLM must be smart enough to produce valid URLs
1271    attach = related = None
1272    if "related_doc" in request.REQUEST:
1273        Form = forms.PartTypeForm
1274        doc = get_obj_by_id(int(request.REQUEST["related_doc"]), request.user)
1275        attach = doc.attach_to_part
1276        ctx["related_doc"] = request.REQUEST["related_doc"]
1277        related = ctx["related"] = doc
1278    elif "related_part" in request.REQUEST:
1279        Form = forms.DocumentTypeForm
1280        part = get_obj_by_id(int(request.REQUEST["related_part"]), request.user)
1281        attach = part.attach_to_document
1282        ctx["related_part"] = request.REQUEST["related_part"]
1283        related = ctx["related"] = part
1284    if "__next__" in request.REQUEST:
1285        redirect_to = request.REQUEST["__next__"]
1286        ctx["next"] = redirect_to
1287    else:
1288        # will redirect to the created object
1289        redirect_to = None
1290
1291    type_form = Form(request.REQUEST)
1292    if type_form.is_valid():
1293        type_ = type_form.cleaned_data["type"]
1294        cls = models.get_all_users_and_plmobjects()[type_]
1295        if not from_registered_view:
1296            view = get_creation_view(cls)
1297            if view is not None:
1298                # view has been registered to create an object of type 'cls'
1299                return view(request)
1300    else:
1301        ctx["creation_type_form"] = type_form
1302        return r2r('create.html', ctx, request)
1303
1304    if request.method == 'GET' and creation_form is None:
1305        creation_form = forms.get_creation_form(request.user, cls)
1306        if related is not None:
1307            creation_form.fields["group"].initial = related.group
1308            creation_form.fields["lifecycle"].initial = related.lifecycle
1309    elif request.method == 'POST':
1310        if creation_form is None:
1311            creation_form = forms.get_creation_form(request.user, cls, request.POST)
1312        if creation_form.is_valid():
1313            ctrl_cls = get_controller(type_)
1314            ctrl = ctrl_cls.create_from_form(creation_form, request.user)
1315            if attach is not None:
1316                try:
1317                    attach(ctrl)
1318                except (ControllerError, ValueError) as e:
1319                    # crtl cannot be attached (maybe the state of the
1320                    # related object as changed)
1321                    # alerting the user using the messages framework since
1322                    # the response is redirected
1323                    message = _(u"Error: %(details)s") % dict(details=unicode(e))
1324                    messages.error(request, message)
1325                    # redirecting to the ctrl page that least its attached
1326                    # objects
1327                    if ctrl.is_document:
1328                        return HttpResponseRedirect(ctrl.plmobject_url + "parts/")
1329                    else:
1330                        return HttpResponseRedirect(ctrl.plmobject_url + "doc-cad/")
1331            return HttpResponseRedirect(redirect_to or ctrl.plmobject_url)
1332    ctx.update({
1333        'creation_form' : creation_form,
1334        'object_type' : type_,
1335        'creation_type_form' : type_form,
1336    })
1337    return r2r('create.html', ctx, request)
1338
1339##########################################################################################
1340@handle_errors(undo="../attributes/")
1341def modify_object(request, obj_type, obj_ref, obj_revi):
1342    """
1343    Manage html page for the modification of the selected object.
1344    It computes a context dictionnary based on
1345   
1346    .. include:: views_params.txt
1347    """
1348    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1349    cls = models.get_all_plmobjects()[obj_type]
1350    if request.method == 'POST' and request.POST:
1351        modification_form = forms.get_modification_form(cls, request.POST)
1352        if modification_form.is_valid():
1353            obj.update_from_form(modification_form)
1354            return HttpResponseRedirect(obj.plmobject_url + "attributes/")
1355    else:
1356        modification_form = forms.get_modification_form(cls, instance=obj.object)
1357   
1358    ctx['modification_form'] = modification_form
1359    return r2r('edit.html', ctx, request)
1360
1361#############################################################################################
1362###         All functions which manage the different html pages specific to user          ###
1363#############################################################################################
1364@handle_errors
1365def modify_user(request, obj_ref):
1366    """
1367    Manage html page for the modification of the selected
1368    :class:`~django.contrib.auth.models.User`.
1369    It computes a context dictionnary based on
1370   
1371    :param request: :class:`django.http.QueryDict`
1372    :param obj_type: :class:`~django.contrib.auth.models.User`
1373    :return: a :class:`django.http.HttpResponse`
1374    """
1375    obj, ctx = get_generic_data(request, "User", obj_ref)
1376    if obj.object != request.user:
1377        raise PermissionError("You are not the user")
1378    class_for_div="ActiveBox4User"
1379    if request.method == 'POST' and request.POST:
1380        modification_form = forms.OpenPLMUserChangeForm(request.POST)
1381        if modification_form.is_valid():
1382            obj.update_from_form(modification_form)
1383            return HttpResponseRedirect("/user/%s/" % obj.username)
1384    else:
1385        modification_form = forms.OpenPLMUserChangeForm(instance=obj.object)
1386   
1387    ctx.update({'class4div': class_for_div, 'modification_form': modification_form})
1388    return r2r('edit.html', ctx, request)
1389   
1390##########################################################################################
1391@handle_errors
1392def change_user_password(request, obj_ref):
1393    """
1394    Manage html page for the modification of the selected
1395    :class:`~django.contrib.auth.models.User` password.
1396    It computes a context dictionnary based on
1397   
1398    :param request: :class:`django.http.QueryDict`
1399    :param obj_ref: :attr:`~django.contrib.auth.models.User.username`
1400    :return: a :class:`django.http.HttpResponse`
1401    """
1402    if request.user.username=='test':
1403        return HttpResponseRedirect("/user/%s/attributes/" % request.user)
1404    obj, ctx = get_generic_data(request, "User", obj_ref)
1405    if obj.object != request.user:
1406        raise PermissionError("You are not the user")
1407
1408    if request.method == 'POST' and request.POST:
1409        modification_form = PasswordChangeForm(obj, request.POST)
1410        if modification_form.is_valid():
1411            obj.set_password(modification_form.cleaned_data['new_password2'])
1412            obj.save()
1413            return HttpResponseRedirect("/user/%s/" % obj.username)
1414    else:
1415        modification_form = PasswordChangeForm(obj)
1416   
1417    ctx.update({'class4div': "ActiveBox4User",
1418                'modification_form': modification_form})
1419    return r2r('users/password.html', ctx, request)
1420
1421#############################################################################################
1422@handle_errors
1423def display_related_plmobject(request, obj_type, obj_ref, obj_revi):
1424    """
1425    View listing the related parts and documents of
1426    the selected :class:`~django.contrib.auth.models.User`.
1427   
1428    .. include:: views_params.txt
1429    """
1430    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1431   
1432    if not hasattr(obj, "get_object_user_links"):
1433        # TODO
1434        raise TypeError()
1435    objs = obj.get_object_user_links().select_related("plmobject")
1436    objs = objs.values("role", "plmobject__type", "plmobject__reference",
1437            "plmobject__revision", "plmobject__name")
1438    ctx.update({'current_page':'parts-doc-cad',
1439        'object_user_link': objs,
1440        'last_edited_objects':  get_last_edited_objects(obj.object),
1441    })
1442    return r2r('users/plmobjects.html', ctx, request)
1443
1444#############################################################################################
1445@handle_errors
1446def display_delegation(request, obj_ref):
1447    """
1448    Manage html page which displays the delegations of the selected
1449    :class:`~django.contrib.auth.models.User`.
1450    It computes a context dictionnary based on
1451   
1452    :param request: :class:`django.http.QueryDict`
1453    :param obj_ref: :attr:`~django.contrib.auth.models.User.username`
1454    :type obj_ref: str
1455    :return: a :class:`django.http.HttpResponse`
1456    """
1457    obj, ctx = get_generic_data(request, "User", obj_ref)
1458   
1459    if not hasattr(obj, "get_user_delegation_links"):
1460        # TODO
1461        raise TypeError()
1462    if request.method == "POST":
1463        selected_link_id = request.POST.get('link_id')
1464        obj.remove_delegation(models.DelegationLink.objects.get(pk=int(selected_link_id)))
1465    links = obj.get_user_delegation_links().select_related("delegatee")
1466    ctx.update({'current_page':'delegation',
1467                'user_delegation_link': links})
1468   
1469    return r2r('users/delegation.html', ctx, request)
1470
1471
1472##########################################################################################   
1473@handle_errors(undo="../../..")
1474def delegate(request, obj_ref, role, sign_level):
1475    """
1476    Manage html page for delegations modification of the selected
1477    :class:`~django.contrib.auth.models.User`.
1478    It computes a context dictionnary based on
1479   
1480    :param request: :class:`django.http.QueryDict`
1481    :param obj_type: :class:`~django.contrib.auth.models.User`
1482    :type obj_ref: str
1483    :param role: :attr:`.DelegationLink.role` if role is not "sign"
1484    :type role: str
1485    :param sign_level: used for :attr:`.DelegationLink.role` if role is "sign"
1486    :type sign_level: str
1487    :return: a :class:`django.http.HttpResponse`
1488    """
1489    obj, ctx = get_generic_data(request, "User", obj_ref)
1490   
1491    if request.method == "POST":
1492        delegation_form = forms.SelectUserForm(request.POST)
1493        if delegation_form.is_valid():
1494            if delegation_form.cleaned_data["type"] == "User":
1495                user_obj = get_obj_from_form(delegation_form, request.user)
1496                if role == "notified" or role == "owner":
1497                    obj.delegate(user_obj.object, role)
1498                    return HttpResponseRedirect("../..")
1499                elif role == "sign":
1500                    if sign_level == "all":
1501                        obj.delegate(user_obj.object, "sign*")
1502                        return HttpResponseRedirect("../../..")
1503                    elif sign_level.isdigit():
1504                        obj.delegate(user_obj.object, level_to_sign_str(int(sign_level)-1))
1505                        return HttpResponseRedirect("../../..")
1506    else:
1507        delegation_form = forms.SelectUserForm()
1508    if role == 'sign':
1509        if sign_level.isdigit():
1510            role = _("signer level") + " " + str(sign_level)
1511        else:
1512            role = _("signer all levels")
1513    elif role == "notified":
1514        role = _("notified")
1515   
1516    ctx.update({'current_page':'delegation',
1517                'replace_management_form': delegation_form,
1518                'link_creation': True,
1519                'attach' : (obj, "delegate"),
1520                'role': role})
1521    return r2r('management_replace.html', ctx, request)
1522   
1523   
1524##########################################################################################
1525###             Manage html pages for file check-in / check-out / download             ###
1526##########################################################################################
1527
1528@csrf_exempt
1529def get_checkin_file(request, obj_type, obj_ref, obj_revi, file_id_value):
1530    request.upload_handlers.insert(0, ProgressBarUploadHandler(request))
1531    return checkin_file(request, obj_type, obj_ref, obj_revi,file_id_value)
1532   
1533@handle_errors(undo="../..")
1534@csrf_protect
1535def checkin_file(request, obj_type, obj_ref, obj_revi, file_id_value):
1536    """
1537    Manage html page for the files (:class:`DocumentFile`) checkin in the selected object.
1538    It computes a context dictionnary based on
1539   
1540    .. include:: views_params.txt
1541    :param file_id_value: :attr:`.DocumentFile.id`
1542    :type file_id_value: str
1543    :return: a :class:`django.http.HttpResponse`
1544    """
1545    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
1546    if request.POST:
1547        checkin_file_form = forms.AddFileForm(request.POST, request.FILES)
1548        print checkin_file_form.errors
1549        if checkin_file_form.is_valid():
1550            obj.checkin(models.DocumentFile.objects.get(id=file_id_value),
1551                        request.FILES["filename"])
1552
1553            return HttpResponseRedirect(obj.plmobject_url + "files/")
1554    else:
1555        checkin_file_form = forms.AddFileForm()
1556    ctx['add_file_form'] =  checkin_file_form
1557    return r2r('documents/files_add_noscript.html', ctx, request)
1558
1559##########################################################################################
1560@handle_errors
1561def download(request, docfile_id, filename=""):
1562    """
1563    View to download a document file.
1564   
1565    :param request: :class:`django.http.QueryDict`
1566    :param docfile_id: :attr:`.DocumentFile.id`
1567    :type docfile_id: str
1568    :return: a :class:`django.http.HttpResponse`
1569    """
1570    doc_file = models.DocumentFile.objects.get(id=docfile_id)
1571    ctrl = get_obj_by_id(int(doc_file.document.id), request.user)
1572    ctrl.check_readable()
1573    return serve(ctrl, doc_file, filename)
1574
1575@secure_required
1576def public_download(request, docfile_id, filename=""):
1577    """
1578    View to download a published document file.
1579
1580    It returns an :class: `HttpResponseForbidden` if the document is
1581    not published.
1582   
1583    :param request: :class:`django.http.QueryDict`
1584    :param docfile_id: :attr:`.DocumentFile.id`
1585    :type docfile_id: str
1586    :return: a :class:`django.http.HttpResponse`
1587    """
1588    doc_file = models.DocumentFile.objects.get(id=docfile_id)
1589    ctrl = get_obj_by_id(int(doc_file.document.id), request.user)
1590    if not ctrl.published:
1591        return HttpResponseForbidden()
1592    return serve(ctrl, doc_file, filename)
1593
1594
1595def serve(ctrl, doc_file, filename):
1596    name = doc_file.filename.encode("utf-8", "ignore")
1597    mimetype = guess_type(name, False)[0]
1598    if not mimetype:
1599        mimetype = 'application/octet-stream'
1600    f, size = ctrl.get_content_and_size(doc_file)
1601    response = HttpResponse(f, mimetype=mimetype)
1602    response["Content-Length"] = size
1603    if not filename:
1604        response['Content-Disposition'] = 'attachment; filename="%s"' % name
1605    return response
1606
1607def get_cad_files(part):
1608    """
1609    Returns an iterable of all :class:`.DocumentFile` related
1610    to *part* that contains a CAD file. It retrieves all non deprecated
1611    files of all documents parts to *part* and its children and
1612    filters these files according to their extension (see :meth:`.is_cad_file`).
1613    """
1614    children = part.get_children(-1, related=("child",))
1615    children_ids = set(c.link.child_id for c in children)
1616    children_ids.add(part.id)
1617    links = models.DocumentPartLink.objects.filter(part__in=children_ids)
1618    docs = links.values_list("document", flat=True)
1619    d_o_u = "document__owner__username"
1620    files = models.DocumentFile.objects.filter(deprecated=False,
1621                document__in=set(docs))
1622    # XXX : maybe its faster to build a complex query than retrieving
1623    # each file and testing their extension
1624    return (df for df in files.select_related(d_o_u) if is_cad_file(df.filename))
1625
1626
1627@handle_errors
1628def download_archive(request, obj_type, obj_ref, obj_revi):
1629    """
1630    View to download all files from a document/part.
1631
1632    .. include:: views_params.txt
1633    """
1634
1635    obj = get_obj(obj_type, obj_ref, obj_revi, request.user)
1636    obj.check_readable()
1637   
1638    d_o_u = "document__owner__username"
1639    if obj.is_document:
1640        files = obj.files.select_related(d_o_u)
1641    elif obj.is_part and "cad" in request.GET:
1642        files = get_cad_files(obj)
1643    elif obj.is_part:
1644        links = obj.get_attached_documents()
1645        docs = (link.document for link in links)
1646        files = itertools.chain(*(doc.files.select_related(d_o_u)
1647            for doc in docs))
1648    else:
1649        return HttpResponseForbidden()
1650
1651    form = forms.ArchiveForm(request.GET)
1652    if form.is_valid():
1653        format = form.cleaned_data["format"]
1654        name = "%s_%s.%s" % (obj_ref, obj_revi, format)
1655        mimetype = guess_type(name, False)[0]
1656        if not mimetype:
1657            mimetype = 'application/octet-stream'
1658        content = generate_archive(files, format)
1659        response = HttpResponse(content, mimetype=mimetype)
1660        #response["Content-Length"] = size
1661        response['Content-Disposition'] = 'attachment; filename="%s"' % name
1662        return response
1663    return HttpResponseForbidden()
1664##########################################################################################
1665@handle_errors
1666def checkout_file(request, obj_type, obj_ref, obj_revi, docfile_id):
1667    """
1668    Manage html page for the files (:class:`DocumentFile`) checkout from the selected object.
1669    It locks the :class:`DocumentFile` and, after, calls :func:`.views.download`
1670   
1671    .. include:: views_params.txt
1672    :param docfile_id: :attr:`.DocumentFile.id`
1673    :type docfile_id_value: str
1674    """
1675    obj = get_obj(obj_type, obj_ref, obj_revi, request.user)
1676    doc_file = models.DocumentFile.objects.get(id=docfile_id)
1677    obj.lock(doc_file)
1678    return download(request, docfile_id)
1679
1680##########################################################################################
1681###                     Manage html pages for navigate function                        ###
1682##########################################################################################   
1683@handle_errors
1684def navigate(request, obj_type, obj_ref, obj_revi):
1685    """
1686    Manage html page which displays a graphical picture the different links
1687    between :class:`~django.contrib.auth.models.User` and  :class:`.models.PLMObject`.
1688    This function uses Graphviz (http://graphviz.org/).
1689    Some filters let user defines which type of links he/she wants to display.
1690    It computes a context dictionary based on
1691   
1692    .. include:: views_params.txt
1693    """
1694    ctx = get_navigate_data(request, obj_type, obj_ref, obj_revi)
1695    ctx["edges"] = simplejson.dumps(ctx["edges"])
1696    return r2r('navigate.html', ctx, request)
1697
1698@handle_errors
1699def display_users(request, obj_ref):
1700    obj, ctx = get_generic_data(request, "Group", obj_ref)
1701    if request.method == "POST":
1702        formset = forms.get_user_formset(obj, request.POST)
1703        if formset.is_valid():
1704            obj.update_users(formset)
1705            return HttpResponseRedirect(".")
1706    else:
1707        formset = forms.get_user_formset(obj)
1708    ctx["user_formset"] = formset
1709    ctx["pending_invitations"] = obj.invitation_set.filter(
1710            state=models.Invitation.PENDING)
1711    ctx['current_page'] = 'users'
1712    ctx['in_group'] = bool(request.user.groups.filter(id=obj.id))
1713    return r2r("groups/users.html", ctx, request)
1714
1715@handle_errors
1716def group_add_user(request, obj_ref):
1717    """
1718    View of the *Add user* page of a group.
1719
1720    """
1721
1722    obj, ctx = get_generic_data(request, "Group", obj_ref)
1723    if request.method == "POST":
1724        form = forms.SelectUserForm(request.POST)
1725        if form.is_valid():
1726            obj.add_user(User.objects.get(username=form.cleaned_data["username"]))
1727            return HttpResponseRedirect("..")
1728    else:
1729        form = forms.SelectUserForm()
1730    ctx["add_user_form"] = form
1731    ctx['current_page'] = 'users'
1732    ctx['link_creation'] = True
1733    return r2r("groups/add_user.html", ctx, request)
1734
1735@handle_errors
1736def group_ask_to_join(request, obj_ref):
1737    obj, ctx = get_generic_data(request, "Group", obj_ref)
1738    if request.method == "POST":
1739        obj.ask_to_join()
1740        return HttpResponseRedirect("..")
1741    else:
1742        form = forms.SelectUserForm()
1743    ctx["ask_form"] = ""
1744    ctx['current_page'] = 'users'
1745    ctx['in_group'] = bool(request.user.groups.filter(id=obj.id))
1746    return r2r("groups/ask_to_join.html", ctx, request)
1747
1748@handle_errors
1749def display_groups(request, obj_ref):
1750    """
1751    View of the *groups* page of an user.
1752
1753    """
1754
1755    obj, ctx = get_generic_data(request, "User", obj_ref)
1756    ctx["groups"] = models.GroupInfo.objects.filter(id__in=obj.groups.all())\
1757            .order_by("name")
1758
1759    ctx['current_page'] = 'groups'
1760    return r2r("users/groups.html", ctx, request)
1761
1762@handle_errors
1763def sponsor(request, obj_ref):
1764    """
1765    View of the *sponsor* page.
1766    """
1767    obj, ctx = get_generic_data(request, "User", obj_ref)
1768
1769    if request.method == "POST":
1770        form = forms.SponsorForm(request.POST)
1771        if form.is_valid():
1772            new_user = form.save()
1773            new_user.get_profile().language = form.cleaned_data["language"]
1774            obj.sponsor(new_user)
1775            return HttpResponseRedirect("..")
1776    else:
1777        form = forms.SponsorForm(initial={"sponsor":obj.id, "language":obj.language}, sponsor=obj.id)
1778    ctx["sponsor_form"] = form
1779    ctx['current_page'] = 'delegation'
1780    return r2r("users/sponsor.html", ctx, request)
1781
1782@handle_errors
1783def create_user(request):
1784    url = request.user.get_profile().plmobject_url + "delegation/sponsor/"
1785    return HttpResponseRedirect(url)
1786register_creation_view(User, create_user)
1787
1788@handle_errors
1789def sponsor_resend_mail(request, obj_ref):
1790    obj, ctx = get_generic_data(request, "User", obj_ref)
1791    if request.method == "POST":
1792        try:
1793            link_id = request.POST["link_id"]
1794            link = models.DelegationLink.objects.get(id=int(link_id))
1795            obj.resend_sponsor_mail(link.delegatee)
1796        except (KeyError, ValueError, ControllerError) as e:
1797            return HttpResponseForbidden()
1798    return HttpResponseRedirect("../../")
1799
1800@handle_errors
1801def display_plmobjects(request, obj_ref):
1802    """
1803    View of the *objects* page of a group.
1804    """
1805   
1806    obj, ctx = get_generic_data(request, "Group", obj_ref)
1807   
1808    #ctx["objects"] = obj.plmobject_group.order_by("type", "reference", "revision")
1809    objects = obj.plmobject_group.order_by("type", "reference", "revision")
1810
1811    display_pagination(request.GET,ctx, objects,"object")
1812
1813    ctx['current_page'] = 'objects'
1814    return r2r("groups/objects.html", ctx, request)
1815
1816@handle_errors(undo="../../../users/")
1817def accept_invitation(request, obj_ref, token):
1818    token = long(token)
1819    obj, ctx = get_generic_data(request, "Group", obj_ref)
1820    inv = models.Invitation.objects.get(token=token)
1821    if request.method == "POST":
1822        form = forms.InvitationForm(request.POST)
1823        if form.is_valid() and inv == form.cleaned_data["invitation"]:
1824            obj.accept_invitation(inv)
1825            return HttpResponseRedirect("../../../users/")
1826    else:
1827        form = forms.InvitationForm(initial={"invitation" : inv})
1828    ctx["invitation_form"] = form
1829    ctx['current_page'] = 'users'
1830    ctx["invitation"] = inv
1831    return r2r("groups/accept_invitation.html", ctx, request)
1832
1833 
1834@handle_errors(undo="../../../users/")
1835def refuse_invitation(request, obj_ref, token):
1836    token = long(token)
1837    obj, ctx = get_generic_data(request, "Group", obj_ref)
1838    inv = models.Invitation.objects.get(token=token)
1839    if request.method == "POST":
1840        form = forms.InvitationForm(request.POST)
1841        if form.is_valid() and inv == form.cleaned_data["invitation"]:
1842            obj.refuse_invitation(inv)
1843            return HttpResponseRedirect("../../../users/")
1844    else:
1845        form = forms.InvitationForm(initial={"invitation" : inv})
1846    ctx["invitation_form"] = form
1847    ctx["invitation"] = inv
1848    ctx['current_page'] = 'users'
1849    return r2r("groups/refuse_invitation.html", ctx, request)
1850
1851@handle_errors
1852def send_invitation(request, obj_ref, token):
1853    """
1854    Views to (re)send an invitation.
1855
1856    :param obj_ref: name of the group
1857    :param token: token that identify the invitation
1858    """
1859    token = long(token)
1860    obj, ctx = get_generic_data(request, "Group", obj_ref)
1861    inv = models.Invitation.objects.get(token=token)
1862    if request.method == "POST":
1863        if inv.guest_asked:
1864            obj.send_invitation_to_owner(inv)
1865        else:
1866            obj.send_invitation_to_guest(inv)
1867    return HttpResponseRedirect("../../../users/")
1868
1869@handle_errors(undo="../..")
1870def import_csv_init(request, target="csv"):
1871    obj, ctx = get_generic_data(request)
1872    if request.method == "POST":
1873        csv_form = forms.CSVForm(request.POST, request.FILES)
1874        if csv_form.is_valid():
1875            f = request.FILES["file"]
1876            prefix = "openplmcsv" + request.user.username
1877            tmp = tempfile.NamedTemporaryFile(prefix=prefix, delete=False)
1878            for chunk in f.chunks():
1879                tmp.write(chunk)
1880            name = os.path.split(tmp.name)[1][len(prefix):]
1881            tmp.close()
1882            encoding = csv_form.cleaned_data["encoding"]
1883            return HttpResponseRedirect("/import/%s/%s/%s/" % (target, name,
1884                                        encoding))
1885    else:
1886        csv_form = forms.CSVForm()
1887    ctx["csv_form"] = csv_form
1888    ctx["step"] = 1
1889    ctx["target"] = target
1890    return r2r("import/csv.html", ctx, request)
1891
1892@handle_errors(undo="../..")
1893def import_csv_apply(request, target, filename, encoding):
1894    obj, ctx = get_generic_data(request)
1895    ctx["encoding_error"] = False
1896    ctx["io_error"] = False
1897    Importer = csvimport.IMPORTERS[target]
1898    Formset = forms.get_headers_formset(Importer)
1899    try:
1900        path = os.path.join(tempfile.gettempdir(),
1901                            "openplmcsv" + request.user.username + filename)
1902        with open(path, "rb") as csv_file:
1903            importer = Importer(csv_file, request.user, encoding)
1904            preview = importer.get_preview()
1905        if request.method == "POST":
1906            headers_formset = Formset(request.POST)
1907            if headers_formset.is_valid():
1908                headers = headers_formset.headers
1909                try:
1910                    with open(path, "rb") as csv_file:
1911                        importer = Importer(csv_file, request.user, encoding)
1912                        importer.import_csv(headers)
1913                except csvimport.CSVImportError as exc:
1914                    ctx["errors"] = exc.errors.iteritems()
1915                else:
1916                    os.remove(path)
1917                    return HttpResponseRedirect("/import/done/")
1918        else:
1919            initial = [{"header": header} for header in preview.guessed_headers]
1920            headers_formset = Formset(initial=initial)
1921        ctx.update({
1922            "preview" :  preview,
1923            "preview_data" : itertools.izip((f["header"] for f in headers_formset.forms),
1924                preview.headers, *preview.rows),
1925            "headers_formset" : headers_formset,
1926        })
1927    except UnicodeError:
1928        ctx["encoding_error"] = True
1929    except (IOError, csv.Error):
1930        ctx["io_error"] = True
1931    ctx["has_critical_error"] = ctx["io_error"] or ctx["encoding_error"] \
1932            or "errors" in ctx
1933    ctx["csv_form"] = forms.CSVForm(initial={"encoding" : encoding})
1934    ctx["step"] = 2
1935    ctx["target"] = target
1936    return r2r("import/csv.html", ctx, request)
1937
1938
1939@handle_errors
1940def import_csv_done(request):
1941    obj, ctx = get_generic_data(request)
1942    return r2r("import/done.html", ctx, request)
1943
1944class OpenPLMSearchView(SearchView):
1945
1946    def extra_context(self):
1947        extra = super(OpenPLMSearchView, self).extra_context()
1948        obj, ctx = get_generic_data(self.request, search=False)
1949        ctx["type"] = self.request.session["type"]
1950        ctx["object_type"] = "Search"
1951        extra.update(ctx)
1952        return extra
1953
1954    def get_query(self):
1955        query = super(OpenPLMSearchView, self).get_query() or "*"
1956        self.request.session["search_query"] = query
1957        return query
1958
1959    def get_results(self):
1960        results = super(OpenPLMSearchView, self).get_results()
1961        # update request.session so that the left panel displays
1962        # the same results
1963        self.request.session["results"] = results[:30]
1964        self.request.session["search_count"] = results.count()
1965        from haystack import site
1966        for r in self.request.session.get("results"):
1967            r.searchsite = site
1968        self.request.session.save()
1969        return results
1970
1971    @method_decorator(handle_errors)
1972    def __call__(self, request):
1973        return super(OpenPLMSearchView, self).__call__(request)
1974
1975def get_pages_num(total_pages, current_page,ctx):
1976    page = int(current_page)
1977    total = int(total_pages)
1978    if total < 5:
1979        ctx["pages"] = range(1,total)
1980    else:
1981        if page < total-1:
1982            if page > 2:
1983                ctx["pages"] = range(page-2, page+3)
1984            else:
1985                ctx["pages"] = range (1,6)
1986        else:
1987            ctx["pages"] = range(total-4, total+1)
1988
1989def display_pagination(r_GET,ctx, object_list, type="object"):
1990    sort = r_GET.get("sort", "recently-added")
1991    if sort== "name" :
1992        sort_critera = "username" if type == "user" else "name"
1993    else:
1994        sort_critera = "-date_joined" if type == "user" else "-ctime"
1995    object_list = object_list.order_by(sort_critera)
1996 
1997    paginator = Paginator(object_list, 24) # Show 24 objects per page
1998 
1999    page = r_GET.get('page', 1)
2000    get_pages_num(paginator.num_pages, page,ctx)
2001    try:
2002        objects = paginator.page(page)
2003    except PageNotAnInteger:
2004        # If page is not an integer, deliver first page.
2005         objects = paginator.page(1)
2006    except EmptyPage:
2007        # If page is out of range (e.g. 9999), deliver last page of results.
2008         objects = paginator.page(paginator.num_pages)
2009    ctx["thumbnails"] = {}
2010    ctx["num_files"] = {}
2011
2012    if type in ("object", "document"):
2013         ids = objects.object_list.values_list("id", flat=True)
2014         thumbnails = models.DocumentFile.objects.filter(deprecated=False,
2015                 document__in=ids, thumbnail__isnull=False)
2016         ctx["thumbnails"].update(dict(thumbnails.values_list("document", "thumbnail")))
2017         num_files = dict.fromkeys(ids, 0)
2018         for doc_id in models.DocumentFile.objects.filter(deprecated=False,
2019                 document__in=ids).values_list("document", flat=True):
2020             num_files[doc_id] += 1
2021         ctx["num_files"] = num_files
2022    ctx.update({
2023         "objects" : objects,
2024         "sort" : sort,
2025    })
2026
2027
2028@secure_required
2029def browse(request, type="object"):
2030    if request.user.is_authenticated():
2031        # only authenticated users can see all groups and users
2032        obj, ctx = get_generic_data(request, search=False)
2033        cls = {
2034            "object" : models.PLMObject,
2035            "part" : models.Part,
2036            "document" : models.Document,
2037            "group" : models.GroupInfo,
2038            "user" : User,
2039        }[type]
2040        object_list = cls.objects.all()
2041        # this only relevant for authenticated users
2042        ctx["state"] = state = request.GET.get("state", "all")
2043        if type in ("object", "part", "document"):
2044            ctx["plmobjects"] = True
2045            if state == "official":
2046                object_list = object_list.\
2047                        exclude(lifecycle=models.get_cancelled_lifecycle()).\
2048                        filter(state=F("lifecycle__official_state"))
2049            elif state == "published":
2050                object_list = object_list.filter(published=True)
2051        else:
2052            ctx["plmobjects"] = False
2053    else:
2054        cls = {
2055            "object" : models.PLMObject,
2056            "part" : models.Part,
2057            "document" : models.Document,
2058        }[type]
2059        ctx = init_ctx("-", "-", "-")
2060        ctx.update({
2061            'is_readable' : True,
2062            'plmobjects' : True,
2063            'object_menu' : [],
2064            'navigation_history' : [],
2065        })
2066        object_list = cls.objects.filter(published=True)
2067
2068    display_pagination(request.GET,ctx, object_list, type=type)
2069   
2070    ctx.update({
2071        "object_type" : _("Browse"),
2072        "type" : type,
2073    })
2074    return r2r("browse.html", ctx, request)
2075
2076def public(request, obj_type, obj_ref, obj_revi):
2077    """
2078    .. versionadded:: 1.1
2079
2080    Public view of the given object, this view is accessible to anonymous
2081    users. The object must be a published part or document.
2082
2083    Redirects to the login page if the object is not published and the user
2084    is not authenticated.
2085
2086    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/public/`
2087
2088    .. include:: views_params.txt
2089
2090    **Template:**
2091   
2092    :file:`public.html`
2093
2094    **Context:**
2095
2096    ``RequestContext``
2097
2098    ``obj``
2099        the controller
2100   
2101    ``object_attributes``
2102        list of tuples(verbose attribute name, value)
2103
2104    ``revisions``
2105        list of published related revisions
2106
2107    ``attached``
2108        list of published attached documents and parts
2109    """
2110    # do not call get_generic_data to avoid the overhead due
2111    # to a possible search and the update of the navigation history
2112    obj = get_obj(obj_type, obj_ref, obj_revi, request.user)
2113    if not (obj.is_part or obj.is_document):
2114        raise Http404
2115    if not obj.published and request.user.is_anonymous():
2116        return redirect_to_login(request.get_full_path())
2117    ctx = init_ctx(obj_type, obj_ref, obj_revi)
2118    attrs = obj.published_attributes
2119    object_attributes = []
2120    for attr in attrs:
2121        item = obj.get_verbose_name(attr)
2122        object_attributes.append((item, getattr(obj, attr)))
2123    object_attributes.insert(4, (obj.get_verbose_name("state"), obj.state.name))
2124    revisions = [rev for rev in obj.get_all_revisions() if rev.published]
2125    if obj.is_part:
2126        attached = [d.document for d in obj.get_attached_documents()
2127            if d.document.published]
2128    else:
2129        attached = [d.part for d in obj.get_attached_parts() if d.part.published]
2130
2131    ctx.update({
2132        # a published object is always readable
2133        'is_readable' : True,
2134        # disable the menu and the navigation_history
2135        'object_menu' : [],
2136        'navigation_history' : [],
2137        'obj' : obj,
2138        'object_attributes': object_attributes,
2139        'revisions' : revisions,
2140        'attached' : attached,
2141    })
2142
2143    return r2r("public.html", ctx, request)
2144
Note: See TracBrowser for help on using the repository browser.