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

Revision 1190, 81.4 KB checked in by zali, 7 years ago (diff)

Templates groups/object and browse : pagination display 5 pages and the first or the last page

  • 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
1575def public_download(request, docfile_id, filename=""):
1576    """
1577    View to download a published document file.
1578   
1579    :param request: :class:`django.http.QueryDict`
1580    :param docfile_id: :attr:`.DocumentFile.id`
1581    :type docfile_id: str
1582    :return: a :class:`django.http.HttpResponse`
1583    """
1584    doc_file = models.DocumentFile.objects.get(id=docfile_id)
1585    ctrl = get_obj_by_id(int(doc_file.document.id), request.user)
1586    if not ctrl.published:
1587        return HttpResponseForbidden()
1588    return serve(ctrl, doc_file, filename)
1589
1590
1591def serve(ctrl, doc_file, filename):
1592    name = doc_file.filename.encode("utf-8", "ignore")
1593    mimetype = guess_type(name, False)[0]
1594    if not mimetype:
1595        mimetype = 'application/octet-stream'
1596    f, size = ctrl.get_content_and_size(doc_file)
1597    response = HttpResponse(f, mimetype=mimetype)
1598    response["Content-Length"] = size
1599    if not filename:
1600        response['Content-Disposition'] = 'attachment; filename="%s"' % name
1601    return response
1602
1603def get_cad_files(part):
1604    """
1605    Returns an iterable of all :class:`.DocumentFile` related
1606    to *part* that contains a CAD file. It retrieves all non deprecated
1607    files of all documents parts to *part* and its children and
1608    filters these files according to their extension (see :meth:`.is_cad_file`).
1609    """
1610    children = part.get_children(-1, related=("child",))
1611    children_ids = set(c.link.child_id for c in children)
1612    children_ids.add(part.id)
1613    links = models.DocumentPartLink.objects.filter(part__in=children_ids)
1614    docs = links.values_list("document", flat=True)
1615    d_o_u = "document__owner__username"
1616    files = models.DocumentFile.objects.filter(deprecated=False,
1617                document__in=set(docs))
1618    # XXX : maybe its faster to build a complex query than retrieving
1619    # each file and testing their extension
1620    return (df for df in files.select_related(d_o_u) if is_cad_file(df.filename))
1621
1622
1623@handle_errors
1624def download_archive(request, obj_type, obj_ref, obj_revi):
1625    """
1626    View to download all files from a document/part.
1627
1628    .. include:: views_params.txt
1629    """
1630
1631    obj = get_obj(obj_type, obj_ref, obj_revi, request.user)
1632    obj.check_readable()
1633   
1634    d_o_u = "document__owner__username"
1635    if obj.is_document:
1636        files = obj.files.select_related(d_o_u)
1637    elif obj.is_part and "cad" in request.GET:
1638        files = get_cad_files(obj)
1639    elif obj.is_part:
1640        links = obj.get_attached_documents()
1641        docs = (link.document for link in links)
1642        files = itertools.chain(*(doc.files.select_related(d_o_u)
1643            for doc in docs))
1644    else:
1645        return HttpResponseForbidden()
1646
1647    form = forms.ArchiveForm(request.GET)
1648    if form.is_valid():
1649        format = form.cleaned_data["format"]
1650        name = "%s_%s.%s" % (obj_ref, obj_revi, format)
1651        mimetype = guess_type(name, False)[0]
1652        if not mimetype:
1653            mimetype = 'application/octet-stream'
1654        content = generate_archive(files, format)
1655        response = HttpResponse(content, mimetype=mimetype)
1656        #response["Content-Length"] = size
1657        response['Content-Disposition'] = 'attachment; filename="%s"' % name
1658        return response
1659    return HttpResponseForbidden()
1660##########################################################################################
1661@handle_errors
1662def checkout_file(request, obj_type, obj_ref, obj_revi, docfile_id):
1663    """
1664    Manage html page for the files (:class:`DocumentFile`) checkout from the selected object.
1665    It locks the :class:`DocumentFile` and, after, calls :func:`.views.download`
1666   
1667    .. include:: views_params.txt
1668    :param docfile_id: :attr:`.DocumentFile.id`
1669    :type docfile_id_value: str
1670    """
1671    obj = get_obj(obj_type, obj_ref, obj_revi, request.user)
1672    doc_file = models.DocumentFile.objects.get(id=docfile_id)
1673    obj.lock(doc_file)
1674    return download(request, docfile_id)
1675
1676##########################################################################################
1677###                     Manage html pages for navigate function                        ###
1678##########################################################################################   
1679@handle_errors
1680def navigate(request, obj_type, obj_ref, obj_revi):
1681    """
1682    Manage html page which displays a graphical picture the different links
1683    between :class:`~django.contrib.auth.models.User` and  :class:`.models.PLMObject`.
1684    This function uses Graphviz (http://graphviz.org/).
1685    Some filters let user defines which type of links he/she wants to display.
1686    It computes a context dictionary based on
1687   
1688    .. include:: views_params.txt
1689    """
1690    ctx = get_navigate_data(request, obj_type, obj_ref, obj_revi)
1691    ctx["edges"] = simplejson.dumps(ctx["edges"])
1692    return r2r('navigate.html', ctx, request)
1693
1694@handle_errors
1695def display_users(request, obj_ref):
1696    obj, ctx = get_generic_data(request, "Group", obj_ref)
1697    if request.method == "POST":
1698        formset = forms.get_user_formset(obj, request.POST)
1699        if formset.is_valid():
1700            obj.update_users(formset)
1701            return HttpResponseRedirect(".")
1702    else:
1703        formset = forms.get_user_formset(obj)
1704    ctx["user_formset"] = formset
1705    ctx["pending_invitations"] = obj.invitation_set.filter(
1706            state=models.Invitation.PENDING)
1707    ctx['current_page'] = 'users'
1708    ctx['in_group'] = bool(request.user.groups.filter(id=obj.id))
1709    return r2r("groups/users.html", ctx, request)
1710
1711@handle_errors
1712def group_add_user(request, obj_ref):
1713    """
1714    View of the *Add user* page of a group.
1715
1716    """
1717
1718    obj, ctx = get_generic_data(request, "Group", obj_ref)
1719    if request.method == "POST":
1720        form = forms.SelectUserForm(request.POST)
1721        if form.is_valid():
1722            obj.add_user(User.objects.get(username=form.cleaned_data["username"]))
1723            return HttpResponseRedirect("..")
1724    else:
1725        form = forms.SelectUserForm()
1726    ctx["add_user_form"] = form
1727    ctx['current_page'] = 'users'
1728    ctx['link_creation'] = True
1729    return r2r("groups/add_user.html", ctx, request)
1730
1731@handle_errors
1732def group_ask_to_join(request, obj_ref):
1733    obj, ctx = get_generic_data(request, "Group", obj_ref)
1734    if request.method == "POST":
1735        obj.ask_to_join()
1736        return HttpResponseRedirect("..")
1737    else:
1738        form = forms.SelectUserForm()
1739    ctx["ask_form"] = ""
1740    ctx['current_page'] = 'users'
1741    ctx['in_group'] = bool(request.user.groups.filter(id=obj.id))
1742    return r2r("groups/ask_to_join.html", ctx, request)
1743
1744@handle_errors
1745def display_groups(request, obj_ref):
1746    """
1747    View of the *groups* page of an user.
1748
1749    """
1750
1751    obj, ctx = get_generic_data(request, "User", obj_ref)
1752    ctx["groups"] = models.GroupInfo.objects.filter(id__in=obj.groups.all())\
1753            .order_by("name")
1754
1755    ctx['current_page'] = 'groups'
1756    return r2r("users/groups.html", ctx, request)
1757
1758@handle_errors
1759def sponsor(request, obj_ref):
1760    """
1761    View of the *sponsor* page.
1762    """
1763    obj, ctx = get_generic_data(request, "User", obj_ref)
1764
1765    if request.method == "POST":
1766        form = forms.SponsorForm(request.POST)
1767        if form.is_valid():
1768            new_user = form.save()
1769            new_user.get_profile().language = form.cleaned_data["language"]
1770            obj.sponsor(new_user)
1771            return HttpResponseRedirect("..")
1772    else:
1773        form = forms.SponsorForm(initial={"sponsor":obj.id, "language":obj.language}, sponsor=obj.id)
1774    ctx["sponsor_form"] = form
1775    ctx['current_page'] = 'delegation'
1776    return r2r("users/sponsor.html", ctx, request)
1777
1778@handle_errors
1779def create_user(request):
1780    url = request.user.get_profile().plmobject_url + "delegation/sponsor/"
1781    return HttpResponseRedirect(url)
1782register_creation_view(User, create_user)
1783
1784@handle_errors
1785def sponsor_resend_mail(request, obj_ref):
1786    obj, ctx = get_generic_data(request, "User", obj_ref)
1787    if request.method == "POST":
1788        try:
1789            link_id = request.POST["link_id"]
1790            link = models.DelegationLink.objects.get(id=int(link_id))
1791            obj.resend_sponsor_mail(link.delegatee)
1792        except (KeyError, ValueError, ControllerError) as e:
1793            return HttpResponseForbidden()
1794    return HttpResponseRedirect("../../")
1795
1796@handle_errors
1797def display_plmobjects(request, obj_ref):
1798    """
1799    View of the *objects* page of a group.
1800    """
1801   
1802    obj, ctx = get_generic_data(request, "Group", obj_ref)
1803   
1804    #ctx["objects"] = obj.plmobject_group.order_by("type", "reference", "revision")
1805    objects = obj.plmobject_group.order_by("type", "reference", "revision")
1806
1807    display_pagination(request.GET,ctx, objects,"object")
1808
1809    ctx['current_page'] = 'objects'
1810    return r2r("groups/objects.html", ctx, request)
1811
1812@handle_errors(undo="../../../users/")
1813def accept_invitation(request, obj_ref, token):
1814    token = long(token)
1815    obj, ctx = get_generic_data(request, "Group", obj_ref)
1816    inv = models.Invitation.objects.get(token=token)
1817    if request.method == "POST":
1818        form = forms.InvitationForm(request.POST)
1819        if form.is_valid() and inv == form.cleaned_data["invitation"]:
1820            obj.accept_invitation(inv)
1821            return HttpResponseRedirect("../../../users/")
1822    else:
1823        form = forms.InvitationForm(initial={"invitation" : inv})
1824    ctx["invitation_form"] = form
1825    ctx['current_page'] = 'users'
1826    ctx["invitation"] = inv
1827    return r2r("groups/accept_invitation.html", ctx, request)
1828
1829 
1830@handle_errors(undo="../../../users/")
1831def refuse_invitation(request, obj_ref, token):
1832    token = long(token)
1833    obj, ctx = get_generic_data(request, "Group", obj_ref)
1834    inv = models.Invitation.objects.get(token=token)
1835    if request.method == "POST":
1836        form = forms.InvitationForm(request.POST)
1837        if form.is_valid() and inv == form.cleaned_data["invitation"]:
1838            obj.refuse_invitation(inv)
1839            return HttpResponseRedirect("../../../users/")
1840    else:
1841        form = forms.InvitationForm(initial={"invitation" : inv})
1842    ctx["invitation_form"] = form
1843    ctx["invitation"] = inv
1844    ctx['current_page'] = 'users'
1845    return r2r("groups/refuse_invitation.html", ctx, request)
1846
1847@handle_errors
1848def send_invitation(request, obj_ref, token):
1849    """
1850    Views to (re)send an invitation.
1851
1852    :param obj_ref: name of the group
1853    :param token: token that identify the invitation
1854    """
1855    token = long(token)
1856    obj, ctx = get_generic_data(request, "Group", obj_ref)
1857    inv = models.Invitation.objects.get(token=token)
1858    if request.method == "POST":
1859        if inv.guest_asked:
1860            obj.send_invitation_to_owner(inv)
1861        else:
1862            obj.send_invitation_to_guest(inv)
1863    return HttpResponseRedirect("../../../users/")
1864
1865@handle_errors(undo="../..")
1866def import_csv_init(request, target="csv"):
1867    obj, ctx = get_generic_data(request)
1868    if request.method == "POST":
1869        csv_form = forms.CSVForm(request.POST, request.FILES)
1870        if csv_form.is_valid():
1871            f = request.FILES["file"]
1872            prefix = "openplmcsv" + request.user.username
1873            tmp = tempfile.NamedTemporaryFile(prefix=prefix, delete=False)
1874            for chunk in f.chunks():
1875                tmp.write(chunk)
1876            name = os.path.split(tmp.name)[1][len(prefix):]
1877            tmp.close()
1878            encoding = csv_form.cleaned_data["encoding"]
1879            return HttpResponseRedirect("/import/%s/%s/%s/" % (target, name,
1880                                        encoding))
1881    else:
1882        csv_form = forms.CSVForm()
1883    ctx["csv_form"] = csv_form
1884    ctx["step"] = 1
1885    ctx["target"] = target
1886    return r2r("import/csv.html", ctx, request)
1887
1888@handle_errors(undo="../..")
1889def import_csv_apply(request, target, filename, encoding):
1890    obj, ctx = get_generic_data(request)
1891    ctx["encoding_error"] = False
1892    ctx["io_error"] = False
1893    Importer = csvimport.IMPORTERS[target]
1894    Formset = forms.get_headers_formset(Importer)
1895    try:
1896        path = os.path.join(tempfile.gettempdir(),
1897                            "openplmcsv" + request.user.username + filename)
1898        with open(path, "rb") as csv_file:
1899            importer = Importer(csv_file, request.user, encoding)
1900            preview = importer.get_preview()
1901        if request.method == "POST":
1902            headers_formset = Formset(request.POST)
1903            if headers_formset.is_valid():
1904                headers = headers_formset.headers
1905                try:
1906                    with open(path, "rb") as csv_file:
1907                        importer = Importer(csv_file, request.user, encoding)
1908                        importer.import_csv(headers)
1909                except csvimport.CSVImportError as exc:
1910                    ctx["errors"] = exc.errors.iteritems()
1911                else:
1912                    os.remove(path)
1913                    return HttpResponseRedirect("/import/done/")
1914        else:
1915            initial = [{"header": header} for header in preview.guessed_headers]
1916            headers_formset = Formset(initial=initial)
1917        ctx.update({
1918            "preview" :  preview,
1919            "preview_data" : itertools.izip((f["header"] for f in headers_formset.forms),
1920                preview.headers, *preview.rows),
1921            "headers_formset" : headers_formset,
1922        })
1923    except UnicodeError:
1924        ctx["encoding_error"] = True
1925    except (IOError, csv.Error):
1926        ctx["io_error"] = True
1927    ctx["has_critical_error"] = ctx["io_error"] or ctx["encoding_error"] \
1928            or "errors" in ctx
1929    ctx["csv_form"] = forms.CSVForm(initial={"encoding" : encoding})
1930    ctx["step"] = 2
1931    ctx["target"] = target
1932    return r2r("import/csv.html", ctx, request)
1933
1934
1935@handle_errors
1936def import_csv_done(request):
1937    obj, ctx = get_generic_data(request)
1938    return r2r("import/done.html", ctx, request)
1939
1940class OpenPLMSearchView(SearchView):
1941
1942    def extra_context(self):
1943        extra = super(OpenPLMSearchView, self).extra_context()
1944        obj, ctx = get_generic_data(self.request, search=False)
1945        ctx["type"] = self.request.session["type"]
1946        ctx["object_type"] = "Search"
1947        extra.update(ctx)
1948        return extra
1949
1950    def get_query(self):
1951        query = super(OpenPLMSearchView, self).get_query() or "*"
1952        self.request.session["search_query"] = query
1953        return query
1954
1955    def get_results(self):
1956        results = super(OpenPLMSearchView, self).get_results()
1957        # update request.session so that the left panel displays
1958        # the same results
1959        self.request.session["results"] = results[:30]
1960        self.request.session["search_count"] = results.count()
1961        from haystack import site
1962        for r in self.request.session.get("results"):
1963            r.searchsite = site
1964        self.request.session.save()
1965        return results
1966
1967    @method_decorator(handle_errors)
1968    def __call__(self, request):
1969        return super(OpenPLMSearchView, self).__call__(request)
1970
1971def get_pages_num(total_pages, current_page,ctx):
1972    page = int(current_page)
1973    total = int(total_pages)
1974    if total < 5:
1975        ctx["pages"] = range(1,total)
1976    else:
1977        if page < total-1:
1978            if page > 2:
1979                ctx["pages"] = range(page-2, page+3)
1980            else:
1981                ctx["pages"] = range (1,6)
1982        else:
1983            ctx["pages"] = range(total-4, total+1)
1984
1985def display_pagination(r_GET,ctx, object_list, type="object"):
1986    sort = r_GET.get("sort", "recently-added")
1987    if sort== "name" :
1988        sort_critera = "username" if type == "user" else "name"
1989    else:
1990        sort_critera = "-date_joined" if type == "user" else "-ctime"
1991    object_list = object_list.order_by(sort_critera)
1992 
1993    paginator = Paginator(object_list, 24) # Show 24 objects per page
1994 
1995    page = r_GET.get('page', 1)
1996    get_pages_num(paginator.num_pages, page,ctx)
1997    try:
1998        objects = paginator.page(page)
1999    except PageNotAnInteger:
2000        # If page is not an integer, deliver first page.
2001         objects = paginator.page(1)
2002    except EmptyPage:
2003        # If page is out of range (e.g. 9999), deliver last page of results.
2004         objects = paginator.page(paginator.num_pages)
2005    ctx["thumbnails"] = {}
2006    ctx["num_files"] = {}
2007
2008    if type in ("object", "document"):
2009         ids = objects.object_list.values_list("id", flat=True)
2010         thumbnails = models.DocumentFile.objects.filter(deprecated=False,
2011                 document__in=ids, thumbnail__isnull=False)
2012         ctx["thumbnails"].update(dict(thumbnails.values_list("document", "thumbnail")))
2013         num_files = dict.fromkeys(ids, 0)
2014         for doc_id in models.DocumentFile.objects.filter(deprecated=False,
2015                 document__in=ids).values_list("document", flat=True):
2016             num_files[doc_id] += 1
2017         ctx["num_files"] = num_files
2018    ctx.update({
2019         "objects" : objects,
2020         "sort" : sort,
2021    })
2022
2023
2024@secure_required
2025def browse(request, type="object"):
2026    if request.user.is_authenticated():
2027        # only authenticated users can see all groups and users
2028        obj, ctx = get_generic_data(request, search=False)
2029        cls = {
2030            "object" : models.PLMObject,
2031            "part" : models.Part,
2032            "document" : models.Document,
2033            "group" : models.GroupInfo,
2034            "user" : User,
2035        }[type]
2036        object_list = cls.objects.all()
2037        # this only relevant for authenticated users
2038        ctx["state"] = state = request.GET.get("state", "all")
2039        if type in ("object", "part", "document"):
2040            ctx["plmobjects"] = True
2041            if state == "official":
2042                object_list = object_list.\
2043                        exclude(lifecycle=models.get_cancelled_lifecycle()).\
2044                        filter(state=F("lifecycle__official_state"))
2045            elif state == "published":
2046                object_list = object_list.filter(published=True)
2047        else:
2048            ctx["plmobjects"] = False
2049    else:
2050        cls = {
2051            "object" : models.PLMObject,
2052            "part" : models.Part,
2053            "document" : models.Document,
2054        }[type]
2055        ctx = init_ctx("-", "-", "-")
2056        ctx.update({
2057            'is_readable' : True,
2058            'plmobjects' : True,
2059            'object_menu' : [],
2060            'navigation_history' : [],
2061        })
2062        object_list = cls.objects.filter(published=True)
2063
2064    display_pagination(request.GET,ctx, object_list, type=type)
2065   
2066    ctx.update({
2067        "object_type" : _("Browse"),
2068        "type" : type,
2069    })
2070    return r2r("browse.html", ctx, request)
2071
2072def public(request, obj_type, obj_ref, obj_revi):
2073    """
2074    .. versionadded:: 1.1
2075
2076    Public view of the given object, this view is accessible to anonymous
2077    users. The object must be a published part or document.
2078
2079    Redirects to the login page if the object is not published and the user
2080    is not authenticated.
2081
2082    :url: :samp:`/object/{obj_type}/{obj_ref}/{obj_revi}/public/`
2083
2084    .. include:: views_params.txt
2085
2086    **Template:**
2087   
2088    :file:`public.html`
2089
2090    **Context:**
2091
2092    ``RequestContext``
2093
2094    ``obj``
2095        the controller
2096   
2097    ``object_attributes``
2098        list of tuples(verbose attribute name, value)
2099
2100    ``revisions``
2101        list of published related revisions
2102
2103    ``attached``
2104        list of published attached documents and parts
2105    """
2106    # do not call get_generic_data to avoid the overhead due
2107    # to a possible search and the update of the navigation history
2108    obj = get_obj(obj_type, obj_ref, obj_revi, request.user)
2109    if not (obj.is_part or obj.is_document):
2110        raise Http404
2111    if not obj.published and request.user.is_anonymous():
2112        return redirect_to_login(request.get_full_path())
2113    ctx = init_ctx(obj_type, obj_ref, obj_revi)
2114    attrs = obj.published_attributes
2115    object_attributes = []
2116    for attr in attrs:
2117        item = obj.get_verbose_name(attr)
2118        object_attributes.append((item, getattr(obj, attr)))
2119    object_attributes.insert(4, (obj.get_verbose_name("state"), obj.state.name))
2120    revisions = [rev for rev in obj.get_all_revisions() if rev.published]
2121    if obj.is_part:
2122        attached = [d.document for d in obj.get_attached_documents()
2123            if d.document.published]
2124    else:
2125        attached = [d.part for d in obj.get_attached_parts() if d.part.published]
2126
2127    ctx.update({
2128        # a published object is always readable
2129        'is_readable' : True,
2130        # disable the menu and the navigation_history
2131        'object_menu' : [],
2132        'navigation_history' : [],
2133        'obj' : obj,
2134        'object_attributes': object_attributes,
2135        'revisions' : revisions,
2136        'attached' : attached,
2137    })
2138
2139    return r2r("public.html", ctx, request)
2140
Note: See TracBrowser for help on using the repository browser.