Changeset 662 in main for branches


Ignore:
Timestamp:
01/18/12 11:59:51 (8 years ago)
Author:
pcosquer
Message:

3D branch: merge changes from trunk (rev [661])

Location:
branches/3D/openPLM
Files:
41 edited
17 copied

Legend:

Unmodified
Added
Removed
  • branches/3D/openPLM/help/fr/search.rst

    r595 r662  
    77    #. sélectionner un type ; 
    88    #. entrer une requête ; 
    9     #. cliquer sur le bouton rechercher ; 
     9    #. cliquer sur le bouton rechercher. 
    1010 
    1111Exemples de requêtes : 
  • branches/3D/openPLM/media/css/help.css

    r595 r662  
    187187 
    188188ol.simple, ul.simple { 
    189   margin-bottom: 1em } 
     189  padding-bottom: 1em } 
    190190 
    191191ol.arabic { 
  • branches/3D/openPLM/media/css/openplm.css

    r595 r662  
    33/* *************************************************************************************** */ 
    44 
    5 * { 
    6     padding: 0px; 
    7     margin: 0px; 
     5/** {*/ 
     6    /*padding: 0px;*/ 
     7    /*margin: 0px;*/ 
     8    /*color: #eee;*/ 
     9/*}*/ 
     10 
     11/* http://meyerweb.com/eric/tools/css/reset/  
     12   v2.0 | 20110126 
     13   License: none (public domain) 
     14*/ 
     15 
     16html, body, div, span, applet, object, iframe, 
     17h1, h2, h3, h4, h5, h6, p, blockquote, pre, 
     18a, abbr, acronym, address, big, cite, code, 
     19del, dfn, em, img, ins, kbd, q, s, samp, 
     20small, strike, strong, sub, sup, tt, var, 
     21b, u, i, center, 
     22dl, dt, dd, ol, ul, li, 
     23fieldset, form, label, legend, 
     24table, caption, tbody, tfoot, thead, tr, th, td, 
     25article, aside, canvas, details, embed,  
     26figure, figcaption, footer, header, hgroup,  
     27menu, nav, output, ruby, section, summary, 
     28time, mark, audio, video { 
     29        margin: 0; 
     30        padding: 0; 
     31        border: 0; 
     32        /*font-size: 100%;*/ 
     33        /*font: inherit;*/ 
     34        vertical-align: baseline; 
    835    color: #eee; 
    936} 
     37/* HTML5 display-role reset for older browsers */ 
     38article, aside, details, figcaption, figure,  
     39footer, header, hgroup, menu, nav, section { 
     40        display: block; 
     41} 
     42body { 
     43        line-height: 1; 
     44} 
     45ol, ul { 
     46        list-style: none; 
     47} 
     48blockquote, q { 
     49        quotes: none; 
     50} 
     51blockquote:before, blockquote:after, 
     52q:before, q:after { 
     53        content: ''; 
     54        content: none; 
     55} 
     56table { 
     57        border-collapse: collapse; 
     58        border-spacing: 0; 
     59} 
     60 
     61/*********************/ 
    1062 
    1163body  
     
    364416    width: 100%; 
    365417    margin-top: 10px; 
    366     border-spacing: 3px; 
     418    border-spacing: 2px; 
     419    border-collapse: separate; 
    367420} 
    368421 
     
    498551} 
    499552 
    500  
     553li.Result div.summary em { 
     554    background-color: #fffcb6; 
     555    color: black; 
     556    font-style: normal; 
     557} 
    501558/** input **/ 
    502559input[type=text], 
     
    606663 
    607664#help-dialog h3 ~ * { 
    608     background-color: #343434; 
     665    padding-left: 1em; 
     666} 
     667#help-dialog h3 + * { 
     668    padding-top: 1em; 
    609669} 
    610670 
     
    622682} 
    623683 
    624 #help-dialog .section * { 
     684#help-dialog .section *:not(h3) { 
    625685    font-size: 14px; 
    626 } 
    627  
     686    background-color: #343434; 
     687} 
     688 
     689tt.docutils.literal { 
     690    color: black; 
     691    background-color: #fffcb6 !important; 
     692} 
    628693 
    629694/* lifecycle */ 
     
    642707} 
    643708 
     709form.archive_form { 
     710    float: right; 
     711} 
     712 
     713form.archive_form > * { 
     714    display: inline; 
     715    padding-left: 0.5em; 
     716} 
     717 
     718ul.errorlist { 
     719    display: block; 
     720    padding-left: 1em; 
     721} 
     722ul.errorlist li { 
     723    display: list-item; 
     724    list-style-type: disc; 
     725    margin-left: 1em; 
     726} 
     727 
  • branches/3D/openPLM/media/css/uniform/uniform.openplm.css

    r595 r662  
    4141.uploader *, 
    4242.button *{ 
    43   margin: 0; 
    44   padding: 0; 
     43  /*margin: 0;*/ 
     44  /*padding: 0;*/ 
    4545} 
    4646 
  • branches/3D/openPLM/media/js/navigate.js

    r595 r662  
    6767        var nw = $("#Navigate").innerWidth(); 
    6868        var nh = $("#Navigate").innerHeight(); 
    69         console.log((nw / 2 - data.center_x) + "px"); 
    7069        divNav.css({left: (nw / 2 - data.center_x) + "px", 
    7170                    top: (nh / 2 - data.center_y) + "px"}); 
     
    284283        } 
    285284        $("#FilterButton").button().click(function () { 
     285            $("#Navigate").showLoading(); 
    286286            $.post(uri, 
    287287                $("#FilterNav").find("form").serialize(), 
    288288                function (data) { 
    289289                    update_nav(null, data); 
     290                    $("#Navigate").hideLoading(); 
    290291                }); 
    291292            } ); 
  • branches/3D/openPLM/plmapp/base_views.py

    r595 r662  
    281281    else: 
    282282        selected_object = get_obj(type_, reference, revision, request_dict.user) 
    283     # Defines a variable for background color selection 
    284     if isinstance(selected_object, UserController): 
    285         class_for_div="ActiveBox4User" 
    286     elif isinstance(selected_object, DocumentController): 
    287         class_for_div="ActiveBox4Doc" 
    288     else: 
    289         class_for_div="ActiveBox4Part" 
    290283    qset = [] 
    291284    # Builds, update and treat Search form 
     
    334327                'link_creation' : False, 
    335328                'attach' : (selected_object, False), 
    336                 'class4div' : class_for_div, 
    337329                'obj' : selected_object, 
    338330              }) 
     
    361353 
    362354    has_session = any(field in request.session for field in FilterForm.base_fields) 
     355    initial = dict((k, v.initial) for k, v in FilterForm.base_fields.items()) 
    363356    if request.method == 'POST' and request.POST: 
    364357        form = FilterForm(request.POST) 
     
    367360    elif has_session: 
    368361        request.session.update(dict(doc_parts = "")) 
    369         form = FilterForm(request.session) 
    370     else: 
    371         initial = dict((k, v.initial) for k, v in FilterForm.base_fields.items()) 
     362        initial.update(request.session) 
     363        form = FilterForm(initial) 
     364    else: 
    372365        form = FilterForm(initial) 
    373366        request.session.update(initial) 
  • branches/3D/openPLM/plmapp/controllers/part.py

    r595 r662  
    3131 
    3232import openPLM.plmapp.models as models 
     33from openPLM.plmapp.units import DEFAULT_UNIT 
    3334from openPLM.plmapp.controllers.plmobject import PLMObjectController 
    3435from openPLM.plmapp.controllers.base import get_controller 
     
    5455         
    5556        :raises: :exc:`ValueError` if *child* is already a child or a parent. 
    56         :raises: :exc:`ValueError` if *quantity* or *order* are negative. 
    5757        :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of 
    5858            :attr:`object`.     
     
    8888        return can_add 
    8989 
    90     def add_child(self, child, quantity, order): 
     90    def add_child(self, child, quantity, order, unit=DEFAULT_UNIT): 
    9191        """ 
    9292        Adds *child* to *self*. 
     
    9898        :param order: order 
    9999        :type order: positive int 
     100        :param unit: a valid unit 
    100101         
    101102        :raises: :exc:`ValueError` if *child* is already a child or a parent. 
     
    120121        link.quantity = quantity 
    121122        link.order = order 
     123        link.unit = unit 
    122124        link.save() 
    123125        # records creation in history 
     
    147149        self._save_histo("Delete - %s" % link.ACTION_NAME, "child : %s" % child) 
    148150 
    149     def modify_child(self, child, new_quantity, new_order): 
     151    def modify_child(self, child, new_quantity, new_order, new_unit): 
    150152        """ 
    151153        Modifies information about *child*. 
     
    171173        link = models.ParentChildLink.objects.get(parent=self.object, 
    172174                                                  child=child, end_time=None) 
    173         if link.quantity == new_quantity and link.order == new_order: 
     175        if (link.quantity == new_quantity and link.order == new_order and 
     176            link.unit == new_unit): 
    174177            # do not make an update if it is useless 
    175178            return 
     
    178181        # make a new link 
    179182        link2 = models.ParentChildLink(parent=self.object, child=child, 
    180                                        quantity=new_quantity, order=new_order) 
     183                                       quantity=new_quantity, order=new_order, 
     184                                       unit=new_unit) 
    181185        details = "" 
    182186        if link.quantity != new_quantity: 
     
    184188        if link.order != new_order: 
    185189            details += "order changes from %d to %d" % (link.order, new_order) 
     190        if link.unit != new_unit: 
     191            details += "unit changes from %s to %s" % (link.unit, new_unit) 
    186192        self._save_histo("Modify - %s" % link.ACTION_NAME, details) 
    187193        link2.save(force_insert=True) 
     
    254260                    quantity = form.cleaned_data["quantity"] 
    255261                    order = form.cleaned_data["order"] 
    256                     self.modify_child(child, quantity, order) 
     262                    unit = form.cleaned_data["unit"] 
     263                    self.modify_child(child, quantity, order, unit) 
    257264 
    258265    def revise(self, new_revision): 
     
    260267        new_controller = super(PartController, self).revise(new_revision) 
    261268        for level, link in self.get_children(1): 
    262             new_controller.add_child(link.child, link.quantity, link.order) 
     269            new_controller.add_child(link.child, link.quantity, link.order, 
     270                    link.unit) 
    263271        return new_controller 
    264272 
     
    322330        else: 
    323331            get_controller(document.type)(document, self._user).check_readable() 
    324             type(self)(document, self._user).check_readable() 
    325332        if self.is_document_attached(document): 
    326333            raise ValueError("Document is already attached.") 
  • branches/3D/openPLM/plmapp/controllers/plmobject.py

    r595 r662  
    118118        except models.DelegationLink.DoesNotExist: 
    119119            sponsor = user 
    120         for i in range(len(obj.lifecycle.to_states_list()) - 1): 
     120        for i in range(obj.lifecycle.nb_states - 1): 
    121121            models.PLMObjectUserLink.objects.create(plmobject=obj, user=sponsor, 
    122122                                                    role=level_to_sign_str(i)) 
     
    214214 
    215215    def has_permission(self, role): 
    216         users = [self._user.id] 
    217         users.extend(models.DelegationLink.get_delegators(self._user, role)) 
     216        if role == models.ROLE_OWNER and self.owner == self._user: 
     217            return True 
     218        if self.plmobjectuserlink_plmobject.filter(user=self._user, role=role).exists(): 
     219            return True 
     220 
     221        users = models.DelegationLink.get_delegators(self._user, role) 
    218222        qset = self.plmobjectuserlink_plmobject.filter(user__in=users, 
    219223                                                          role=role) 
     
    227231            raise PermissionError("The object is not editable") 
    228232 
    229     @permission_required(role="owner") 
    230233    def revise(self, new_revision): 
    231234        u""" 
    232         Makes a new revision : duplicates :attr:`object`. The duplicated  
     235        Makes a new revision: duplicates :attr:`object`. The duplicated  
    233236        object's revision is *new_revision*. 
    234237 
    235238        Returns a controller of the new object. 
    236239        """ 
    237          
     240        self.check_readable()  
    238241        if not new_revision or new_revision == self.revision or \ 
    239242           rx_bad_ref.search(new_revision): 
     
    256259    def is_revisable(self, check_user=True): 
    257260        """ 
    258         Returns True if :attr:`object` is revisable : if :meth:`revise` can be 
     261        Returns True if :attr:`object` is revisable: if :meth:`revise` can be 
    259262        called safely. 
    260263 
    261         If *check_user* is True (the default), it also checks if :attr:`_user` is 
    262         the *owner* of :attr:`object`. 
     264        If *check_user* is True (the default), it also checks if :attr:`_user` can 
     265        see the objects. 
    263266        """ 
    264267        # objects.get fails if a link does not exist 
     
    268271            return False 
    269272        except ObjectDoesNotExist: 
    270             return self.check_permission("owner", False) 
     273            return self.check_readable(False) 
    271274     
    272275    def get_previous_revisions(self): 
     
    377380            pass 
    378381        # check if the role is valid 
    379         max_level = len(self.lifecycle.to_states_list()) - 1 
     382        max_level = self.lifecycle.nb_states - 1 
    380383        level = int(re.search(r"\d+", role).group(0)) 
    381384        if level > max_level: 
  • branches/3D/openPLM/plmapp/csvimport.py

    r474 r662  
    1515from openPLM.plmapp import models 
    1616from openPLM.plmapp.unicodecsv import UnicodeReader 
    17 from openPLM.plmapp.controllers.plmobject import PLMObjectController 
     17from openPLM.plmapp.controllers import PLMObjectController, UserController 
    1818from openPLM.plmapp.tasks import update_indexes 
    1919 
     
    353353 
    354354        parent.add_child(child, quantity, order) 
     355 
     356class UsersImporter(CSVImporter): 
     357    """ 
     358    A :class:`CSVImporter` that sponsors users from a CSV file. 
     359 
     360    The CSV must contain the following columns: 
     361 
     362        * username 
     363        * first_name 
     364        * last_name 
     365        * email 
     366        * groups (multiple groups can be separeted by a "/") 
     367 
     368    """ 
     369 
     370    REQUIRED_HEADERS = ('username', 'first_name', 'last_name', 'email', 'groups') 
     371 
     372    HEADERS_SET = set(REQUIRED_HEADERS) 
     373 
     374    def __init__(self, csv_file, user, encoding="utf-8"): 
     375        self.ctrl = UserController(user, user) 
     376        self.ctrl.block_mails() 
     377        super(UsersImporter, self).__init__(csv_file, user) 
     378        self.groups = dict(user.groups.values_list("name", "id")) 
     379 
     380    @classmethod 
     381    def get_headers_set(cls): 
     382        return cls.HEADERS_SET  
     383 
     384    def tear_down(self): 
     385        self.ctrl.unblock_mails() 
     386     
     387    def parse_row(self, line, row):  
     388        from openPLM.plmapp.forms import SponsorForm 
     389        un, fn, ln, em, grps = self.get_values(row, *self.REQUIRED_HEADERS) 
     390        groups = [] 
     391        for grp in grps.split("/"): 
     392            try: 
     393                groups.append(self.groups[grp]) 
     394            except KeyError: 
     395                self.store_errors(line, u"Invalid group:%s" % grp) 
     396                return 
     397        data = { 
     398                "sponsor" : self.user.id, 
     399                "username": un, 
     400                "last_name": ln, 
     401                "first_name": fn, 
     402                "email" : em, 
     403                "groups" : groups, 
     404                "warned" : True, 
     405                } 
     406        form = SponsorForm(data, sponsor=self.user.id) 
     407        if form.is_valid(): 
     408            new_user = form.save() 
     409            self.ctrl.sponsor(new_user) 
     410            self.objects.append(new_user) 
     411        else: 
     412            items = (mark_safe(u"%s: %s" % item) for item  
     413                    in form.errors.iteritems()) 
     414            self.store_errors(line, *items) 
     415     
    355416    
    356417#: Dictionary (name -> CSVImporter's subclass) of known :class:`CSVImporter` 
    357 IMPORTERS = {"csv" : PLMObjectsImporter, "bom" : BOMImporter } 
    358  
     418IMPORTERS = {"csv" : PLMObjectsImporter, "bom" : BOMImporter, 
     419        "users" : UsersImporter} 
     420 
  • branches/3D/openPLM/plmapp/forms.py

    r595 r662  
    3636 
    3737import openPLM.plmapp.models as m 
     38from openPLM.plmapp.units import UNITS, DEFAULT_UNIT 
    3839from openPLM.plmapp.controllers import rx_bad_ref, DocumentController 
    3940from openPLM.plmapp.controllers.user import UserController 
     
    6768        " to this group.") 
    6869 
    69 def get_creation_form(user, cls=m.PLMObject, data=None, empty_allowed=False): 
     70def auto_complete_fields(form, cls): 
     71    """ 
     72    Replaces textinputs field of *form* with auto complete fields. 
     73 
     74    :param form: a :class:`Form` instance or class 
     75    :param cls: class of the source that provides suggested values 
     76    """ 
     77    for field, form_field in form.base_fields.iteritems(): 
     78        if field not in ("reference", "revision") and \ 
     79                isinstance(form_field.widget, forms.TextInput): 
     80            source = '/ajax/complete/%s/%s/' % (cls.__name__, field) 
     81            form_field.widget = JQueryAutoComplete(source) 
     82 
     83def get_new_reference(cls, start=0): 
     84    u""" 
     85    Returns a new reference for creating a :class:`.PLMObject` of type 
     86    *cls*. 
     87 
     88    The formatting is ``PART_000XX`` if *cls* is a subclass of :class:`.Part` 
     89    and ``DOC_000XX`` otherwise. 
     90     
     91    The number is the count of Parts or Documents plus *start* plus 1. 
     92    It is incremented while an object with the same reference aleady exists. 
     93    *start* can be used to create several creation forms at once. 
     94 
     95    .. note:: 
     96        The returned referenced may not be valid if a new object has been 
     97        created after the call to this function. 
     98    """ 
     99    if issubclass(cls, m.Part): 
     100        base_cls, name = m.Part, "PART" 
     101    else: 
     102        base_cls, name = m.Document, "DOC" 
     103    nb = base_cls.objects.count() + start + 1 
     104    reference = "%s_%05d" % (name, nb) 
     105    while base_cls.objects.filter(reference=reference).exists(): 
     106        nb += 1 
     107        reference = "%s_%05d" % (name, nb) 
     108    return reference 
     109 
     110def get_initial_creation_data(cls, start=0): 
     111    u""" 
     112    Returns initial data to create a new object (from :func:`get_creation_form`). 
     113 
     114    :param cls: class of the created object 
     115    :param start: used to generate the reference,  see :func:`get_new_reference` 
     116    """ 
     117    if issubclass(cls, m.PLMObject): 
     118        data = { 
     119                'reference' : get_new_reference(cls),  
     120                'revision' : 'a', 
     121                'lifecycle' : str(m.get_default_lifecycle().pk), 
     122        } 
     123    else: 
     124        data = {} 
     125    return data 
     126 
     127def get_creation_form(user, cls=m.PLMObject, data=None, start=0): 
    70128    u""" 
    71129    Returns a creation form suitable to creates an object 
     
    77135    :class:`~plmapp.controllers.PLMObjectController`. 
    78136 
    79     If *initial* is provided, it will be used to fill the form. 
     137    If *data* is provided, it will be used to fill the form. 
     138 
     139    *start* is used if *data* is ``None``, it's usefull if you need to show 
     140    several initial creation forms at once and you want different references. 
    80141    """ 
    81142    Form = get_creation_form.cache.get(cls) 
     
    83144        fields = cls.get_creation_fields() 
    84145        Form = modelform_factory(cls, fields=fields, exclude=('type', 'state')) 
     146        # replace textinputs with autocomplete inputs, see ticket #66 
     147        auto_complete_fields(Form, cls) 
    85148        if issubclass(cls, m.PLMObject): 
    86149            Form.clean_reference = _clean_reference 
     
    95158            Form.clean = _clean 
    96159        get_creation_form.cache[cls] = Form 
    97     form = Form(data=data, empty_permitted=empty_allowed) if data else Form() 
     160    if data is None: 
     161        form = Form(initial=get_initial_creation_data(cls, start)) 
     162    else: 
     163        form = Form(data=data) 
    98164    if issubclass(cls, m.PLMObject): 
    99165        # display only valid groups 
     
    110176        fields = cls.get_modification_fields() 
    111177        Form = modelform_factory(cls, fields=fields) 
     178        auto_complete_fields(Form, cls) 
    112179        get_modification_form.cache[cls] = Form 
    113180    if data: 
     
    225292get_search_form.cache = {}     
    226293 
    227 from haystack.query import EmptySearchQuerySet 
    228 from openPLM.plmapp.search import SmartSearchQuerySet 
    229  
    230294class SimpleSearchForm(forms.Form): 
    231295    q = forms.CharField(label=_("Query"), required=False) 
    232296 
    233297    def search(self, *models): 
     298        from haystack.query import EmptySearchQuerySet 
     299        from openPLM.plmapp.search import SmartSearchQuerySet 
     300 
    234301        if self.is_valid(): 
    235302            query = self.cleaned_data["q"].strip() 
     
    246313    quantity = forms.FloatField() 
    247314    order = forms.IntegerField() 
     315    unit = forms.ChoiceField(choices=UNITS, initial=DEFAULT_UNIT) 
    248316 
    249317 
     
    265333    class Meta: 
    266334        model = m.ParentChildLink 
    267         fields = ["order", "quantity", "child", "parent"] 
     335        fields = ["order", "quantity", "unit", "child", "parent",] 
    268336 
    269337ChildrenFormset = modelformset_factory(m.ParentChildLink, 
     
    526594get_headers_formset = memoize(get_headers_formset, {}, 1) 
    527595 
     596from openPLM.plmapp.archive import ARCHIVE_FORMATS 
     597class ArchiveForm(forms.Form): 
     598    format = forms.TypedChoiceField(choices=zip(ARCHIVE_FORMATS, ARCHIVE_FORMATS)) 
     599 
  • branches/3D/openPLM/plmapp/mail.py

    r473 r662  
    3333from django.conf import settings 
    3434from django.core.mail import EmailMultiAlternatives 
    35 from django.db.models import Model 
     35from django.db.models import Model, Q 
    3636from django.template.loader import render_to_string 
    37 from django.contrib.contenttypes.models import ContentType 
     37from django.db.models.loading import get_model 
    3838from django.contrib.sites.models import Site 
    3939from celery.task import task 
    4040 
    41 from openPLM.plmapp.models import User, DelegationLink, ROLE_OWNER 
     41from openPLM.plmapp.models import User, DelegationLink, ROLE_OWNER, ROLE_SIGN 
    4242 
    4343 
     
    4545    recipients = set(users) 
    4646    if hasattr(obj, "plmobjectuserlink_plmobject"): 
    47         manager = obj.plmobjectuserlink_plmobject 
     47        manager = obj.plmobjectuserlink_plmobject.order_by() 
     48        roles_filter = Q() 
     49 
    4850        for role in roles: 
    49             users = manager.filter(role__contains=role).values_list("user", flat=True) 
    50             recipients.update(users) 
    51             links = DelegationLink.objects.filter(role__contains=role)\ 
     51            if role == ROLE_SIGN: 
     52                roles_filter |= Q(role__startswith=role) 
     53            else: 
     54                roles_filter |= Q(role=role) 
     55        users = manager.filter(roles_filter).values_list("user", flat=True) 
     56        recipients.update(users) 
     57        links = DelegationLink.objects.filter(roles_filter)\ 
    5258                        .values_list("delegator", "delegatee") 
    53             gr = kjbuckets.kjGraph(tuple(links)) 
    54             for u in users: 
    55                 recipients.update(gr.reachable(u).items()) 
    56     elif ROLE_OWNER: 
     59        gr = kjbuckets.kjGraph(tuple(links)) 
     60        for u in users: 
     61            recipients.update(gr.reachable(u).items()) 
     62    elif roles == [ROLE_OWNER]: 
    5763        if hasattr(obj, "owner"): 
    58             recipients.add(obj.owner.id) 
     64            recipients.add(obj.owner_id) 
    5965        elif isinstance(obj, User): 
    6066            recipients.add(obj.id) 
     
    6975 
    7076class CT(object): 
    71     def __init__(self, ct_id, pk): 
    72         self.ct_id = ct_id 
     77    __slots__ = ("app_label", "module_name", "pk") 
     78 
     79    def __init__(self, app_label, module_name, pk): 
     80        self.app_label = app_label 
     81        self.module_name = module_name 
    7382        self.pk = pk 
     83 
     84    def __getstate__(self): 
     85        return dict(app_label=self.app_label,  
     86                    module_name=self.module_name, 
     87                    pk=self.pk) 
     88     
     89    def __setstate__(self, state): 
     90        self.app_label = state["app_label"] 
     91        self.module_name = state["module_name"] 
     92        self.pk = state["pk"] 
    7493 
    7594    @classmethod 
    7695    def from_object(cls, obj): 
    77         return cls(ContentType.objects.get_for_model(obj).id, obj.pk) 
     96        return cls(obj._meta.app_label, obj._meta.module_name, obj.pk) 
    7897 
    7998    def get_object(self): 
    80         ct = ContentType.objects.get_for_id(self.ct_id) 
    81         return ct.get_object_for_this_type(pk=self.pk) 
     99        model_class = get_model(self.app_label, self.module_name) 
     100        return model_class.objects.get(pk=self.pk) 
    82101 
    83102 
     
    130149        The mail is sent in a separated thread.  
    131150    """ 
    132      
    133151    plmobject = unserialize(plmobject) 
    134     user = unserialize(user) 
    135     subject = "[PLM] " + unicode(plmobject) 
    136152    recipients = get_recipients(plmobject, roles, users)  
    137      
    138153    if recipients: 
     154        user = unserialize(user) 
     155        subject = "[PLM] " + unicode(plmobject) 
    139156        ctx = { 
    140157                "last_action" : last_action, 
     
    148165def do_send_mail(subject, recipients, ctx, template, blacklist=()): 
    149166    if recipients: 
     167        if len(recipients) == 1: 
     168            email = User.objects.get(id=recipients.pop()).email 
     169            if not email or email in blacklist: 
     170                return 
     171            emails = (email,) 
     172        else: 
     173            emails = User.objects.filter(id__in=recipients).exclude(email="")\ 
     174                            .values_list("email", flat=True) 
     175            emails = set(emails) - set(blacklist) 
     176            if not emails: 
     177                return 
    150178        ctx = unserialize(ctx) 
    151         emails = User.objects.filter(id__in=recipients).exclude(email="")\ 
    152                         .values_list("email", flat=True) 
    153         emails = set(emails) - set(blacklist) 
    154179        ctx["site"] = Site.objects.get_current() 
    155180        html_content = render_to_string(template + ".htm", ctx) 
     
    172197    histories = serialize(histories) 
    173198    user = CT.from_object(user) 
    174     do_send_histories_mail.delay(plmobject, roles, last_action, histories, user, blacklist, 
    175               convert_users(users), template) 
    176  
    177  
     199    do_send_histories_mail.delay(plmobject, roles, last_action, histories, 
     200            user, blacklist, convert_users(users), template) 
     201 
     202 
  • branches/3D/openPLM/plmapp/models.py

    r595 r662  
    9191from django.utils.translation import ugettext_lazy as _ 
    9292from django.utils.translation import ugettext_noop 
    93  
     93from django.forms.util import ErrorList 
     94 
     95from openPLM.plmapp.units import UNITS, DEFAULT_UNIT 
    9496from openPLM.plmapp.lifecycle import LifecycleList 
    9597from openPLM.plmapp.utils import level_to_sign_str, memoize_noarg 
     
    175177    mtime = models.DateTimeField(_("date of last modification"), auto_now=True) 
    176178 
     179    def __init__(self, *args, **kwargs): 
     180        if "__fake__" not in kwargs: 
     181            super(GroupInfo, self).__init__(*args, **kwargs) 
     182 
    177183    @property 
    178184    def plmobject_url(self): 
     
    204210        """ 
    205211        fields = [] 
    206         for field in cls().attributes: 
     212        for field in cls(__fake__=True).attributes: 
    207213            if field not in cls.excluded_creation_fields(): 
    208214                fields.append(field) 
     
    225231        "Returns fields which should be displayed in a modification form" 
    226232        fields = [] 
    227         for field in cls().attributes: 
     233        for field in cls(__fake__=True).attributes: 
    228234            if field not in cls.excluded_modification_fields(): 
    229235                fields.append(field) 
     
    253259        return u'State<%s>' % self.name 
    254260 
    255  
    256261class Lifecycle(models.Model): 
    257262    u""" 
     
    274279 
    275280    """ 
     281 
    276282    name = models.CharField(max_length=50, primary_key=True) 
    277283    official_state = models.ForeignKey(State) 
     284 
     285    def __init__(self, *args, **kwargs): 
     286        super(Lifecycle, self).__init__(*args, **kwargs) 
     287        # keep a cache of some values: Lifecycle are most of the time 
     288        # read-only objects, and there are no valid reasons to modify a 
     289        # lifecycle in a production environment 
     290        self._first_state = None 
     291        self._last_state = None 
     292        self._states_list = None 
    278293 
    279294    def __unicode__(self): 
     
    284299        Converts a Lifecycle to a :class:`LifecycleList` (a list of strings) 
    285300        """ 
    286          
    287         lcs = LifecycleStates.objects.filter(lifecycle=self).order_by("rank") 
    288         return LifecycleList(self.name, self.official_state.name, 
    289                              *(l.state.name for l in lcs)) 
     301        if self._states_list is None: 
     302            lcs = self.lifecyclestates_set.order_by("rank") 
     303            self._states_list = LifecycleList(self.name, self.official_state.name, 
     304                    *lcs.values_list("state__name", flat=True)) 
     305        return LifecycleList(self.name, self.official_state, *self._states_list) 
     306 
     307    @property 
     308    def first_state(self): 
     309        if self._first_state is None: 
     310            self._first_state = self.lifecyclestates_set.order_by('rank')[0].state 
     311        return self._first_state 
     312     
     313    @property 
     314    def last_state(self): 
     315        if self._last_state is None: 
     316            self._last_state = self.lifecyclestates_set.order_by('-rank')[0].state 
     317        return self._last_state 
     318 
     319    @property 
     320    def nb_states(self): 
     321        return self.lifecyclestates_set.count() 
    290322 
    291323    def __iter__(self): 
     
    330362                                                 self.rank) 
    331363 
    332  
     364@memoize_noarg 
    333365def get_default_lifecycle(): 
    334366    u""" 
     
    337369    return Lifecycle.objects.all()[0] 
    338370 
     371_default_states_cache = {} 
    339372def get_default_state(lifecycle=None): 
    340373    u""" 
     
    345378    if not lifecycle: 
    346379        lifecycle = get_default_lifecycle() 
    347     return State.objects.get(name=list(lifecycle)[0]) 
    348  
     380    state = _default_states_cache.get(lifecycle.name, None) 
     381    if state is None: 
     382        state = lifecycle.first_state 
     383        _default_states_cache[lifecycle.name] = state 
     384    return state 
    349385 
    350386# PLMobjects 
     
    401437 
    402438    # key attributes 
    403     reference = models.CharField(_("reference"), max_length=50) 
     439    reference = models.CharField(_("reference"), max_length=50, db_index=True) 
    404440    type = models.CharField(_("type"), max_length=50) 
    405441    revision = models.CharField(_("revision"), max_length=50) 
     
    432468        ordering = ["type", "reference", "revision"] 
    433469 
     470    def __init__(self, *args, **kwargs): 
     471        # little hack: 
     472        # get_creation_fields is a class method but it needs to create 
     473        # an instance, this hacks avoids calls to default value functions 
     474        if "__fake__" not in kwargs: 
     475            super(PLMObject, self).__init__(*args, **kwargs) 
     476        self._promotion_errors = None 
     477 
    434478    def __unicode__(self): 
    435479        return u"%s<%s/%s/%s>" % (type(self).__name__, self.reference, self.type, 
     
    438482    def _is_promotable(self): 
    439483        """ 
    440         Returns True if the object's state is the last state of its lifecyle 
    441         """ 
    442         lcl = self.lifecycle.to_states_list() 
    443         return lcl[-1] != self.state.name 
     484        Returns True if the object's state is the last state of its lifecycle. 
     485        """ 
     486        self._promotion_errors = ErrorList() 
     487        if self.lifecycle.last_state == self.state: 
     488            self._promotion_errors.append(_(u"The object is at its last state.")) 
     489            return False 
     490        return True 
    444491 
    445492    def is_promotable(self): 
     
    453500        raise NotImplementedError() 
    454501 
     502    def _get_promotion_errors(self): 
     503        """ Returns an :class:`ErrorList` of promotion errors. 
     504        Calls :meth:`is_promotable()` if it has not already been called. 
     505        """ 
     506        if self._promotion_errors is None: 
     507            self.is_promotable() 
     508        return self._promotion_errors 
     509    promotion_errors = property(_get_promotion_errors) 
     510 
    455511    @property 
    456512    def is_editable(self): 
    457513        """ 
    458514        True if the object is not in a non editable state 
    459         (for example, in an official or deprecated state 
    460         """ 
    461         current_rank = LifecycleStates.objects.get(state=self.state, 
    462                             lifecycle=self.lifecycle).rank 
    463         official_rank = LifecycleStates.objects.get(state=self.lifecycle.official_state, 
    464                             lifecycle=self.lifecycle).rank 
     515        (for example, in an official or deprecated state). 
     516        """ 
     517        lcs = self.lifecycle.lifecyclestates_set.only("rank") 
     518        current_rank = lcs.get(state=self.state).rank 
     519        official_rank = lcs.get(state=self.lifecycle.official_state).rank 
    465520        return current_rank < official_rank 
    466521     
     
    495550        return False 
    496551     
     552    @property 
     553    def is_official(self): 
     554        u"Returns True if document's state is official.""" 
     555        return self.state == self.lifecycle.official_state 
     556 
    497557    @property 
    498558    def attributes(self): 
     
    527587        """ 
    528588        fields = ["reference", "type", "revision", "lifecycle"] 
    529         for field in cls().attributes: 
     589        for field in cls(__fake__=True).attributes: 
    530590            if field not in cls.excluded_creation_fields(): 
    531591                fields.append(field) 
     
    552612        """ 
    553613        fields = [] 
    554         for field in cls().attributes: 
     614        for field in cls(__fake__=True).attributes: 
    555615            if field not in cls.excluded_modification_fields(): 
    556616                fields.append(field) 
     
    576636    def is_promotable(self): 
    577637        """ 
    578         Returns True if the object is promotable. A part is promotable 
    579         if there is a next state in its lifecycle and if its childs which 
    580         have the same lifecycle are in a state as mature as the object's state.   
     638        Returns True if the part is promotable.  
     639         
     640        A part is promotable if: 
     641             
     642            #. its state is not the last state of its lifecycle 
     643             
     644            #. if the part is not editable (its state is official). 
     645             
     646            #. the part is editable and: 
     647 
     648                #. there is a next state in its lifecycle and if its children 
     649                    which have the same lifecycle are in a state as mature as 
     650                    the object's state.   
     651 
     652                #. if the part has no children, there is at least one official 
     653                   document attached to it. 
    581654        """ 
    582655        if not self._is_promotable(): 
    583656            return False 
    584         childs = self.parentchildlink_parent.filter(end_time__exact=None).only("child") 
    585         lcs = LifecycleStates.objects.filter(lifecycle=self.lifecycle) 
    586         rank = lcs.get(state=self.state).rank 
    587         for link in childs: 
     657        if not self.is_editable: 
     658            return True 
     659        # check children 
     660        children = self.parentchildlink_parent.filter(end_time__exact=None).only("child") 
     661        lcs = self.lifecycle.to_states_list() 
     662        rank = lcs.index(self.state.name) 
     663        for link in children: 
    588664            child = link.child 
    589665            if child.lifecycle == self.lifecycle: 
    590                 rank_c = lcs.get(state=child.state).rank 
    591                 if rank_c < rank: 
     666                rank_c = lcs.index(child.state.name) 
     667                if rank_c == 0 or rank_c < rank: 
     668                    self._promotion_errors.append(_("Some children are at a lower or draft state.")) 
    592669                    return False 
     670        if not children: 
     671            # check that at least one document is attached and its state is official 
     672            # see ticket #57 
     673            found = False 
     674            links = self.documentpartlink_part.all() 
     675            for link in links: 
     676                found = link.document.is_official 
     677                if found: 
     678                    break 
     679            if not found: 
     680                self._promotion_errors.append(_("There are no official documents attached.")) 
     681            return found 
    593682        return True 
    594683 
     
    725814        if not self._is_promotable(): 
    726815            return False 
    727         return bool(self.files) and not bool(self.files.filter(locked=True)) 
     816        if not bool(self.files): 
     817            self._promotion_errors.append(_("This document has no files.")) 
     818            return False 
     819        if bool(self.files.filter(locked=True)): 
     820            self._promotion_errors.append(_("Some files are locked.")) 
     821            return False 
     822        return True 
    728823 
    729824    @property 
    730825    def menu_items(self): 
    731826        items = list(super(Document, self).menu_items) 
    732         items.extend([ugettext_noop("parts"), ugettext_noop("files")]) 
     827        items.insert(0, ugettext_noop("files")) 
     828        items.append(ugettext_noop("parts")) 
    733829        return items 
    734830 
     
    9111007    child = models.ForeignKey(Part, related_name="%(class)s_child")     
    9121008    quantity = models.FloatField(default=lambda: 1) 
     1009    unit = models.CharField(max_length=4, choices=UNITS, 
     1010            default=lambda: DEFAULT_UNIT) 
    9131011    order = models.PositiveSmallIntegerField(default=lambda: 1) 
    9141012    end_time = models.DateTimeField(blank=True, null=True, default=lambda: None) 
     
    9201018        return u"ParentChildLink<%s, %s, %f, %d>" % (self.parent, self.child, 
    9211019                                                     self.quantity, self.order) 
     1020    def get_shortened_unit(self): 
     1021        if self.unit == "-": 
     1022            return u"" 
     1023        return self.get_unit_display() 
    9221024 
    9231025class DocumentPartLink(Link): 
     
    9781080    delegator = models.ForeignKey(User, related_name="%(class)s_delegator")     
    9791081    delegatee = models.ForeignKey(User, related_name="%(class)s_delegatee")     
    980     role = models.CharField(max_length=30, choices=zip(ROLES, ROLES)) 
     1082    role = models.CharField(max_length=30, choices=zip(ROLES, ROLES), 
     1083            db_index=True) 
    9811084 
    9821085    class Meta: 
     
    10191122    plmobject = models.ForeignKey(PLMObject, related_name="%(class)s_plmobject")     
    10201123    user = models.ForeignKey(User, related_name="%(class)s_user")     
    1021     role = models.CharField(max_length=30, choices=zip(ROLES, ROLES)) 
     1124    role = models.CharField(max_length=30, choices=zip(ROLES, ROLES), 
     1125            db_index=True) 
    10221126 
    10231127    class Meta: 
  • branches/3D/openPLM/plmapp/navigate.py

    r595 r662  
    151151        self.graph.node_attr.update(self.NODE_ATTRIBUTES) 
    152152        self.graph.edge_attr.update(self.EDGE_ATTRIBUTES) 
     153        self.title_to_nodes = {} 
    153154 
    154155    def set_options(self, options): 
     
    202203        if self.options["prog"] == "twopi": 
    203204            self.graph.graph_attr["ranksep"] = "1.2" 
    204          
     205        
    205206    def _create_child_edges(self, obj, *args): 
    206207        if self.options[OSR] and self.users_result: 
     
    211212                continue 
    212213            child = PartController(link.child, None) 
    213             label = "Qty: %.2f\\nOrder: %d" % (link.quantity, link.order)  
     214            label = "Qty: %.2f %s\\nOrder: %d" % (link.quantity, 
     215                    link.get_shortened_unit(), link.order)  
    214216            self.graph.add_edge(obj.id, child.id, label) 
    215217            self._set_node_attributes(child) 
     
    226228                continue 
    227229            parent = PartController(link.parent, None) 
    228             label = "Qty: %.2f\\nOrder: %d" % (link.quantity, link.order)  
     230            label = "Qty: %.2f %s\\nOrder: %d" % (link.quantity, 
     231                    link.get_shortened_unit(), link.order)  
    229232            self.graph.add_edge(parent.id, obj.id, label) 
    230233            self._set_node_attributes(parent) 
     
    240243                continue 
    241244            part = PartController(link.part, None) 
    242             self.graph.add_edge(obj.id, part.id, " ") 
     245            # create a link part -> document: 
     246            # if layout is dot, the part is on top of the document 
     247            # cf. tickets #82 and #83 
     248            self.graph.add_edge(part.id, obj.id, " ") 
    243249            self._set_node_attributes(part) 
    244250     
     
    330336 
    331337    def _set_node_attributes(self, obj, obj_id=None, extra_label=""): 
    332         node = self.graph.get_node(obj_id or obj.id) 
     338        obj_id = obj_id or obj.id 
     339         
     340        # data and title_to_nodes are used to retrieve usefull data (url, tooltip) 
     341        # in convert_map 
     342        data = {} 
     343        node = self.graph.get_node(obj_id) 
     344        node.attr["tooltip"] = str(obj_id) 
     345        node.attr["URL"] = obj.plmobject_url + "navigate/" 
     346         
     347        # set node attributes according to its type 
    333348        type_ = type(obj) 
    334349        if issubclass(type_, PartController): 
     
    337352            type_ = DocumentController 
    338353        node.attr.update(self.TYPE_TO_ATTRIBUTES[type_]) 
    339         node.attr["URL"] = obj.plmobject_url + "navigate/" 
    340         node.attr["tooltip"] = "None" 
     354 
    341355        if isinstance(obj, PLMObjectController): 
    342             node.attr['label'] = get_path(obj).replace("/", "\\n") 
     356            # display the object's name if it is not empty 
     357            path = get_path(obj) 
     358            node.attr['label'] = obj.name.strip() or path.replace("/", "\\n") 
     359            data["title"] = path.replace("/", " - ") 
     360             
     361            # add urls to show/hide thumbnails and attached documents 
    343362            if type_ == DocumentController: 
    344                 node.attr["tooltip"] = "/ajax/thumbnails/" + get_path(obj) 
     363                data["url"] = "/ajax/thumbnails/" + get_path(obj) 
    345364            elif type_ == PartController and not self.options["doc"]: 
    346365                if obj.get_attached_documents(): 
    347366                    s = "+" if obj.id not in self.options["doc_parts"] else "-" 
    348                     node.attr["tooltip"] = s + str(obj.id) 
     367                    data["url"] = s + str(obj.id) 
    349368        elif isinstance(obj, UserController): 
    350             node.attr["label"] = obj.username 
     369            full_name =  u'%s\\n%s' % (obj.first_name, obj.last_name) 
     370            node.attr["label"] = full_name.strip() or obj.username 
     371            data["title"] = obj.username 
    351372        else: 
    352373            node.attr["label"] = obj.name 
    353374        node.attr["label"] += "\\n" + extra_label 
     375        # id is used by the javascript 
    354376        t = type_.__name__.replace("Controller", "") 
    355         node.attr["id"] = "_".join((str(obj_id or obj.id), t, str(obj.id))) 
     377        node.attr["id"] = "_".join((str(obj_id), t, str(obj.id))) 
     378        self.title_to_nodes[node.attr["id"]] = data 
    356379 
    357380    def convert_map(self, map_string): 
     
    360383        ajax_navigate = "/ajax/navigate/" + get_path(self.object) 
    361384        for area in ET.fromstring(map_string).findall("area"): 
     385            data = self.title_to_nodes.get(area.get("id"), {}) 
     386            # compute css position of the div 
    362387            left, top, x2, y2 = map(int, area.get("coords").split(",")) 
    363388            width = x2 - left 
    364389            height = y2 - top 
    365390            style = "position:absolute;z-index:5;top:%dpx;left:%dpx;width:%dpx;height:%dpx;" % (top, left, width, height) 
     391            # create a div with a title, and an <a> element 
    366392            id_ = "Nav-%s" % area.get("id") 
    367393            div = ET.Element("div", id=id_, style=style) 
    368394            div.set("class", "node" + " main_node" * (self.main_node == area.get("id"))) 
    369             url = area.get("title") 
     395            title = data.get("title") 
     396            if title: 
     397                div.set("title", title) 
     398            # add thumbnails and attached documents buttons 
     399            url = data.get("url", "None") 
    370400            if url.startswith("/ajax/thumbnails/"): 
    371401                thumbnails = ET.SubElement(div, "img", src="/media/img/search.png", 
     
    386416                show_doc.set("class", "node_show_docs" + self.BUTTON_CLASS) 
    387417                show_doc.set("onclick", "display_docs('%s', '%s', '%s');" % (id_, ajax_navigate, parts)) 
     418            # add the link 
    388419            a = ET.SubElement(div, "a", href=area.get("href"))  
    389420            span = ET.SubElement(a, "span") 
  • branches/3D/openPLM/plmapp/query_parser.py

    r460 r662  
    3030    pass 
    3131 
     32 
     33def convert_number(query, qualifier): 
     34    """ If query represents a number, replaces it with an OR query built with 
     35        several formatting of the number: for example, it replaces 51 with 51 
     36        or 051 or 0051... so that "51" matches "part-0051". 
     37 
     38        If *query* does not represent a number, it returns a simple 
     39        SQ(qualifier -> query) object. 
     40    """ 
     41    sq = SQ() 
     42    if query.isdigit(): 
     43        or_ = SQ() 
     44        numbers = ["0" * x + query for x in range(10)] 
     45        for nb in numbers: 
     46            or_ |= SQ(**{ qualifier : nb }) 
     47        sq &= or_ 
     48    else: 
     49        sq &= SQ(**{ qualifier : query }) 
     50    return sq 
     51 
    3252class Text(List): 
    33      
     53    
    3454    def to_SQ(self): 
    3555        if len(self) == 2: 
     
    4161        text = text.strip().lower() 
    4262        filters = {} 
     63        # here we replace a number with an OR query built with several formatting 
     64        # of the number: 
     65        # for example, we replace 51 with 51 or 051 or 0051... 
     66        sq = SQ() 
    4367        if text.endswith("*"): 
    44             sq = SQ() 
    4568            text = text.rstrip("*") 
    4669            items = split(text) 
    4770            for item in items[:-1]: 
    48                 sq &= SQ(**{ qualifier: item }) 
     71                sq &= convert_number(item, qualifier) 
    4972            suffix = "*" if qualifier == "content" else "" 
    5073            sq &= SQ(**{ qualifier + "__startswith" : items[-1]+suffix}) 
    51             return sq 
    5274        else: 
    53             return SQ(**{ qualifier : text }) 
     75            sq = convert_number(text, qualifier) 
     76        return sq 
    5477 
    5578class Not(List): 
  • branches/3D/openPLM/plmapp/search_indexes.py

    r595 r662  
    11from django.conf import settings 
    2  
    3 from haystack.indexes import * 
    4 from haystack import site 
    5  
    6 import openPLM.plmapp.models as models 
    7  
    82from django.db.models import signals 
    93from django.db.models.loading import get_model 
    104 
     5from haystack import site 
    116from haystack import indexes 
     7from haystack.indexes import * 
     8from haystack.models import SearchResult 
    129 
     10import openPLM.plmapp.models as models 
    1311from openPLM.plmapp.tasks import update_index 
     12 
     13# just a hack to prevent a KeyError 
     14def get_state(self): 
     15    ret_dict = self.__dict__.copy() 
     16    if 'searchsite' in ret_dict: 
     17        del(ret_dict['searchsite']) 
     18    del(ret_dict['log']) 
     19    return ret_dict 
     20SearchResult.__getstate__ = get_state 
    1421 
    1522########################### 
     
    111118            else: 
    112119                return self.model.objects.all() 
    113  
    114120    set_template_name(ModelIndex) 
    115121    site.register(model, ModelIndex) 
    116122 
    117123from subprocess import Popen, PIPE 
     124import codecs 
     125import os.path 
     126text_files = set((".txt",)) 
     127 
    118128class DocumentFileIndex(QueuedModelSearchIndex): 
    119129    text = CharField(document=True, use_template=True) 
     
    125135 
    126136    def prepare_file(self, obj): 
    127         p = Popen([settings.EXTRACTOR, obj.file.path], stdout=PIPE, close_fds=True) 
    128         return p.stdout.read() 
     137        # if it is a text file, we can dump it 
     138        # it's faster than launching a new process  
     139        path = obj.file.path 
     140        name, ext = os.path.splitext(path) 
     141        if ext.lower() in text_files: 
     142            return codecs.open(path, encoding="utf-8", errors="ignore").read() 
     143        else: 
     144            p = Popen([settings.EXTRACTOR, path], stdout=PIPE, close_fds=True) 
     145            return p.stdout.read() 
    129146 
    130147site.register(models.DocumentFile, DocumentFileIndex) 
  • branches/3D/openPLM/plmapp/tasks.py

    r595 r662  
    66 
    77from django.db.models.loading import get_model 
    8  
    9 from haystack import site 
    108 
    119from celery.task import task 
     
    4341@task(default_retry_delay = 60, max_retries = 10) 
    4442def update_index(app_name, model_name, pk, **kwargs): 
     43    from haystack import site 
     44    import openPLM.plmapp.search_indexes 
     45 
    4546    model_class = get_model(app_name, model_name) 
    46     instance = model_class.objects.get(pk=pk) 
     47    instance = model_class.objects.select_related(depth=1).get(pk=pk) 
    4748    search_index = site.get_index(model_class) 
    4849    search_index.update_object(instance) 
     
    5051@task(default_retry_delay = 60, max_retries = 10) 
    5152def update_indexes(instances): 
     53    from haystack import site 
     54    import openPLM.plmapp.search_indexes 
     55 
    5256    for app_name, model_name, pk in instances: 
    5357        model_class = get_model(app_name, model_name) 
    54         instance = model_class.objects.get(pk=pk) 
     58        instance = model_class.objects.select_related(depth=1).get(pk=pk) 
    5559        search_index = site.get_index(model_class) 
    5660        search_index.update_object(instance) 
  • branches/3D/openPLM/plmapp/tests/__init__.py

    r439 r662  
    2323################################################################################ 
    2424 
     25# import custom application models 
     26from django.conf import settings 
     27for app in settings.INSTALLED_APPS: 
     28    if app.startswith("openPLM"): 
     29        __import__("%s.models" % app, globals(), locals(), [], -1) 
     30 
     31import openPLM.plmapp.search_indexes 
     32 
    2533from openPLM.plmapp.tests.filehandlers import * 
    2634from openPLM.plmapp.tests.controllers import * 
     
    3038from openPLM.plmapp.tests.api import * 
    3139from openPLM.plmapp.tests.csvimport import * 
     40from openPLM.plmapp.tests.archive import * 
    3241 
    3342import openPLM.plmapp.models 
  • branches/3D/openPLM/plmapp/tests/ajax.py

    r433 r662  
    9595        data = self.post("/ajax/add_child/%d/" % self.controller.id, 
    9696                type=p2.type, reference=p2.reference, revision=p2.revision, 
    97                 quantity="10", order="10") 
     97                quantity="10", order="10", unit="g") 
    9898        self.assertEqual("ok", data["result"]) 
    9999 
  • branches/3D/openPLM/plmapp/tests/api.py

    r472 r662  
    6161 
    6262    def test_search_editable_only(self): 
     63        self.attach_to_official_document() 
    6364        self.controller.promote() 
    6465        data = self.get("/api/search/true/", type="Part", reference="Pa*") 
  • branches/3D/openPLM/plmapp/tests/base.py

    r474 r662  
    66from openPLM.plmapp.models import GroupInfo 
    77from openPLM.plmapp.controllers import PLMObjectController 
    8  
    9 import os.path 
    10 import shutil 
    11 from django.conf import settings 
    128 
    139class BaseTestCase(TestCase): 
     
    4541 
    4642    def tearDown(self): 
    47         if os.path.exists(settings.HAYSTACK_XAPIAN_PATH): 
    48             shutil.rmtree(settings.HAYSTACK_XAPIAN_PATH) 
     43        from haystack import backend 
     44        backend.SearchBackend.inmemory_db = None 
    4945        super(BaseTestCase, self).tearDown() 
    5046 
  • branches/3D/openPLM/plmapp/tests/controllers/part.py

    r472 r662  
    2929import datetime 
    3030 
    31 from openPLM.plmapp.controllers import PLMObjectController, PartController 
     31from openPLM.plmapp.controllers import PLMObjectController, PartController, \ 
     32        DocumentController 
     33import openPLM.plmapp.exceptions as exc 
     34import openPLM.plmapp.models as models 
     35from openPLM.plmapp.lifecycle import LifecycleList 
    3236 
    3337from openPLM.plmapp.tests.controllers.plmobject import ControllerTest 
     
    4852        self.controller4 = self.CONTROLLER.create("aPart4", self.TYPE, "a", 
    4953                                                  self.user, self.DATA) 
     54        self.document = DocumentController.create("Doc1", "Document", "a", 
     55                self.user, self.DATA) 
     56        self.document.add_file(self.get_file()) 
     57        self.document.promote() 
     58        for ctrl in (self.controller, self.controller2): 
     59            ctrl.attach_to_document(self.document) 
    5060 
    5161    def test_add_child(self): 
     
    102112 
    103113    def test_modify_child(self): 
    104         self.controller.add_child(self.controller2, 10, 15) 
    105         self.controller.modify_child(self.controller2, 3, 5) 
     114        self.controller.add_child(self.controller2, 10, 15, "-") 
     115        self.controller.modify_child(self.controller2, 3, 5, "kg") 
    106116        children = self.controller.get_children() 
    107117        level, link = children[0] 
    108118        self.assertEqual(link.quantity, 3) 
    109119        self.assertEqual(link.order, 5) 
     120        self.assertEqual(link.unit, "kg") 
    110121 
    111122    def test_delete_child(self): 
     
    152163 
    153164    def test_is_promotable1(self): 
     165        """Tests promotion from draft state, an official document is attached.""" 
    154166        self.failUnless(self.controller.is_promotable()) 
    155167 
    156168    def test_is_promotable2(self): 
     169        """Tests promotion from official state.""" 
    157170        self.controller.promote() 
    158171        self.failUnless(self.controller.is_promotable()) 
    159172     
    160173    def test_is_promotable3(self): 
     174        """Tests promotions with an official child.""" 
     175        self.controller2.promote() 
    161176        self.controller.add_child(self.controller2, 10, 15) 
    162177        self.failUnless(self.controller.is_promotable()) 
    163178         
    164179    def test_is_promotable4(self): 
     180        """Tests promotion from official state, with a deprecated child.""" 
    165181        self.controller2.promote() 
    166         self.controller.add_child(self.controller2, 10, 15) 
    167         self.failUnless(self.controller.is_promotable()) 
    168  
    169     def test_is_promotable5(self): 
    170         self.controller.add_child(self.controller2, 10, 15) 
    171         self.controller.promote() 
    172         self.failIf(self.controller.is_promotable()) 
    173  
    174  
     182        self.controller2.promote() 
     183        self.controller.add_child(self.controller2, 10, 15) 
     184        self.failUnless(self.controller.is_promotable()) 
     185 
     186    def test_is_promotable_no_document(self): 
     187        "Tests that a part with no document attached is not promotable.""" 
     188        self.failIf(self.controller3.is_promotable()) 
     189 
     190    def test_is_promotable_no_official_document(self): 
     191        "Tests that a part with no official document attached is not promotable." 
     192        doc = DocumentController.create("doc_2", "Document", "a", self.user, 
     193                self.DATA) 
     194        self.controller3.attach_to_document(doc) 
     195        self.failIf(self.controller3.is_promotable()) 
     196 
     197    def test_is_promotable_one_official_document(self): 
     198        """Tests that a part with one official document attached and another 
     199        not official is promotable.""" 
     200        doc = DocumentController.create("doc_2", "Document", "a", self.user, 
     201                self.DATA) 
     202        self.controller3.attach_to_document(self.document) 
     203        self.controller3.attach_to_document(doc) 
     204        self.failUnless(self.controller3.is_promotable()) 
     205 
     206    def test_promote(self): 
     207        controller = self.controller 
     208        self.assertEqual(controller.state.name, "draft") 
     209        controller.promote() 
     210        self.assertEqual(controller.state.name, "official") 
     211        self.failIf(controller.is_editable) 
     212        self.assertRaises(exc.PromotionError, controller.demote) 
     213        lcl = LifecycleList("diop", "official", "draft",  
     214                "issue1", "official", "deprecated") 
     215        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     216        controller.lifecycle = lc 
     217        controller.state = models.State.objects.get(name="draft") 
     218        controller.save() 
     219        controller.promote() 
     220        self.assertEqual(controller.state.name, "issue1") 
     221        controller.demote() 
     222        self.assertEqual(controller.state.name, "draft") 
     223        self.failUnless(controller.is_editable) 
     224 
     225 
  • branches/3D/openPLM/plmapp/tests/controllers/plmobject.py

    r472 r662  
    3434import openPLM.plmapp.models as models 
    3535from openPLM.plmapp.controllers import PLMObjectController 
    36 from openPLM.plmapp.lifecycle import LifecycleList 
    3736 
    3837from openPLM.plmapp.tests.base import BaseTestCase 
     
    4847        self.assertEqual(controller.name, "") 
    4948        self.assertEqual(controller.type, self.TYPE) 
    50         self.assertEqual(type(controller.object),  
    51                 models.get_all_plmobjects()[self.TYPE]) 
    5249        type_ = models.get_all_plmobjects()[self.TYPE] 
     50        self.assertEqual(type(controller.object), type_)  
    5351        obj = type_.objects.get(reference=controller.reference, 
    5452                revision=controller.revision, type=controller.type) 
     
    126124        self.assertRaises(ValueError, setattr, controller, "state", "draft") 
    127125 
    128     def test_promote(self): 
    129         controller = self.create("Part1") 
    130         self.assertEqual(controller.state.name, "draft") 
    131         controller.promote() 
    132         self.assertEqual(controller.state.name, "official") 
    133         self.failIf(controller.is_editable) 
    134         self.assertRaises(exc.PromotionError, controller.demote) 
    135         lcl = LifecycleList("diop", "official", "draft",  
    136                 "issue1", "official", "deprecated") 
    137         lc = models.Lifecycle.from_lifecyclelist(lcl) 
    138         controller.lifecycle = lc 
    139         controller.state = models.State.objects.get(name="draft") 
    140         controller.save() 
    141         controller.promote() 
    142         self.assertEqual(controller.state.name, "issue1") 
    143         controller.demote() 
    144         self.assertEqual(controller.state.name, "draft") 
    145         self.failUnless(controller.is_editable) 
    146  
    147126    def test_revise(self): 
    148127        """ 
     
    158137            self.assertEqual(getattr(controller, attr), getattr(rev, attr)) 
    159138 
     139    def test_revise_official(self): 
     140        ctrl = self.create("Part1") 
     141        ctrl.state = ctrl.lifecycle.official_state 
     142        ctrl.set_owner(self.cie) 
     143        ctrl.save() 
     144        self.failUnless(ctrl.is_revisable()) 
     145        rev = ctrl.revise("b") 
     146        self.assertEqual(self.user, rev.owner) 
     147 
    160148    def test_revise_error1(self): 
    161         "Revision : error : empty name" 
     149        "Revision : error : empty new revision" 
    162150        controller = self.create("Part1") 
    163151        self.assertRaises(exc.RevisionError, controller.revise, "") 
  • branches/3D/openPLM/plmapp/tests/csvimport.py

    r475 r662  
    11 
    2 import os.path 
    3 import shutil 
    42import cStringIO, StringIO 
    53from collections import defaultdict 
    64 
    7 from django.conf import settings 
    85from django.core import mail 
    96from django.contrib.auth.models import User 
    107from django.test import TransactionTestCase 
    11 from django.core.management import call_command 
    128 
    139from celery.signals import task_prerun 
    1410 
     11import openPLM.plmapp.models as models 
    1512from openPLM.plmapp.models import GroupInfo, PLMObject, ParentChildLink 
    1613from openPLM.plmapp.csvimport import PLMObjectsImporter, BOMImporter,\ 
    17         CSVImportError 
     14        CSVImportError, UsersImporter 
    1815from openPLM.plmapp.base_views import get_obj 
    1916from openPLM.plmapp.unicodecsv import UnicodeWriter 
     
    4542        self.client.post("/login/", {'username' : 'user', 'password' : 'password'}) 
    4643        task_prerun.connect(self.task_sent_handler) 
    47         call_command("rebuild_index", interactive=False, verbosity=0) 
    4844 
    4945    def task_sent_handler(self, sender=None, task_id=None, task=None, args=None, 
     
    5450        super(CSVImportTestCase, self).tearDown() 
    5551        task_prerun.disconnect(self.task_sent_handler) 
    56         if os.path.exists(settings.HAYSTACK_XAPIAN_PATH): 
    57             shutil.rmtree(settings.HAYSTACK_XAPIAN_PATH) 
    58          
     52        from haystack import backend 
     53        backend.SearchBackend.inmemory_db = None 
     54     
    5955    def get_valid_rows(self): 
    6056        return [[u'Type', 
     
    249245        self.assertEquals("SP1", sp1.name) 
    250246 
    251  
    252  
     247    def get_users_rows(self): 
     248        return [['username', 'first_name', 'last_name', 'email', 'groups'], 
     249                ['user_1', 'fn1', 'ln1', 'user_1@example.net', 'grp'], 
     250                ['user_2', 'fn2', 'ln2', 'user_2@example.net', 'grp'], 
     251                ['user_3', 'fn3', 'ln3', 'user_3@example.net', 'grp'], 
     252                ['user_4', 'fn4', 'ln4', 'user_4@example.net', 'grp'], 
     253                ['user_5', 'fn5', 'ln5', 'user_5@example.net', 'grp'], 
     254               ] 
     255 
     256    def test_users_valid(self): 
     257        csv_rows = self.get_users_rows() 
     258        objects = self.import_csv(UsersImporter, csv_rows) 
     259        self.assertEquals(len(csv_rows) - 1, len(objects)) 
     260        users = models.User.objects.filter(username__startswith="user_") 
     261        self.assertEquals(5, users.count()) 
     262        sponsor_links = self.user.delegationlink_delegator.filter(role="sponsor") 
     263        self.assertEquals(5, sponsor_links.count()) 
     264        self.assertEquals(set(users.values_list("id", flat=True)), 
     265                set(sponsor_links.values_list("delegatee", flat=True))) 
     266 
     267    def test_users_invalid_duplicated_users(self): 
     268        csv_rows = self.get_users_rows() 
     269        csv_rows.append(csv_rows[1]) 
     270        self.assertRaises(CSVImportError, self.import_csv, 
     271                          UsersImporter, csv_rows) 
     272        # 2 : company + test 
     273        self.assertEquals(2, User.objects.count()) 
     274        sponsor_links = self.user.delegationlink_delegator.filter(role="sponsor") 
     275        self.assertFalse(bool(sponsor_links)) 
     276        self.assertEqual(len(mail.outbox), 0) 
     277 
     278    def test_users_invalid_missing_first_name(self): 
     279        csv_rows = self.get_users_rows() 
     280        csv_rows[1][1] = "" 
     281        self.assertRaises(CSVImportError, self.import_csv, 
     282                          UsersImporter, csv_rows) 
     283        # 2 : company + test 
     284        self.assertEquals(2, User.objects.count()) 
     285        sponsor_links = self.user.delegationlink_delegator.filter(role="sponsor") 
     286        self.assertFalse(bool(sponsor_links)) 
     287        self.assertEqual(len(mail.outbox), 0) 
     288 
  • branches/3D/openPLM/plmapp/tests/runner.py

    r474 r662  
    11# from: http://djangosnippets.org/snippets/2211/ by cronosa 
    2 from django.test.simple import DjangoTestSuiteRunner #@UnresolvedImport 
     2import os 
    33import logging 
    44from django.conf import settings 
    55EXCLUDED_APPS = getattr(settings, 'TEST_EXCLUDE', []) 
     6from django.test.simple import DjangoTestSuiteRunner 
     7from django_xml_test_runner.xmltestrunner import XMLTestSuiteRunner 
    68 
    7 class OpenPLMTestSuiteRunner(DjangoTestSuiteRunner): 
     9if os.environ.get("TEST_OUTPUT", "stdin") == "xml": 
     10    TestSuiteRunner = XMLTestSuiteRunner 
     11else: 
     12    TestSuiteRunner= DjangoTestSuiteRunner 
     13 
     14class OpenPLMTestSuiteRunner(TestSuiteRunner): 
    815    def __init__(self, *args, **kwargs): 
    916        from django.conf import settings 
  • branches/3D/openPLM/plmapp/tests/views.py

    r474 r662  
    7676        return response 
    7777 
     78    def attach_to_official_document(self): 
     79        u""" If :attr:`controller`` is a PartController, this method attachs 
     80        an official document to it, so that it becomes promotable. 
     81 
     82        Does nothing if :attr:`controller` is a DocumentController. 
     83        """ 
     84        if self.controller.is_part: 
     85            document = DocumentController.create("doc_1", "Document", "a", 
     86                    self.user, self.DATA) 
     87            document.add_file(self.get_file()) 
     88            document.promote() 
     89            self.controller.attach_to_document(document) 
    7890 
    7991class ViewTest(CommonViewTest): 
     
    98110                "state" : m.get_default_state().pk, 
    99111                }) 
    100  
    101         response = self.post("/object/create/", data, page="attributes") 
     112        model_cls = m.get_all_plmobjects()[self.TYPE] 
     113        page = "files" if issubclass(model_cls, m.Document) else "attributes" 
     114        response = self.post("/object/create/", data, page=page) 
    102115        obj = m.PLMObject.objects.get(type=self.TYPE, reference="mapart", revision="a") 
    103116        self.assertEqual(obj.id, response.context["obj"].id) 
     
    125138 
    126139    def test_lifecycle(self): 
     140        self.attach_to_official_document() 
    127141        response = self.get(self.base_url + "lifecycle/") 
    128142        lifecycles = tuple(response.context["object_lifecycle"]) 
     
    328342        response = self.client.post(self.base_url + "BOM-child/add/", 
    329343                {"type": "Part", "reference":"c1", "revision":"a", 
    330                  "quantity" : 10, "order" : 10 }) 
     344                    "quantity" : 10, "order" : 10, "unit" : "m"}) 
    331345        self.assertEquals(1, len(self.controller.get_children())) 
    332346 
     
    345359            'form-0-parent' :  self.controller.id, 
    346360            'form-0-quantity' :  '45.0', 
     361            'form-0-unit' :  'cm', 
    347362        } 
    348363        response = self.post(self.base_url + "BOM-child/edit/", data) 
     
    522537    return sorted(l, key=lambda x: x.id) 
    523538 
    524 from django.core.management import call_command 
    525539class SearchViewTestCase(CommonViewTest): 
    526540 
     
    533547 
    534548    def search(self, request, type=None): 
    535         # rebuild the index 
    536         call_command("rebuild_index", interactive=False, verbosity=0) 
    537549         
    538550        query = self.get_query(request) 
     
    641653        self.assertTrue(c.object not in results)  
    642654 
     655    def test_search_numbers(self): 
     656        """ Tests that 1759 matches 001759. (see ticket #69). """ 
     657 
     658        c2 = self.CONTROLLER.create("part-001759", self.TYPE, "c", self.user, self.DATA) 
     659        results = self.search("1759", self.TYPE) 
     660        self.assertEqual([c2.object], results)  
     661        c3 = self.CONTROLLER.create("part-0001759", self.TYPE, "c", self.user, self.DATA) 
     662        results = self.search("1759", self.TYPE) 
     663        self.assertEqual([c2.object, c3.object], results)  
     664 
    643665    def test_search_all(self): 
    644666        for i in xrange(6): 
     
    649671 
    650672    def test_search_not(self): 
     673        self.controller.name = "abcdef" 
     674        self.controller.save() 
    651675        results = self.search("NOT %s" % self.controller.name, self.TYPE) 
    652676        self.assertEqual([], results) 
  • branches/3D/openPLM/plmapp/views/ajax.py

    r595 r662  
    8181    results = cls.objects.filter(**{"%s__icontains" % field : term}) 
    8282    results = results.values_list(field, flat=True).order_by(field).distinct() 
    83     json = JSONEncoder().encode(list(results[:limit]))   
     83    json = JSONEncoder().encode(list(str(r) for r in results[:limit]))   
    8484    return HttpResponse(json, mimetype='application/json') 
    8585 
     
    139139            child = get_obj_from_form(form, request.user) 
    140140            part.add_child(child, form.cleaned_data["quantity"],  
    141                            form.cleaned_data["order"]) 
     141                           form.cleaned_data["order"], 
     142                           form.cleaned_data["unit"]) 
    142143            return {"result" : "ok"} 
    143144        else: 
  • branches/3D/openPLM/plmapp/views/api.py

    r368 r662  
    279279    fields = [] 
    280280    for field_name, field in form.fields.items(): 
    281         data = dict(name=field_name, label=field.label.capitalize(), initial=field.initial) 
    282         if callable(field.initial): 
    283             data["initial"] = field.initial() 
    284             if hasattr(data["initial"], "pk"): 
    285                 data["initial"] = data["initial"].pk 
     281        initial = form.initial.get(field_name, field.initial) 
     282        if callable(initial): 
     283            initial = initial() 
     284        if hasattr(initial, "pk"): 
     285            initial = initial.pk 
     286        data = dict(name=field_name, 
     287                    label=field.label.capitalize(), 
     288                    initial=initial, 
     289               ) 
    286290        data["type"] = field_to_type(field) 
    287291        if hasattr(field, "choices"): 
  • branches/3D/openPLM/plmapp/views/main.py

    r595 r662  
    6363from django.forms import HiddenInput 
    6464 
     65from openPLM.plmapp.archive import generate_archive 
    6566from openPLM.plmapp.exceptions import ControllerError, PermissionError 
    6667import openPLM.plmapp.models as models 
     
    127128        url = u"/%s/%s/attributes/" % (obj_type.lower(), obj_ref) 
    128129    else: 
    129         url = u"/object/%s/%s/%s/attributes/" % (obj_type, obj_ref, obj_revi)  
     130        model_cls = models.get_all_plmobjects()[obj_type] 
     131        page = "files" if issubclass(model_cls, models.Document) else "attributes" 
     132        url = u"/object/%s/%s/%s/%s/" % (obj_type, obj_ref, obj_revi, page)  
    130133    return HttpResponsePermanentRedirect(iri_to_uri(url)) 
    131134 
     
    281284            obj.add_child(child_obj, 
    282285                          add_child_form.cleaned_data["quantity"], 
    283                           add_child_form.cleaned_data["order"]) 
     286                          add_child_form.cleaned_data["order"], 
     287                          add_child_form.cleaned_data["unit"]) 
    284288            return HttpResponseRedirect(obj.plmobject_url + "BOM-child/")  
    285289    else: 
    286290        add_child_form = AddChildForm() 
    287         ctx.update({'current_page':'BOM-child'}) 
     291        ctx['current_page'] = 'BOM-child' 
    288292    ctx.update({'link_creation': True, 
    289293                'add_child_form': add_child_form, 
     
    347351    else: 
    348352        formset = get_doc_cad_formset(obj) 
     353    archive_form = forms.ArchiveForm() 
    349354    ctx.update({'current_page':'doc-cad', 
    350355                'object_doc_cad': obj.get_attached_documents(), 
     356                'archive_form' : archive_form, 
    351357                'doc_cad_formset': formset}) 
    352358    return r2r('DisplayObjectDocCad.htm', ctx, request) 
     
    450456    else: 
    451457        formset = get_file_formset(obj) 
     458    archive_form = forms.ArchiveForm() 
    452459    ctx.update({'current_page':'files',  
    453                 'file_formset': formset}) 
     460                'file_formset': formset, 
     461                'archive_form' : archive_form, 
     462               }) 
    454463    return r2r('DisplayObjectFiles.htm', ctx, request) 
    455464 
     
    605614 
    606615    obj, ctx = get_generic_data(request) 
    607      
     616 
    608617    if request.method == 'GET': 
    609         type_form = TypeForm(request.GET) 
     618        type_form = TypeFormWithoutUser(request.GET) 
    610619        if type_form.is_valid(): 
    611620            type_ = type_form.cleaned_data["type"] 
    612             cls = models.get_all_userprofiles_and_plmobjects()[type_] 
    613             if issubclass(cls, models.Document): 
    614                 class_for_div="ActiveBox4Doc" 
    615             else: 
    616                 class_for_div="ActiveBox4Part" 
    617             data = {'revision':'a', 
    618                     'lifecycle': str(models.get_default_lifecycle()), } 
    619             creation_form = get_creation_form(request.user, cls, data, True) 
     621            cls = models.get_all_plmobjects()[type_] 
     622            creation_form = get_creation_form(request.user, cls) 
    620623    elif request.method == 'POST': 
    621         type_form = TypeForm(request.POST) 
     624        type_form = TypeFormWithoutUser(request.POST) 
    622625        if type_form.is_valid(): 
    623626            type_name = type_form.cleaned_data["type"] 
    624             cls = models.get_all_userprofiles_and_plmobjects()[type_name] 
    625             if issubclass(cls, models.Document): 
    626                 class_for_div="ActiveBox4Doc" 
    627             else: 
    628                 class_for_div="ActiveBox4Part" 
     627            cls = models.get_all_plmobjects()[type_name] 
    629628            creation_form = get_creation_form(request.user, cls, request.POST) 
    630629            if creation_form.is_valid(): 
     
    633632                controller = controller_cls.create_from_form(creation_form, user) 
    634633                return HttpResponseRedirect(controller.plmobject_url) 
    635     ctx.update({'class4div': class_for_div, 
    636                 'creation_form': creation_form, 
    637                 'object_type': type_form.cleaned_data["type"], 
    638                }) 
     634    ctx.update({ 
     635        'creation_form': creation_form, 
     636        'object_type': type_form.cleaned_data["type"], 
     637    }) 
    639638    return r2r('DisplayObject4creation.htm', ctx, request) 
    640639 
    641640########################################################################################## 
    642 @handle_errors 
     641@handle_errors(undo="../attributes/") 
    643642def modify_object(request, obj_type, obj_ref, obj_revi): 
    644643    """ 
     
    849848def download(request, docfile_id, filename=""): 
    850849    """ 
    851     Manage html page for the files (:class:`DocumentFile`) download in the selected object. 
    852     It computes a context dictionnary based on 
     850    View to download a document file. 
    853851     
    854852    :param request: :class:`django.http.QueryDict` 
     
    869867        response['Content-Disposition'] = 'attachment; filename="%s"' % name 
    870868    return response 
    871   
     869 
     870@handle_errors  
     871def download_archive(request, obj_type, obj_ref, obj_revi): 
     872    """ 
     873    View to download all files from a document/part. 
     874 
     875    .. include:: views_params.txt  
     876    """ 
     877 
     878    obj = get_obj(obj_type, obj_ref, obj_revi, request.user) 
     879    obj.check_readable() 
     880     
     881    d_o_u = "document__owner__username" 
     882    if obj.is_document: 
     883        files = obj.files.select_related(d_o_u) 
     884    elif obj.is_part: 
     885        links = obj.get_attached_documents() 
     886        docs = (link.document for link in links) 
     887        files = itertools.chain(*(doc.files.select_related(d_o_u) 
     888            for doc in docs)) 
     889    else: 
     890        return HttpResponseForbidden() 
     891 
     892    form = forms.ArchiveForm(request.GET) 
     893    if form.is_valid(): 
     894        format = form.cleaned_data["format"] 
     895        name = "%s_%s.%s" % (obj_ref, obj_revi, format) 
     896        mimetype = guess_type(name, False)[0] 
     897        if not mimetype: 
     898            mimetype = 'application/octet-stream' 
     899        content = generate_archive(files, format) 
     900        response = HttpResponse(content, mimetype=mimetype) 
     901        #response["Content-Length"] = size 
     902        response['Content-Disposition'] = 'attachment; filename="%s"' % name 
     903        return response 
     904    return HttpResponseForbidden() 
     905 
    872906########################################################################################## 
    873907@handle_errors  
  • branches/3D/openPLM/settings_tests.py

    r599 r662  
    33# sqlite version 
    44 
     5import sys 
    56import os.path 
    67 
     
    1516 
    1617DATABASE_ENGINE = 'sqlite3'           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 
    17 DATABASE_NAME = 'openplm'             # Or path to database file if using sqlite3. 
    18 DATABASE_USER = 'django'             # Not used with sqlite3. 
    19 DATABASE_PASSWORD = 'django#6'         # Not used with sqlite3. 
    20 DATABASE_HOST = 'localhost'             # Set to empty string for localhost. Not used with sqlite3. 
     18DATABASE_NAME = 'database_tests.db'             # Or path to database file if using sqlite3. 
     19DATABASE_USER = ''             # Not used with sqlite3. 
     20DATABASE_PASSWORD = ''         # Not used with sqlite3. 
     21DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3. 
    2122DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3. 
    2223 
     
    4041# Absolute path to the directory that holds media. 
    4142# Example: "/home/media/media.lawrence.com/" 
    42 MEDIA_ROOT = '/home/linux/openPLM/branches/3D/openPLM/media/' 
     43MEDIA_ROOT = 'media/' 
    4344 
    4445# URL that handles the media served from MEDIA_ROOT. Make sure to use a 
     
    5758# List of callables that know how to import templates from various sources. 
    5859TEMPLATE_LOADERS = ( 
     60    ('django.template.loaders.cached.Loader', ( 
    5961    'django.template.loaders.filesystem.load_template_source', 
    6062    'django.template.loaders.app_directories.load_template_source', 
     63    )), 
    6164#     'django.template.loaders.eggs.load_template_source', 
    6265) 
     
    8285    # Always use forward slashes, even on Windows. 
    8386    # Don't forget to use absolute paths, not relative paths. 
    84     "/home/linux/openPLM/branches/3D/openPLM/templates", 
    85     "/home/linux/openPLM/branches/3D/openPLM/document3D/templates", 
     87    "templates", 
     88    "document3D/templates", 
    8689) 
    8790 
     
    9699    'djcelery', 
    97100    'haystack', 
    98     'south', 
    99101    'openPLM.plmapp', 
    100102    # you can add your application after this line 
     
    103105    'openPLM.cae', 
    104106    'openPLM.office', 
    105     'openPLM.bicycle', 
    106107    'openPLM.document3D', 
    107108) 
     
    124125 
    125126#: directory that stores documents. Make sure to use a trailing slash. 
    126 DOCUMENTS_DIR = "/home/linux/openPLM/branches/3D/openPLM/docs/"    #aquica 
     127DOCUMENTS_DIR = "/tmp/docs/"    #aquica 
    127128THUMBNAILS_DIR = os.path.join(MEDIA_ROOT, "thumbnails/") 
    128129#: directory that stores thumbnails. Make sure to use a trailing slash. 
     
    150151 
    151152# search stuff 
     153if "rebuild_index" not in sys.argv: 
     154    HAYSTACK_ENABLE_REGISTRATIONS = False 
    152155HAYSTACK_SITECONF = 'openPLM.plmapp.search_sites' 
    153156HAYSTACK_SEARCH_ENGINE = 'xapian' 
    154 HAYSTACK_XAPIAN_PATH = "/home/linux/openPLM/branches/3D/openPLM/xapian_index/" 
    155 HAYSTACK_INCLUDE_SPELLING = True 
     157# use a memory backend 
     158HAYSTACK_XAPIAN_PATH = ":memory:"  
     159# the memory backend does not support spelling 
     160HAYSTACK_INCLUDE_SPELLING = False 
    156161EXTRACTOR = os.path.abspath(os.path.join(os.path.dirname(__file__), "bin", "extractor.sh")) 
    157162 
     
    165170COMPANY = "company" 
    166171 
     172TEST_RUNNER = "openPLM.plmapp.tests.runner.OpenPLMTestSuiteRunner" 
     173TEST_OUTPUT_DIR = "tests_results" 
     174 
  • branches/3D/openPLM/templates/BaseDisplayHomePage.htm

    r595 r662  
    2525        <script type="text/javascript" src="/media/js/panels.js"></script> 
    2626        <script type="text/javascript" src="/media/js/help.js"></script> 
     27        <script type="text/javascript" src="/media/js/confirm.js"></script> 
    2728 
    2829 
     
    6162                        <li class="{{"Button"|button:"corner-left"}}" id="NavigateButton"> 
    6263                        {% endif %} 
    63                         {% ifequal class4div 'ActiveBox4User' %} 
    64                         <a href="/user/{{object_reference}}/navigate/"> 
    65                             {% else %} 
    66                             <a href="/object/{{object_type}}/{{object_reference}}/{{object_revision}}/navigate/"> 
    67                                 {% endifequal %} 
     64                        <a href="{{obj.plmobject_url}}navigate/"> 
    6865                                <span class="ui-button-text">{% trans "NAVIGATE" %}</span> 
    6966                            </a> 
  • branches/3D/openPLM/templates/DisplayObject4modification.htm

    r595 r662  
    2424                {% endfor %} 
    2525                <tr class="Content"> 
    26                         <td></td><td><input type="submit" class="{{"Button"|button}}" value="{% trans "MODIFY" %}" /></td> 
     26            <td></td><td><input type="submit" class="{{"Button"|button}}" value="{% trans "MODIFY" %}" /> 
     27             
     28                <input type="submit" class="{{"Button"|button}}" value="{% trans "Undo" %}" name="_undo"/> 
     29            </td> 
    2730                </tr> 
    2831        </table> 
  • branches/3D/openPLM/templates/DisplayObjectChild.htm

    r486 r662  
    3232            <th class="Content" style="width:50px"> {% trans "Ord." %} </th> 
    3333            <th class="Content" style="width:50px"> {% trans "Qty" %}</th> 
     34            <th class="Content" style="width:50px"> {% trans "Unit" %}</th> 
    3435            <th class="Content"> {{ obj.reference }} </th> 
    3536            <th class="Content"> {{ obj.revision }} </th> 
     
    4647                <td class="Content"> {{ link.order }} </td> 
    4748                <td class="Content"> {{ link.quantity }} </td> 
     49                <td class="Content"> {{ link.get_unit_display }} </td> 
    4850                <td class="Content">  
    4951                    <a href="/object/{{link.child.type}}/{{link.child.reference}}/{{link.child.revision}}/"> 
  • branches/3D/openPLM/templates/DisplayObjectChildEdit.htm

    r486 r662  
    1111            <th class="Content">{% trans "Order" %}</th> 
    1212            <th class="Content">{% trans "Quantity" %} </th> 
     13            <th class="Content" style="width:50px"> {% trans "Unit" %}</th> 
    1314            <th class="Content"> {{ obj.reference }} </th> 
    1415            <th class="Content"> {{ obj.revision }} </th> 
     
    2526                <td class="Content"> {{ form.order }} </td> 
    2627                <td class="Content"> {{ form.quantity }} </td> 
     28                <td class="Content" style="width:50px"> {{ form.unit }} </td> 
    2729                <td class="Content">  
    2830                    <a href="{{form.instance.child.plmobject_url}}"> 
  • branches/3D/openPLM/templates/DisplayObjectDocCad.htm

    r486 r662  
    55 
    66{% block content %} 
     7    {% if object_doc_cad.count > 0 %} 
     8        <form class="archive_form" method="GET" action="./archive/"> 
     9            {{ archive_form.as_p }} 
     10            <input type="submit" class="{{"Button"|button}}" value="{% trans "Download all files" %}"/> 
     11        </form> 
     12    {% endif %} 
    713    <form method="post" action="">       
    814        {{ doc_cad_formset.management_form }} 
     
    1117                <span class="ui-button-text">{% trans "Attach another document" %}</span> 
    1218            </a> 
    13             {% if doc_cad_formset.forms %} 
     19            {% if object_doc_cad.count > 0 %} 
    1420                <input type="submit" class="{{"Button"|button}}" value="{% trans "DISCONNECT" %}"/> 
    1521            {% endif %} 
  • branches/3D/openPLM/templates/DisplayObjectFiles.htm

    r521 r662  
    55 
    66{% block content %} 
     7    {% if file_formset.total_form_count %} 
     8        <form class="archive_form" method="GET" action="./archive/"> 
     9            {{ archive_form.as_p }} 
     10            <input type="submit" class="{{"Button"|button}}" value="{% trans "Download all files" %}"/> 
     11        </form> 
     12    {% endif %} 
    713    <form method="POST" action=""> 
    814        {{ file_formset.management_form }} 
     
    1218                <span class="ui-button-text">{% trans "ADD" %}</span> 
    1319            </a> 
    14             <input type="submit" class="{{"Button"|button}}" value="{% trans "DELETE" %}"/> 
     20            {% if file_formset.total_form_count %} 
     21                <input type="submit" class="{{"Button"|button}}" value="{% trans "DELETE" %}"/> 
     22            {% endif %} 
    1523        {% endif %} 
    1624            <table class="Content"> 
     25                    {% for form in file_formset.forms %} 
    1726                    <tr class="Content"> 
    18                         {% for form in file_formset.forms %} 
    1927                {{ form.id }} 
    2028                {{ form.document }} 
  • branches/3D/openPLM/templates/DisplayObjectLifecycle.htm

    r595 r662  
    55 
    66{% block content %} 
    7     <form action="" method="POST"> 
    8         {% if is_signer_dm and obj.is_editable %} 
    9         <input name="action" type="submit" class="{{"Button"|button}}" value="DEMOTE"/> 
     7    <div id="form-promote-dialog" title="{% trans "Are you sure?" %}" style="display:none"> 
     8        <p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>{% trans "If you promote or demote this object, you may not be allowed to undo this action." %}</p> 
     9    </div> 
     10 
     11    <form id ="form-promote" class="confirmation"  action="" method="POST"> 
     12        {% if is_signer_dm %} 
     13            {% if obj.is_editable %} 
     14                <input name="action" type="submit" class="{{"Button"|button}}" value="DEMOTE" /> 
     15            {% else %} 
     16                <p>{% trans "You can not demote this object since its state is official or more advanced." %}</p> 
     17            {% endif %} 
     18        {% else %} 
     19            <p>{% trans "You do not have the permission to demote this object." %}</p> 
    1020        {% endif %} 
    11         {% if is_signer and obj.is_promotable %} 
    12         <input name="action" type="submit" class="{{"Button"|button}}" value="PROMOTE"/> 
     21        {% if is_signer %} 
     22            {% if obj.is_promotable %} 
     23                <input name="action" type="submit" class="{{"Button"|button}}" value="PROMOTE" /> 
     24            {% else %} 
     25                <p>{% trans "You can not promote this object:" %}</p> 
     26                {{ obj.promotion_errors.as_ul }} 
     27            {% endif %} 
     28        {% else %} 
     29            <p>{% trans "You do not have the permission to promote this object." %}</p> 
    1330        {% endif %} 
    1431    </form> 
    15         <table class="Content"> 
    16                 <tr class="Content"> 
    17                 {% for status, is_current_state in object_lifecycle %} 
     32    <table class="Content"> 
     33        <tr class="Content"> 
     34            {% for status, is_current_state in object_lifecycle %} 
    1835                <td class="Content status Button ui-state-default ui-button-text-only  
    19                                 {% if is_current_state %} 
     36                    {% if is_current_state %} 
    2037                        ui-state-active active 
    2138                    {% else %} 
    2239                        ui-state-inactive 
    23                                     {% endif %} 
     40                    {% endif %} 
    2441                    "> 
    25                                 {{status|upper}}</td> 
    26                                 {% if not forloop.last %} 
    27                                     <td class="Content arrow">===&gt;</td> 
    28                                     {% endif %} 
    29                 {% endfor %} 
    30                 </tr> 
    31         </table> 
     42                    {{status|upper}}</td> 
     43                {% if not forloop.last %} 
     44                    <td class="Content arrow">===&gt;</td> 
     45                {% endif %} 
     46            {% endfor %} 
     47        </tr> 
     48    </table> 
    3249{% endblock %} 
    3350 
  • branches/3D/openPLM/templates/Navigate.htm

    r595 r662  
    55 
    66    <link rel="stylesheet" href="/media/css/navigate.css" type="text/css" media="screen" charset="utf-8" /> 
     7    <link rel="stylesheet" href="/media/css/showLoading.css" type="text/css" media="screen" charset="utf-8" /> 
    78{% endblock css %} 
    89 
    910{% block scripts %} 
    1011    <script type="text/javascript" src="/media/js/jquery.hoverIntent.minified.js"></script> 
     12    <script type="text/javascript" src="/media/js/jquery.showLoading.min.js"></script> 
    1113    <script type="text/javascript" src="/media/js/navigate.js"></script> 
    1214 
  • branches/3D/openPLM/templates/import/done.htm

    r486 r662  
    88        <a href="/import/csv/">{% trans "Click here to import a new CSV file." %}</a> 
    99        <a href="/import/bom/">{% trans "Click here to import a new BOM." %}</a> 
     10        <a href="/import/users/">{% trans "Click here to sponsor more users." %}</a> 
    1011    </div> 
    1112{% endblock %} 
  • branches/3D/openPLM/templates/users/sponsor.htm

    r595 r662  
    88        {% if obj.is_contributor or obj.is_administrator %} 
    99            <p> {% trans "Sponsor a new user" %} </p> 
     10            <a href="/import/users/">{% trans "Click here to sponsor several users from a CSV file." %}</a> 
     11 
    1012            {% with sponsor_form as form %} 
    1113                {% include "snippets/undo_form.htm" %} 
  • branches/3D/openPLM/urls.py

    r466 r662  
    3232        __import__("%s.models" % app, globals(), locals(), [], -1) 
    3333 
     34import openPLM.plmapp.search_indexes 
     35 
    3436from django.conf.urls.defaults import include, patterns 
    3537from openPLM.plmapp.views import * 
     
    4042from django.contrib import admin 
    4143admin.autodiscover() 
    42  
    43 # just a hack to prevent a KeyError 
    44 from haystack.models import SearchResult 
    45 def get_state(self): 
    46     ret_dict = self.__dict__.copy() 
    47     if 'searchsite' in ret_dict: 
    48         del(ret_dict['searchsite']) 
    49     del(ret_dict['log']) 
    50     return ret_dict 
    51 SearchResult.__getstate__ = get_state 
    5244 
    5345 
     
    132124    (r'management/delete/$', delete_management), 
    133125    (r'navigate/$', navigate), 
     126    (r'(?:files/|doc-cad/)?archive/$', download_archive), 
    134127) 
    135128 
  • branches/3D/openPLM/xapian_backend.py

    r595 r662  
    6969        """ 
    7070        colon = begin.find(':') 
    71         field_name = begin[:colon] 
    72         begin = begin[colon + 1:len(begin)] 
     71        if colon == -1: 
     72            field_name = "text" 
     73        else: 
     74            field_name = begin[:colon] or "text" 
     75            begin = begin[colon + 1:len(begin)] 
    7376        for field_dict in self.backend.schema: 
    7477            if field_dict['field_name'] == field_name: 
     
    591594            ) 
    592595         
    593         vrp = XHValueRangeProcessor(self) 
    594         qp.add_valuerangeprocessor(vrp) 
     596        #vrp = XHValueRangeProcessor(self) 
     597        #qp.add_valuerangeprocessor(vrp) 
    595598         
    596599        return qp.parse_query(query_string, flags) 
Note: See TracChangeset for help on using the changeset viewer.