- Timestamp:
- 01/18/12 11:59:51 (8 years ago)
- Location:
- branches/3D/openPLM
- Files:
-
- 41 edited
- 17 copied
Legend:
- Unmodified
- Added
- Removed
-
branches/3D/openPLM/help/fr/search.rst
r595 r662 7 7 #. sélectionner un type ; 8 8 #. entrer une requête ; 9 #. cliquer sur le bouton rechercher ;9 #. cliquer sur le bouton rechercher. 10 10 11 11 Exemples de requêtes : -
branches/3D/openPLM/media/css/help.css
r595 r662 187 187 188 188 ol.simple, ul.simple { 189 margin-bottom: 1em }189 padding-bottom: 1em } 190 190 191 191 ol.arabic { -
branches/3D/openPLM/media/css/openplm.css
r595 r662 3 3 /* *************************************************************************************** */ 4 4 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 16 html, body, div, span, applet, object, iframe, 17 h1, h2, h3, h4, h5, h6, p, blockquote, pre, 18 a, abbr, acronym, address, big, cite, code, 19 del, dfn, em, img, ins, kbd, q, s, samp, 20 small, strike, strong, sub, sup, tt, var, 21 b, u, i, center, 22 dl, dt, dd, ol, ul, li, 23 fieldset, form, label, legend, 24 table, caption, tbody, tfoot, thead, tr, th, td, 25 article, aside, canvas, details, embed, 26 figure, figcaption, footer, header, hgroup, 27 menu, nav, output, ruby, section, summary, 28 time, mark, audio, video { 29 margin: 0; 30 padding: 0; 31 border: 0; 32 /*font-size: 100%;*/ 33 /*font: inherit;*/ 34 vertical-align: baseline; 8 35 color: #eee; 9 36 } 37 /* HTML5 display-role reset for older browsers */ 38 article, aside, details, figcaption, figure, 39 footer, header, hgroup, menu, nav, section { 40 display: block; 41 } 42 body { 43 line-height: 1; 44 } 45 ol, ul { 46 list-style: none; 47 } 48 blockquote, q { 49 quotes: none; 50 } 51 blockquote:before, blockquote:after, 52 q:before, q:after { 53 content: ''; 54 content: none; 55 } 56 table { 57 border-collapse: collapse; 58 border-spacing: 0; 59 } 60 61 /*********************/ 10 62 11 63 body … … 364 416 width: 100%; 365 417 margin-top: 10px; 366 border-spacing: 3px; 418 border-spacing: 2px; 419 border-collapse: separate; 367 420 } 368 421 … … 498 551 } 499 552 500 553 li.Result div.summary em { 554 background-color: #fffcb6; 555 color: black; 556 font-style: normal; 557 } 501 558 /** input **/ 502 559 input[type=text], … … 606 663 607 664 #help-dialog h3 ~ * { 608 background-color: #343434; 665 padding-left: 1em; 666 } 667 #help-dialog h3 + * { 668 padding-top: 1em; 609 669 } 610 670 … … 622 682 } 623 683 624 #help-dialog .section * {684 #help-dialog .section *:not(h3) { 625 685 font-size: 14px; 626 } 627 686 background-color: #343434; 687 } 688 689 tt.docutils.literal { 690 color: black; 691 background-color: #fffcb6 !important; 692 } 628 693 629 694 /* lifecycle */ … … 642 707 } 643 708 709 form.archive_form { 710 float: right; 711 } 712 713 form.archive_form > * { 714 display: inline; 715 padding-left: 0.5em; 716 } 717 718 ul.errorlist { 719 display: block; 720 padding-left: 1em; 721 } 722 ul.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 41 41 .uploader *, 42 42 .button *{ 43 margin: 0;44 padding: 0;43 /*margin: 0;*/ 44 /*padding: 0;*/ 45 45 } 46 46 -
branches/3D/openPLM/media/js/navigate.js
r595 r662 67 67 var nw = $("#Navigate").innerWidth(); 68 68 var nh = $("#Navigate").innerHeight(); 69 console.log((nw / 2 - data.center_x) + "px");70 69 divNav.css({left: (nw / 2 - data.center_x) + "px", 71 70 top: (nh / 2 - data.center_y) + "px"}); … … 284 283 } 285 284 $("#FilterButton").button().click(function () { 285 $("#Navigate").showLoading(); 286 286 $.post(uri, 287 287 $("#FilterNav").find("form").serialize(), 288 288 function (data) { 289 289 update_nav(null, data); 290 $("#Navigate").hideLoading(); 290 291 }); 291 292 } ); -
branches/3D/openPLM/plmapp/base_views.py
r595 r662 281 281 else: 282 282 selected_object = get_obj(type_, reference, revision, request_dict.user) 283 # Defines a variable for background color selection284 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"290 283 qset = [] 291 284 # Builds, update and treat Search form … … 334 327 'link_creation' : False, 335 328 'attach' : (selected_object, False), 336 'class4div' : class_for_div,337 329 'obj' : selected_object, 338 330 }) … … 361 353 362 354 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()) 363 356 if request.method == 'POST' and request.POST: 364 357 form = FilterForm(request.POST) … … 367 360 elif has_session: 368 361 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: 372 365 form = FilterForm(initial) 373 366 request.session.update(initial) -
branches/3D/openPLM/plmapp/controllers/part.py
r595 r662 31 31 32 32 import openPLM.plmapp.models as models 33 from openPLM.plmapp.units import DEFAULT_UNIT 33 34 from openPLM.plmapp.controllers.plmobject import PLMObjectController 34 35 from openPLM.plmapp.controllers.base import get_controller … … 54 55 55 56 :raises: :exc:`ValueError` if *child* is already a child or a parent. 56 :raises: :exc:`ValueError` if *quantity* or *order* are negative.57 57 :raises: :exc:`.PermissionError` if :attr:`_user` is not the owner of 58 58 :attr:`object`. … … 88 88 return can_add 89 89 90 def add_child(self, child, quantity, order ):90 def add_child(self, child, quantity, order, unit=DEFAULT_UNIT): 91 91 """ 92 92 Adds *child* to *self*. … … 98 98 :param order: order 99 99 :type order: positive int 100 :param unit: a valid unit 100 101 101 102 :raises: :exc:`ValueError` if *child* is already a child or a parent. … … 120 121 link.quantity = quantity 121 122 link.order = order 123 link.unit = unit 122 124 link.save() 123 125 # records creation in history … … 147 149 self._save_histo("Delete - %s" % link.ACTION_NAME, "child : %s" % child) 148 150 149 def modify_child(self, child, new_quantity, new_order ):151 def modify_child(self, child, new_quantity, new_order, new_unit): 150 152 """ 151 153 Modifies information about *child*. … … 171 173 link = models.ParentChildLink.objects.get(parent=self.object, 172 174 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): 174 177 # do not make an update if it is useless 175 178 return … … 178 181 # make a new link 179 182 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) 181 185 details = "" 182 186 if link.quantity != new_quantity: … … 184 188 if link.order != new_order: 185 189 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) 186 192 self._save_histo("Modify - %s" % link.ACTION_NAME, details) 187 193 link2.save(force_insert=True) … … 254 260 quantity = form.cleaned_data["quantity"] 255 261 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) 257 264 258 265 def revise(self, new_revision): … … 260 267 new_controller = super(PartController, self).revise(new_revision) 261 268 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) 263 271 return new_controller 264 272 … … 322 330 else: 323 331 get_controller(document.type)(document, self._user).check_readable() 324 type(self)(document, self._user).check_readable()325 332 if self.is_document_attached(document): 326 333 raise ValueError("Document is already attached.") -
branches/3D/openPLM/plmapp/controllers/plmobject.py
r595 r662 118 118 except models.DelegationLink.DoesNotExist: 119 119 sponsor = user 120 for i in range( len(obj.lifecycle.to_states_list())- 1):120 for i in range(obj.lifecycle.nb_states - 1): 121 121 models.PLMObjectUserLink.objects.create(plmobject=obj, user=sponsor, 122 122 role=level_to_sign_str(i)) … … 214 214 215 215 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) 218 222 qset = self.plmobjectuserlink_plmobject.filter(user__in=users, 219 223 role=role) … … 227 231 raise PermissionError("The object is not editable") 228 232 229 @permission_required(role="owner")230 233 def revise(self, new_revision): 231 234 u""" 232 Makes a new revision 235 Makes a new revision: duplicates :attr:`object`. The duplicated 233 236 object's revision is *new_revision*. 234 237 235 238 Returns a controller of the new object. 236 239 """ 237 240 self.check_readable() 238 241 if not new_revision or new_revision == self.revision or \ 239 242 rx_bad_ref.search(new_revision): … … 256 259 def is_revisable(self, check_user=True): 257 260 """ 258 Returns True if :attr:`object` is revisable 261 Returns True if :attr:`object` is revisable: if :meth:`revise` can be 259 262 called safely. 260 263 261 If *check_user* is True (the default), it also checks if :attr:`_user` is262 the *owner* of :attr:`object`.264 If *check_user* is True (the default), it also checks if :attr:`_user` can 265 see the objects. 263 266 """ 264 267 # objects.get fails if a link does not exist … … 268 271 return False 269 272 except ObjectDoesNotExist: 270 return self.check_ permission("owner",False)273 return self.check_readable(False) 271 274 272 275 def get_previous_revisions(self): … … 377 380 pass 378 381 # check if the role is valid 379 max_level = len(self.lifecycle.to_states_list())- 1382 max_level = self.lifecycle.nb_states - 1 380 383 level = int(re.search(r"\d+", role).group(0)) 381 384 if level > max_level: -
branches/3D/openPLM/plmapp/csvimport.py
r474 r662 15 15 from openPLM.plmapp import models 16 16 from openPLM.plmapp.unicodecsv import UnicodeReader 17 from openPLM.plmapp.controllers .plmobject import PLMObjectController17 from openPLM.plmapp.controllers import PLMObjectController, UserController 18 18 from openPLM.plmapp.tasks import update_indexes 19 19 … … 353 353 354 354 parent.add_child(child, quantity, order) 355 356 class 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 355 416 356 417 #: Dictionary (name -> CSVImporter's subclass) of known :class:`CSVImporter` 357 IMPORTERS = {"csv" : PLMObjectsImporter, "bom" : BOMImporter } 358 418 IMPORTERS = {"csv" : PLMObjectsImporter, "bom" : BOMImporter, 419 "users" : UsersImporter} 420 -
branches/3D/openPLM/plmapp/forms.py
r595 r662 36 36 37 37 import openPLM.plmapp.models as m 38 from openPLM.plmapp.units import UNITS, DEFAULT_UNIT 38 39 from openPLM.plmapp.controllers import rx_bad_ref, DocumentController 39 40 from openPLM.plmapp.controllers.user import UserController … … 67 68 " to this group.") 68 69 69 def get_creation_form(user, cls=m.PLMObject, data=None, empty_allowed=False): 70 def 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 83 def 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 110 def 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 127 def get_creation_form(user, cls=m.PLMObject, data=None, start=0): 70 128 u""" 71 129 Returns a creation form suitable to creates an object … … 77 135 :class:`~plmapp.controllers.PLMObjectController`. 78 136 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. 80 141 """ 81 142 Form = get_creation_form.cache.get(cls) … … 83 144 fields = cls.get_creation_fields() 84 145 Form = modelform_factory(cls, fields=fields, exclude=('type', 'state')) 146 # replace textinputs with autocomplete inputs, see ticket #66 147 auto_complete_fields(Form, cls) 85 148 if issubclass(cls, m.PLMObject): 86 149 Form.clean_reference = _clean_reference … … 95 158 Form.clean = _clean 96 159 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) 98 164 if issubclass(cls, m.PLMObject): 99 165 # display only valid groups … … 110 176 fields = cls.get_modification_fields() 111 177 Form = modelform_factory(cls, fields=fields) 178 auto_complete_fields(Form, cls) 112 179 get_modification_form.cache[cls] = Form 113 180 if data: … … 225 292 get_search_form.cache = {} 226 293 227 from haystack.query import EmptySearchQuerySet228 from openPLM.plmapp.search import SmartSearchQuerySet229 230 294 class SimpleSearchForm(forms.Form): 231 295 q = forms.CharField(label=_("Query"), required=False) 232 296 233 297 def search(self, *models): 298 from haystack.query import EmptySearchQuerySet 299 from openPLM.plmapp.search import SmartSearchQuerySet 300 234 301 if self.is_valid(): 235 302 query = self.cleaned_data["q"].strip() … … 246 313 quantity = forms.FloatField() 247 314 order = forms.IntegerField() 315 unit = forms.ChoiceField(choices=UNITS, initial=DEFAULT_UNIT) 248 316 249 317 … … 265 333 class Meta: 266 334 model = m.ParentChildLink 267 fields = ["order", "quantity", " child", "parent"]335 fields = ["order", "quantity", "unit", "child", "parent",] 268 336 269 337 ChildrenFormset = modelformset_factory(m.ParentChildLink, … … 526 594 get_headers_formset = memoize(get_headers_formset, {}, 1) 527 595 596 from openPLM.plmapp.archive import ARCHIVE_FORMATS 597 class ArchiveForm(forms.Form): 598 format = forms.TypedChoiceField(choices=zip(ARCHIVE_FORMATS, ARCHIVE_FORMATS)) 599 -
branches/3D/openPLM/plmapp/mail.py
r473 r662 33 33 from django.conf import settings 34 34 from django.core.mail import EmailMultiAlternatives 35 from django.db.models import Model 35 from django.db.models import Model, Q 36 36 from django.template.loader import render_to_string 37 from django. contrib.contenttypes.models import ContentType37 from django.db.models.loading import get_model 38 38 from django.contrib.sites.models import Site 39 39 from celery.task import task 40 40 41 from openPLM.plmapp.models import User, DelegationLink, ROLE_OWNER 41 from openPLM.plmapp.models import User, DelegationLink, ROLE_OWNER, ROLE_SIGN 42 42 43 43 … … 45 45 recipients = set(users) 46 46 if hasattr(obj, "plmobjectuserlink_plmobject"): 47 manager = obj.plmobjectuserlink_plmobject 47 manager = obj.plmobjectuserlink_plmobject.order_by() 48 roles_filter = Q() 49 48 50 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)\ 52 58 .values_list("delegator", "delegatee") 53 54 55 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]: 57 63 if hasattr(obj, "owner"): 58 recipients.add(obj.owner .id)64 recipients.add(obj.owner_id) 59 65 elif isinstance(obj, User): 60 66 recipients.add(obj.id) … … 69 75 70 76 class 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 73 82 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"] 74 93 75 94 @classmethod 76 95 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) 78 97 79 98 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) 82 101 83 102 … … 130 149 The mail is sent in a separated thread. 131 150 """ 132 133 151 plmobject = unserialize(plmobject) 134 user = unserialize(user)135 subject = "[PLM] " + unicode(plmobject)136 152 recipients = get_recipients(plmobject, roles, users) 137 138 153 if recipients: 154 user = unserialize(user) 155 subject = "[PLM] " + unicode(plmobject) 139 156 ctx = { 140 157 "last_action" : last_action, … … 148 165 def do_send_mail(subject, recipients, ctx, template, blacklist=()): 149 166 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 150 178 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)154 179 ctx["site"] = Site.objects.get_current() 155 180 html_content = render_to_string(template + ".htm", ctx) … … 172 197 histories = serialize(histories) 173 198 user = CT.from_object(user) 174 do_send_histories_mail.delay(plmobject, roles, last_action, histories, user, blacklist,175 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 91 91 from django.utils.translation import ugettext_lazy as _ 92 92 from django.utils.translation import ugettext_noop 93 93 from django.forms.util import ErrorList 94 95 from openPLM.plmapp.units import UNITS, DEFAULT_UNIT 94 96 from openPLM.plmapp.lifecycle import LifecycleList 95 97 from openPLM.plmapp.utils import level_to_sign_str, memoize_noarg … … 175 177 mtime = models.DateTimeField(_("date of last modification"), auto_now=True) 176 178 179 def __init__(self, *args, **kwargs): 180 if "__fake__" not in kwargs: 181 super(GroupInfo, self).__init__(*args, **kwargs) 182 177 183 @property 178 184 def plmobject_url(self): … … 204 210 """ 205 211 fields = [] 206 for field in cls( ).attributes:212 for field in cls(__fake__=True).attributes: 207 213 if field not in cls.excluded_creation_fields(): 208 214 fields.append(field) … … 225 231 "Returns fields which should be displayed in a modification form" 226 232 fields = [] 227 for field in cls( ).attributes:233 for field in cls(__fake__=True).attributes: 228 234 if field not in cls.excluded_modification_fields(): 229 235 fields.append(field) … … 253 259 return u'State<%s>' % self.name 254 260 255 256 261 class Lifecycle(models.Model): 257 262 u""" … … 274 279 275 280 """ 281 276 282 name = models.CharField(max_length=50, primary_key=True) 277 283 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 278 293 279 294 def __unicode__(self): … … 284 299 Converts a Lifecycle to a :class:`LifecycleList` (a list of strings) 285 300 """ 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() 290 322 291 323 def __iter__(self): … … 330 362 self.rank) 331 363 332 364 @memoize_noarg 333 365 def get_default_lifecycle(): 334 366 u""" … … 337 369 return Lifecycle.objects.all()[0] 338 370 371 _default_states_cache = {} 339 372 def get_default_state(lifecycle=None): 340 373 u""" … … 345 378 if not lifecycle: 346 379 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 349 385 350 386 # PLMobjects … … 401 437 402 438 # key attributes 403 reference = models.CharField(_("reference"), max_length=50 )439 reference = models.CharField(_("reference"), max_length=50, db_index=True) 404 440 type = models.CharField(_("type"), max_length=50) 405 441 revision = models.CharField(_("revision"), max_length=50) … … 432 468 ordering = ["type", "reference", "revision"] 433 469 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 434 478 def __unicode__(self): 435 479 return u"%s<%s/%s/%s>" % (type(self).__name__, self.reference, self.type, … … 438 482 def _is_promotable(self): 439 483 """ 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 444 491 445 492 def is_promotable(self): … … 453 500 raise NotImplementedError() 454 501 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 455 511 @property 456 512 def is_editable(self): 457 513 """ 458 514 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 465 520 return current_rank < official_rank 466 521 … … 495 550 return False 496 551 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 497 557 @property 498 558 def attributes(self): … … 527 587 """ 528 588 fields = ["reference", "type", "revision", "lifecycle"] 529 for field in cls( ).attributes:589 for field in cls(__fake__=True).attributes: 530 590 if field not in cls.excluded_creation_fields(): 531 591 fields.append(field) … … 552 612 """ 553 613 fields = [] 554 for field in cls( ).attributes:614 for field in cls(__fake__=True).attributes: 555 615 if field not in cls.excluded_modification_fields(): 556 616 fields.append(field) … … 576 636 def is_promotable(self): 577 637 """ 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. 581 654 """ 582 655 if not self._is_promotable(): 583 656 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: 588 664 child = link.child 589 665 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.")) 592 669 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 593 682 return True 594 683 … … 725 814 if not self._is_promotable(): 726 815 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 728 823 729 824 @property 730 825 def menu_items(self): 731 826 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")) 733 829 return items 734 830 … … 911 1007 child = models.ForeignKey(Part, related_name="%(class)s_child") 912 1008 quantity = models.FloatField(default=lambda: 1) 1009 unit = models.CharField(max_length=4, choices=UNITS, 1010 default=lambda: DEFAULT_UNIT) 913 1011 order = models.PositiveSmallIntegerField(default=lambda: 1) 914 1012 end_time = models.DateTimeField(blank=True, null=True, default=lambda: None) … … 920 1018 return u"ParentChildLink<%s, %s, %f, %d>" % (self.parent, self.child, 921 1019 self.quantity, self.order) 1020 def get_shortened_unit(self): 1021 if self.unit == "-": 1022 return u"" 1023 return self.get_unit_display() 922 1024 923 1025 class DocumentPartLink(Link): … … 978 1080 delegator = models.ForeignKey(User, related_name="%(class)s_delegator") 979 1081 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) 981 1084 982 1085 class Meta: … … 1019 1122 plmobject = models.ForeignKey(PLMObject, related_name="%(class)s_plmobject") 1020 1123 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) 1022 1126 1023 1127 class Meta: -
branches/3D/openPLM/plmapp/navigate.py
r595 r662 151 151 self.graph.node_attr.update(self.NODE_ATTRIBUTES) 152 152 self.graph.edge_attr.update(self.EDGE_ATTRIBUTES) 153 self.title_to_nodes = {} 153 154 154 155 def set_options(self, options): … … 202 203 if self.options["prog"] == "twopi": 203 204 self.graph.graph_attr["ranksep"] = "1.2" 204 205 205 206 def _create_child_edges(self, obj, *args): 206 207 if self.options[OSR] and self.users_result: … … 211 212 continue 212 213 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) 214 216 self.graph.add_edge(obj.id, child.id, label) 215 217 self._set_node_attributes(child) … … 226 228 continue 227 229 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) 229 232 self.graph.add_edge(parent.id, obj.id, label) 230 233 self._set_node_attributes(parent) … … 240 243 continue 241 244 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, " ") 243 249 self._set_node_attributes(part) 244 250 … … 330 336 331 337 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 333 348 type_ = type(obj) 334 349 if issubclass(type_, PartController): … … 337 352 type_ = DocumentController 338 353 node.attr.update(self.TYPE_TO_ATTRIBUTES[type_]) 339 node.attr["URL"] = obj.plmobject_url + "navigate/" 340 node.attr["tooltip"] = "None" 354 341 355 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 343 362 if type_ == DocumentController: 344 node.attr["tooltip"] = "/ajax/thumbnails/" + get_path(obj)363 data["url"] = "/ajax/thumbnails/" + get_path(obj) 345 364 elif type_ == PartController and not self.options["doc"]: 346 365 if obj.get_attached_documents(): 347 366 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) 349 368 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 351 372 else: 352 373 node.attr["label"] = obj.name 353 374 node.attr["label"] += "\\n" + extra_label 375 # id is used by the javascript 354 376 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 356 379 357 380 def convert_map(self, map_string): … … 360 383 ajax_navigate = "/ajax/navigate/" + get_path(self.object) 361 384 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 362 387 left, top, x2, y2 = map(int, area.get("coords").split(",")) 363 388 width = x2 - left 364 389 height = y2 - top 365 390 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 366 392 id_ = "Nav-%s" % area.get("id") 367 393 div = ET.Element("div", id=id_, style=style) 368 394 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") 370 400 if url.startswith("/ajax/thumbnails/"): 371 401 thumbnails = ET.SubElement(div, "img", src="/media/img/search.png", … … 386 416 show_doc.set("class", "node_show_docs" + self.BUTTON_CLASS) 387 417 show_doc.set("onclick", "display_docs('%s', '%s', '%s');" % (id_, ajax_navigate, parts)) 418 # add the link 388 419 a = ET.SubElement(div, "a", href=area.get("href")) 389 420 span = ET.SubElement(a, "span") -
branches/3D/openPLM/plmapp/query_parser.py
r460 r662 30 30 pass 31 31 32 33 def 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 32 52 class Text(List): 33 53 34 54 def to_SQ(self): 35 55 if len(self) == 2: … … 41 61 text = text.strip().lower() 42 62 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() 43 67 if text.endswith("*"): 44 sq = SQ()45 68 text = text.rstrip("*") 46 69 items = split(text) 47 70 for item in items[:-1]: 48 sq &= SQ(**{ qualifier: item })71 sq &= convert_number(item, qualifier) 49 72 suffix = "*" if qualifier == "content" else "" 50 73 sq &= SQ(**{ qualifier + "__startswith" : items[-1]+suffix}) 51 return sq52 74 else: 53 return SQ(**{ qualifier : text }) 75 sq = convert_number(text, qualifier) 76 return sq 54 77 55 78 class Not(List): -
branches/3D/openPLM/plmapp/search_indexes.py
r595 r662 1 1 from django.conf import settings 2 3 from haystack.indexes import *4 from haystack import site5 6 import openPLM.plmapp.models as models7 8 2 from django.db.models import signals 9 3 from django.db.models.loading import get_model 10 4 5 from haystack import site 11 6 from haystack import indexes 7 from haystack.indexes import * 8 from haystack.models import SearchResult 12 9 10 import openPLM.plmapp.models as models 13 11 from openPLM.plmapp.tasks import update_index 12 13 # just a hack to prevent a KeyError 14 def 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 20 SearchResult.__getstate__ = get_state 14 21 15 22 ########################### … … 111 118 else: 112 119 return self.model.objects.all() 113 114 120 set_template_name(ModelIndex) 115 121 site.register(model, ModelIndex) 116 122 117 123 from subprocess import Popen, PIPE 124 import codecs 125 import os.path 126 text_files = set((".txt",)) 127 118 128 class DocumentFileIndex(QueuedModelSearchIndex): 119 129 text = CharField(document=True, use_template=True) … … 125 135 126 136 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() 129 146 130 147 site.register(models.DocumentFile, DocumentFileIndex) -
branches/3D/openPLM/plmapp/tasks.py
r595 r662 6 6 7 7 from django.db.models.loading import get_model 8 9 from haystack import site10 8 11 9 from celery.task import task … … 43 41 @task(default_retry_delay = 60, max_retries = 10) 44 42 def update_index(app_name, model_name, pk, **kwargs): 43 from haystack import site 44 import openPLM.plmapp.search_indexes 45 45 46 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) 47 48 search_index = site.get_index(model_class) 48 49 search_index.update_object(instance) … … 50 51 @task(default_retry_delay = 60, max_retries = 10) 51 52 def update_indexes(instances): 53 from haystack import site 54 import openPLM.plmapp.search_indexes 55 52 56 for app_name, model_name, pk in instances: 53 57 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) 55 59 search_index = site.get_index(model_class) 56 60 search_index.update_object(instance) -
branches/3D/openPLM/plmapp/tests/__init__.py
r439 r662 23 23 ################################################################################ 24 24 25 # import custom application models 26 from django.conf import settings 27 for app in settings.INSTALLED_APPS: 28 if app.startswith("openPLM"): 29 __import__("%s.models" % app, globals(), locals(), [], -1) 30 31 import openPLM.plmapp.search_indexes 32 25 33 from openPLM.plmapp.tests.filehandlers import * 26 34 from openPLM.plmapp.tests.controllers import * … … 30 38 from openPLM.plmapp.tests.api import * 31 39 from openPLM.plmapp.tests.csvimport import * 40 from openPLM.plmapp.tests.archive import * 32 41 33 42 import openPLM.plmapp.models -
branches/3D/openPLM/plmapp/tests/ajax.py
r433 r662 95 95 data = self.post("/ajax/add_child/%d/" % self.controller.id, 96 96 type=p2.type, reference=p2.reference, revision=p2.revision, 97 quantity="10", order="10" )97 quantity="10", order="10", unit="g") 98 98 self.assertEqual("ok", data["result"]) 99 99 -
branches/3D/openPLM/plmapp/tests/api.py
r472 r662 61 61 62 62 def test_search_editable_only(self): 63 self.attach_to_official_document() 63 64 self.controller.promote() 64 65 data = self.get("/api/search/true/", type="Part", reference="Pa*") -
branches/3D/openPLM/plmapp/tests/base.py
r474 r662 6 6 from openPLM.plmapp.models import GroupInfo 7 7 from openPLM.plmapp.controllers import PLMObjectController 8 9 import os.path10 import shutil11 from django.conf import settings12 8 13 9 class BaseTestCase(TestCase): … … 45 41 46 42 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 49 45 super(BaseTestCase, self).tearDown() 50 46 -
branches/3D/openPLM/plmapp/tests/controllers/part.py
r472 r662 29 29 import datetime 30 30 31 from openPLM.plmapp.controllers import PLMObjectController, PartController 31 from openPLM.plmapp.controllers import PLMObjectController, PartController, \ 32 DocumentController 33 import openPLM.plmapp.exceptions as exc 34 import openPLM.plmapp.models as models 35 from openPLM.plmapp.lifecycle import LifecycleList 32 36 33 37 from openPLM.plmapp.tests.controllers.plmobject import ControllerTest … … 48 52 self.controller4 = self.CONTROLLER.create("aPart4", self.TYPE, "a", 49 53 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) 50 60 51 61 def test_add_child(self): … … 102 112 103 113 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") 106 116 children = self.controller.get_children() 107 117 level, link = children[0] 108 118 self.assertEqual(link.quantity, 3) 109 119 self.assertEqual(link.order, 5) 120 self.assertEqual(link.unit, "kg") 110 121 111 122 def test_delete_child(self): … … 152 163 153 164 def test_is_promotable1(self): 165 """Tests promotion from draft state, an official document is attached.""" 154 166 self.failUnless(self.controller.is_promotable()) 155 167 156 168 def test_is_promotable2(self): 169 """Tests promotion from official state.""" 157 170 self.controller.promote() 158 171 self.failUnless(self.controller.is_promotable()) 159 172 160 173 def test_is_promotable3(self): 174 """Tests promotions with an official child.""" 175 self.controller2.promote() 161 176 self.controller.add_child(self.controller2, 10, 15) 162 177 self.failUnless(self.controller.is_promotable()) 163 178 164 179 def test_is_promotable4(self): 180 """Tests promotion from official state, with a deprecated child.""" 165 181 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 34 34 import openPLM.plmapp.models as models 35 35 from openPLM.plmapp.controllers import PLMObjectController 36 from openPLM.plmapp.lifecycle import LifecycleList37 36 38 37 from openPLM.plmapp.tests.base import BaseTestCase … … 48 47 self.assertEqual(controller.name, "") 49 48 self.assertEqual(controller.type, self.TYPE) 50 self.assertEqual(type(controller.object),51 models.get_all_plmobjects()[self.TYPE])52 49 type_ = models.get_all_plmobjects()[self.TYPE] 50 self.assertEqual(type(controller.object), type_) 53 51 obj = type_.objects.get(reference=controller.reference, 54 52 revision=controller.revision, type=controller.type) … … 126 124 self.assertRaises(ValueError, setattr, controller, "state", "draft") 127 125 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 = lc139 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 147 126 def test_revise(self): 148 127 """ … … 158 137 self.assertEqual(getattr(controller, attr), getattr(rev, attr)) 159 138 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 160 148 def test_revise_error1(self): 161 "Revision : error : empty n ame"149 "Revision : error : empty new revision" 162 150 controller = self.create("Part1") 163 151 self.assertRaises(exc.RevisionError, controller.revise, "") -
branches/3D/openPLM/plmapp/tests/csvimport.py
r475 r662 1 1 2 import os.path3 import shutil4 2 import cStringIO, StringIO 5 3 from collections import defaultdict 6 4 7 from django.conf import settings8 5 from django.core import mail 9 6 from django.contrib.auth.models import User 10 7 from django.test import TransactionTestCase 11 from django.core.management import call_command12 8 13 9 from celery.signals import task_prerun 14 10 11 import openPLM.plmapp.models as models 15 12 from openPLM.plmapp.models import GroupInfo, PLMObject, ParentChildLink 16 13 from openPLM.plmapp.csvimport import PLMObjectsImporter, BOMImporter,\ 17 CSVImportError 14 CSVImportError, UsersImporter 18 15 from openPLM.plmapp.base_views import get_obj 19 16 from openPLM.plmapp.unicodecsv import UnicodeWriter … … 45 42 self.client.post("/login/", {'username' : 'user', 'password' : 'password'}) 46 43 task_prerun.connect(self.task_sent_handler) 47 call_command("rebuild_index", interactive=False, verbosity=0)48 44 49 45 def task_sent_handler(self, sender=None, task_id=None, task=None, args=None, … … 54 50 super(CSVImportTestCase, self).tearDown() 55 51 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 59 55 def get_valid_rows(self): 60 56 return [[u'Type', … … 249 245 self.assertEquals("SP1", sp1.name) 250 246 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 1 1 # from: http://djangosnippets.org/snippets/2211/ by cronosa 2 from django.test.simple import DjangoTestSuiteRunner #@UnresolvedImport 2 import os 3 3 import logging 4 4 from django.conf import settings 5 5 EXCLUDED_APPS = getattr(settings, 'TEST_EXCLUDE', []) 6 from django.test.simple import DjangoTestSuiteRunner 7 from django_xml_test_runner.xmltestrunner import XMLTestSuiteRunner 6 8 7 class OpenPLMTestSuiteRunner(DjangoTestSuiteRunner): 9 if os.environ.get("TEST_OUTPUT", "stdin") == "xml": 10 TestSuiteRunner = XMLTestSuiteRunner 11 else: 12 TestSuiteRunner= DjangoTestSuiteRunner 13 14 class OpenPLMTestSuiteRunner(TestSuiteRunner): 8 15 def __init__(self, *args, **kwargs): 9 16 from django.conf import settings -
branches/3D/openPLM/plmapp/tests/views.py
r474 r662 76 76 return response 77 77 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) 78 90 79 91 class ViewTest(CommonViewTest): … … 98 110 "state" : m.get_default_state().pk, 99 111 }) 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) 102 115 obj = m.PLMObject.objects.get(type=self.TYPE, reference="mapart", revision="a") 103 116 self.assertEqual(obj.id, response.context["obj"].id) … … 125 138 126 139 def test_lifecycle(self): 140 self.attach_to_official_document() 127 141 response = self.get(self.base_url + "lifecycle/") 128 142 lifecycles = tuple(response.context["object_lifecycle"]) … … 328 342 response = self.client.post(self.base_url + "BOM-child/add/", 329 343 {"type": "Part", "reference":"c1", "revision":"a", 330 "quantity" : 10, "order" : 10})344 "quantity" : 10, "order" : 10, "unit" : "m"}) 331 345 self.assertEquals(1, len(self.controller.get_children())) 332 346 … … 345 359 'form-0-parent' : self.controller.id, 346 360 'form-0-quantity' : '45.0', 361 'form-0-unit' : 'cm', 347 362 } 348 363 response = self.post(self.base_url + "BOM-child/edit/", data) … … 522 537 return sorted(l, key=lambda x: x.id) 523 538 524 from django.core.management import call_command525 539 class SearchViewTestCase(CommonViewTest): 526 540 … … 533 547 534 548 def search(self, request, type=None): 535 # rebuild the index536 call_command("rebuild_index", interactive=False, verbosity=0)537 549 538 550 query = self.get_query(request) … … 641 653 self.assertTrue(c.object not in results) 642 654 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 643 665 def test_search_all(self): 644 666 for i in xrange(6): … … 649 671 650 672 def test_search_not(self): 673 self.controller.name = "abcdef" 674 self.controller.save() 651 675 results = self.search("NOT %s" % self.controller.name, self.TYPE) 652 676 self.assertEqual([], results) -
branches/3D/openPLM/plmapp/views/ajax.py
r595 r662 81 81 results = cls.objects.filter(**{"%s__icontains" % field : term}) 82 82 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])) 84 84 return HttpResponse(json, mimetype='application/json') 85 85 … … 139 139 child = get_obj_from_form(form, request.user) 140 140 part.add_child(child, form.cleaned_data["quantity"], 141 form.cleaned_data["order"]) 141 form.cleaned_data["order"], 142 form.cleaned_data["unit"]) 142 143 return {"result" : "ok"} 143 144 else: -
branches/3D/openPLM/plmapp/views/api.py
r368 r662 279 279 fields = [] 280 280 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 ) 286 290 data["type"] = field_to_type(field) 287 291 if hasattr(field, "choices"): -
branches/3D/openPLM/plmapp/views/main.py
r595 r662 63 63 from django.forms import HiddenInput 64 64 65 from openPLM.plmapp.archive import generate_archive 65 66 from openPLM.plmapp.exceptions import ControllerError, PermissionError 66 67 import openPLM.plmapp.models as models … … 127 128 url = u"/%s/%s/attributes/" % (obj_type.lower(), obj_ref) 128 129 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) 130 133 return HttpResponsePermanentRedirect(iri_to_uri(url)) 131 134 … … 281 284 obj.add_child(child_obj, 282 285 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"]) 284 288 return HttpResponseRedirect(obj.plmobject_url + "BOM-child/") 285 289 else: 286 290 add_child_form = AddChildForm() 287 ctx .update({'current_page':'BOM-child'})291 ctx['current_page'] = 'BOM-child' 288 292 ctx.update({'link_creation': True, 289 293 'add_child_form': add_child_form, … … 347 351 else: 348 352 formset = get_doc_cad_formset(obj) 353 archive_form = forms.ArchiveForm() 349 354 ctx.update({'current_page':'doc-cad', 350 355 'object_doc_cad': obj.get_attached_documents(), 356 'archive_form' : archive_form, 351 357 'doc_cad_formset': formset}) 352 358 return r2r('DisplayObjectDocCad.htm', ctx, request) … … 450 456 else: 451 457 formset = get_file_formset(obj) 458 archive_form = forms.ArchiveForm() 452 459 ctx.update({'current_page':'files', 453 'file_formset': formset}) 460 'file_formset': formset, 461 'archive_form' : archive_form, 462 }) 454 463 return r2r('DisplayObjectFiles.htm', ctx, request) 455 464 … … 605 614 606 615 obj, ctx = get_generic_data(request) 607 616 608 617 if request.method == 'GET': 609 type_form = TypeForm (request.GET)618 type_form = TypeFormWithoutUser(request.GET) 610 619 if type_form.is_valid(): 611 620 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) 620 623 elif request.method == 'POST': 621 type_form = TypeForm (request.POST)624 type_form = TypeFormWithoutUser(request.POST) 622 625 if type_form.is_valid(): 623 626 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] 629 628 creation_form = get_creation_form(request.user, cls, request.POST) 630 629 if creation_form.is_valid(): … … 633 632 controller = controller_cls.create_from_form(creation_form, user) 634 633 return HttpResponseRedirect(controller.plmobject_url) 635 ctx.update({ 'class4div': class_for_div,636 637 638 634 ctx.update({ 635 'creation_form': creation_form, 636 'object_type': type_form.cleaned_data["type"], 637 }) 639 638 return r2r('DisplayObject4creation.htm', ctx, request) 640 639 641 640 ########################################################################################## 642 @handle_errors 641 @handle_errors(undo="../attributes/") 643 642 def modify_object(request, obj_type, obj_ref, obj_revi): 644 643 """ … … 849 848 def download(request, docfile_id, filename=""): 850 849 """ 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. 853 851 854 852 :param request: :class:`django.http.QueryDict` … … 869 867 response['Content-Disposition'] = 'attachment; filename="%s"' % name 870 868 return response 871 869 870 @handle_errors 871 def 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 872 906 ########################################################################################## 873 907 @handle_errors -
branches/3D/openPLM/settings_tests.py
r599 r662 3 3 # sqlite version 4 4 5 import sys 5 6 import os.path 6 7 … … 15 16 16 17 DATABASE_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.18 DATABASE_NAME = 'database_tests.db' # Or path to database file if using sqlite3. 19 DATABASE_USER = '' # Not used with sqlite3. 20 DATABASE_PASSWORD = '' # Not used with sqlite3. 21 DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 21 22 DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 22 23 … … 40 41 # Absolute path to the directory that holds media. 41 42 # Example: "/home/media/media.lawrence.com/" 42 MEDIA_ROOT = ' /home/linux/openPLM/branches/3D/openPLM/media/'43 MEDIA_ROOT = 'media/' 43 44 44 45 # URL that handles the media served from MEDIA_ROOT. Make sure to use a … … 57 58 # List of callables that know how to import templates from various sources. 58 59 TEMPLATE_LOADERS = ( 60 ('django.template.loaders.cached.Loader', ( 59 61 'django.template.loaders.filesystem.load_template_source', 60 62 'django.template.loaders.app_directories.load_template_source', 63 )), 61 64 # 'django.template.loaders.eggs.load_template_source', 62 65 ) … … 82 85 # Always use forward slashes, even on Windows. 83 86 # 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", 86 89 ) 87 90 … … 96 99 'djcelery', 97 100 'haystack', 98 'south',99 101 'openPLM.plmapp', 100 102 # you can add your application after this line … … 103 105 'openPLM.cae', 104 106 'openPLM.office', 105 'openPLM.bicycle',106 107 'openPLM.document3D', 107 108 ) … … 124 125 125 126 #: directory that stores documents. Make sure to use a trailing slash. 126 DOCUMENTS_DIR = "/ home/linux/openPLM/branches/3D/openPLM/docs/" #aquica127 DOCUMENTS_DIR = "/tmp/docs/" #aquica 127 128 THUMBNAILS_DIR = os.path.join(MEDIA_ROOT, "thumbnails/") 128 129 #: directory that stores thumbnails. Make sure to use a trailing slash. … … 150 151 151 152 # search stuff 153 if "rebuild_index" not in sys.argv: 154 HAYSTACK_ENABLE_REGISTRATIONS = False 152 155 HAYSTACK_SITECONF = 'openPLM.plmapp.search_sites' 153 156 HAYSTACK_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 158 HAYSTACK_XAPIAN_PATH = ":memory:" 159 # the memory backend does not support spelling 160 HAYSTACK_INCLUDE_SPELLING = False 156 161 EXTRACTOR = os.path.abspath(os.path.join(os.path.dirname(__file__), "bin", "extractor.sh")) 157 162 … … 165 170 COMPANY = "company" 166 171 172 TEST_RUNNER = "openPLM.plmapp.tests.runner.OpenPLMTestSuiteRunner" 173 TEST_OUTPUT_DIR = "tests_results" 174 -
branches/3D/openPLM/templates/BaseDisplayHomePage.htm
r595 r662 25 25 <script type="text/javascript" src="/media/js/panels.js"></script> 26 26 <script type="text/javascript" src="/media/js/help.js"></script> 27 <script type="text/javascript" src="/media/js/confirm.js"></script> 27 28 28 29 … … 61 62 <li class="{{"Button"|button:"corner-left"}}" id="NavigateButton"> 62 63 {% 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/"> 68 65 <span class="ui-button-text">{% trans "NAVIGATE" %}</span> 69 66 </a> -
branches/3D/openPLM/templates/DisplayObject4modification.htm
r595 r662 24 24 {% endfor %} 25 25 <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> 27 30 </tr> 28 31 </table> -
branches/3D/openPLM/templates/DisplayObjectChild.htm
r486 r662 32 32 <th class="Content" style="width:50px"> {% trans "Ord." %} </th> 33 33 <th class="Content" style="width:50px"> {% trans "Qty" %}</th> 34 <th class="Content" style="width:50px"> {% trans "Unit" %}</th> 34 35 <th class="Content"> {{ obj.reference }} </th> 35 36 <th class="Content"> {{ obj.revision }} </th> … … 46 47 <td class="Content"> {{ link.order }} </td> 47 48 <td class="Content"> {{ link.quantity }} </td> 49 <td class="Content"> {{ link.get_unit_display }} </td> 48 50 <td class="Content"> 49 51 <a href="/object/{{link.child.type}}/{{link.child.reference}}/{{link.child.revision}}/"> -
branches/3D/openPLM/templates/DisplayObjectChildEdit.htm
r486 r662 11 11 <th class="Content">{% trans "Order" %}</th> 12 12 <th class="Content">{% trans "Quantity" %} </th> 13 <th class="Content" style="width:50px"> {% trans "Unit" %}</th> 13 14 <th class="Content"> {{ obj.reference }} </th> 14 15 <th class="Content"> {{ obj.revision }} </th> … … 25 26 <td class="Content"> {{ form.order }} </td> 26 27 <td class="Content"> {{ form.quantity }} </td> 28 <td class="Content" style="width:50px"> {{ form.unit }} </td> 27 29 <td class="Content"> 28 30 <a href="{{form.instance.child.plmobject_url}}"> -
branches/3D/openPLM/templates/DisplayObjectDocCad.htm
r486 r662 5 5 6 6 {% 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 %} 7 13 <form method="post" action=""> 8 14 {{ doc_cad_formset.management_form }} … … 11 17 <span class="ui-button-text">{% trans "Attach another document" %}</span> 12 18 </a> 13 {% if doc_cad_formset.forms%}19 {% if object_doc_cad.count > 0 %} 14 20 <input type="submit" class="{{"Button"|button}}" value="{% trans "DISCONNECT" %}"/> 15 21 {% endif %} -
branches/3D/openPLM/templates/DisplayObjectFiles.htm
r521 r662 5 5 6 6 {% 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 %} 7 13 <form method="POST" action=""> 8 14 {{ file_formset.management_form }} … … 12 18 <span class="ui-button-text">{% trans "ADD" %}</span> 13 19 </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 %} 15 23 {% endif %} 16 24 <table class="Content"> 25 {% for form in file_formset.forms %} 17 26 <tr class="Content"> 18 {% for form in file_formset.forms %}19 27 {{ form.id }} 20 28 {{ form.document }} -
branches/3D/openPLM/templates/DisplayObjectLifecycle.htm
r595 r662 5 5 6 6 {% 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> 10 20 {% 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> 13 30 {% endif %} 14 31 </form> 15 16 17 32 <table class="Content"> 33 <tr class="Content"> 34 {% for status, is_current_state in object_lifecycle %} 18 35 <td class="Content status Button ui-state-default ui-button-text-only 19 36 {% if is_current_state %} 20 37 ui-state-active active 21 38 {% else %} 22 39 ui-state-inactive 23 40 {% endif %} 24 41 "> 25 26 27 28 29 30 31 42 {{status|upper}}</td> 43 {% if not forloop.last %} 44 <td class="Content arrow">===></td> 45 {% endif %} 46 {% endfor %} 47 </tr> 48 </table> 32 49 {% endblock %} 33 50 -
branches/3D/openPLM/templates/Navigate.htm
r595 r662 5 5 6 6 <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" /> 7 8 {% endblock css %} 8 9 9 10 {% block scripts %} 10 11 <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> 11 13 <script type="text/javascript" src="/media/js/navigate.js"></script> 12 14 -
branches/3D/openPLM/templates/import/done.htm
r486 r662 8 8 <a href="/import/csv/">{% trans "Click here to import a new CSV file." %}</a> 9 9 <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> 10 11 </div> 11 12 {% endblock %} -
branches/3D/openPLM/templates/users/sponsor.htm
r595 r662 8 8 {% if obj.is_contributor or obj.is_administrator %} 9 9 <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 10 12 {% with sponsor_form as form %} 11 13 {% include "snippets/undo_form.htm" %} -
branches/3D/openPLM/urls.py
r466 r662 32 32 __import__("%s.models" % app, globals(), locals(), [], -1) 33 33 34 import openPLM.plmapp.search_indexes 35 34 36 from django.conf.urls.defaults import include, patterns 35 37 from openPLM.plmapp.views import * … … 40 42 from django.contrib import admin 41 43 admin.autodiscover() 42 43 # just a hack to prevent a KeyError44 from haystack.models import SearchResult45 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_dict51 SearchResult.__getstate__ = get_state52 44 53 45 … … 132 124 (r'management/delete/$', delete_management), 133 125 (r'navigate/$', navigate), 126 (r'(?:files/|doc-cad/)?archive/$', download_archive), 134 127 ) 135 128 -
branches/3D/openPLM/xapian_backend.py
r595 r662 69 69 """ 70 70 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)] 73 76 for field_dict in self.backend.schema: 74 77 if field_dict['field_name'] == field_name: … … 591 594 ) 592 595 593 vrp = XHValueRangeProcessor(self)594 qp.add_valuerangeprocessor(vrp)596 #vrp = XHValueRangeProcessor(self) 597 #qp.add_valuerangeprocessor(vrp) 595 598 596 599 return qp.parse_query(query_string, flags)
Note: See TracChangeset
for help on using the changeset viewer.