Changeset 870 in main for branches


Ignore:
Timestamp:
03/09/12 10:57:38 (8 years ago)
Author:
pcosquer
Message:

merge change from trunk rev[867]

Location:
branches/3D/openPLM
Files:
58 edited
10 copied

Legend:

Unmodified
Added
Removed
  • branches/3D/openPLM/bin/extractor.sh

    r410 r870  
    77# errors are silently passed 
    88 
    9 # TODO: remove noise ("nieXXX:") 
    10 /usr/lib/tracker/tracker-extract  -v 1 -f $1 2> /dev/null 
     9filename=$1 
     10ext=${filename##*.} 
     11ext=`echo $ext | tr '[:upper:]' '[:lower:]'` 
    1112 
     13case "$ext" in  
     14    pdf) 
     15        pdftotext -nopgbrk "$filename" - 2> /dev/null 
     16        ;; 
     17     
     18    html|xhtml|htm) 
     19        html2text "$filename" 2> /dev/null 
     20        ;; 
     21 
     22    odt|ods|odp|odg) 
     23        odt2txt "$filename" 2> /dev/null 
     24        ;; 
     25 
     26    docx|xlsx|pptx) 
     27        openxmlinfo words "$filename" 2> /dev/null 
     28        ;; 
     29     
     30    doc) 
     31        antiword "$filename" 2> /dev/null 
     32        ;; 
     33 
     34    xls) 
     35        xls2csv "$filename" 2> /dev/null 
     36        ;; 
     37    ppt) 
     38        catppt "$filename" 2> /dev/null 
     39        ;; 
     40    stp|step) 
     41        grep -P -o "(?<=PRODUCT\(')([^']+)" $filename 
     42        ;; 
     43    *) ;; 
     44esac 
     45 
  • branches/3D/openPLM/cad/models.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/cad/tests.py

    r472 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/cad/urls.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/cad/views.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/cae/models.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/cae/tests.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/cae/views.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/check_modules.py

    r465 r870  
    1919# 
    2020#    You should have received a copy of the GNU General Public License 
    21 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     21#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    2222# 
    2323# Ce fichier fait parti d' openPLM. 
  • branches/3D/openPLM/computer/models.py

    r674 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/computer/tests.py

    r472 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919#     
    2020# Contact : 
  • branches/3D/openPLM/computer/views.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/locale/fr/LC_MESSAGES/django.po

    r817 r870  
    88"Project-Id-Version: PACKAGE VERSION\n" 
    99"Report-Msgid-Bugs-To: \n" 
    10 "POT-Creation-Date: 2012-02-27 13:56+0100\n" 
     10"POT-Creation-Date: 2012-03-06 18:20+0100\n" 
    1111"PO-Revision-Date: 2011-11-21 11:12+0100\n" 
    1212"Last-Translator: Pierre Cosquer <pcosquer@linobject.com>\n" 
    13 "Language-Team: LANGUAGE <LL@li.org>\n" 
    14 "Language: \n" 
     13"Language-Team: LANGUAGE <openplm_translate@list.openplm.org>\n" 
    1514"MIME-Version: 1.0\n" 
    1615"Content-Type: text/plain; charset=UTF-8\n" 
    1716"Content-Transfer-Encoding: 8bit\n" 
    18  
    19 #: computer/models.py:48 
     17"Language: \n" 
     18 
     19#: computer/models.py:48 phone/models.py:48 
    2020msgid "supplier" 
    2121msgstr "fournisseur" 
    2222 
    23 #: computer/models.py:48 
     23#: computer/models.py:48 phone/models.py:48 
    2424msgid "tech_details" 
    25 msgstr "détail technique" 
     25msgstr "détails techniques" 
    2626 
    2727#: gdoc/templates/gdoc_files.html:8 
    2828msgid "You cannot add a file to a GoogleDocument." 
    29 msgstr "" 
     29msgstr "Vous ne pouvez pas télécharger un fichier dans un GoogleDocument" 
    3030 
    3131#: gdoc/templates/gdoc_files.html:12 
    3232msgid "" 
    3333"\n" 
    34 "            You cannot access to this Google Document. If you did not " 
    35 "created\n" 
    36 "            this document, you can ask its owner to grant you the " 
    37 "permission\n" 
    38 "            to read this file in Google Document.\n" 
    39 "        " 
    40 msgstr "" 
     34"You cannot access to this Google Document. If you did not created\n" 
     35"this document, you can ask its owner to grant you the permission\n" 
     36"to read this file in Google Document.\n" 
     37msgstr "" 
     38"\n" 
     39"Vous ne pouvez pas avoir accès à ce document Google. Si vous n'avez pas créé\n" 
     40"ce document, vous pouvez demander à son propriétaire de vous donner la permission\n" 
     41"de lire ce fichier dans Google Document.\n" 
    4142 
    4243#: gdoc/templates/gdoc_files.html:21 
    4344msgid "Click here to edit the file in Google Document ." 
    44 msgstr "" 
     45msgstr "Cliquez ici pour éditer le fichier dans Google Document." 
    4546 
    4647#: gdoc/templates/gdoc_files.html:24 
    4748msgid "Click here to download the file from Google." 
    48 msgstr "" 
    49  
    50 #: pdfgen/templates/attributes.xhtml:19 
    51 #, fuzzy 
    52 msgid "User" 
    53 msgstr "Identifiant" 
    54  
    55 #: pdfgen/templates/attributes.xhtml:21 
    56 #: templates/groups/pending_invitations.html:7 
    57 msgid "Group" 
    58 msgstr "" 
     49msgstr "Cliquez ici pour télécharger le fichier depuis Google." 
    5950 
    6051#: pdfgen/templates/select_pdf.html:8 
    6152msgid "" 
    6253"\n" 
    63 "            Select PDF files that will be merged into a unique PDF\n" 
    64 "            and click on the Download button.\n" 
    65 "        " 
    66 msgstr "" 
     54"Select PDF files that will be merged into a unique PDF\n" 
     55"and click on the Download button.\n" 
     56msgstr "" 
     57"\n" 
     58"Sélectionnez les fichiers pdf qui seront fusionnés dans un pdf unique\n" 
     59"et cliquez sur le bouton Télécharger.\n" 
    6760 
    6861#: pdfgen/templates/select_pdf.html:17 
    6962msgid "Download" 
    70 msgstr "" 
     63msgstr "Télécharger" 
    7164 
    7265#: pdfgen/templates/select_pdf_doc.html:16 
    7366msgid "There are no PDF files." 
    74 msgstr "" 
     67msgstr "Il n'y a pas de fichiers pdf." 
    7568 
    7669#: pdfgen/templates/select_pdf_part.html:19 
    7770msgid "Level:" 
    78 msgstr "" 
    79  
    80 #: pdfgen/templates/summary.xhtml:18 
    81 msgid "File" 
    82 msgstr "" 
    83  
    84 #: pdfgen/templates/summary.xhtml:34 
    85 #, fuzzy 
    86 msgid "State" 
    87 msgstr "état" 
    88  
    89 #: pdfgen/templates/summary.xhtml:35 
    90 msgid "User who promoted/demoted the object" 
    91 msgstr "" 
    92  
    93 #: pdfgen/templates/summary.xhtml:36 
    94 msgid "Date" 
    95 msgstr "" 
     71msgstr "Niveau:" 
    9672 
    9773#: plmapp/base_views.py:128 
     
    515491"Bad group, check that the group exists and that you belong to this group." 
    516492msgstr "" 
     493"Mauvais groupe, vérifiez que ce groupe existe et que vous faites bien parti " 
     494"de ce groupe." 
    517495 
    518496#: plmapp/forms.py:158 
    519 #, fuzzy 
    520497msgid "An object with the same type, reference and revision already exists" 
    521 msgstr "Vous ne pouvez pas reviser car une révision plus récente existe déjà" 
     498msgstr "Un objet avec le même type, la même référence et la même révision existe déjà" 
    522499 
    523500#: plmapp/forms.py:160 
    524 #, fuzzy 
    525 msgid "" 
    526 "An object with the same type and revision exists, you may consider to revise " 
    527 "it." 
    528 msgstr "Vous ne pouvez pas reviser car une révision plus récente existe déjà" 
     501msgid "" 
     502"An object with the same type and revision exists, you may consider to revise it." 
     503msgstr "Un objet avec le même type, la même référence et la même révision existe, vous devriez réfléchir à le réviser." 
    529504 
    530505#: plmapp/forms.py:222 
    531506msgid "Select a type" 
    532 msgstr "" 
     507msgstr "Sélectionnez un type" 
    533508 
    534509#: plmapp/forms.py:317 
     
    536511msgstr "Requête" 
    537512 
    538 #: plmapp/forms.py:515 
     513#: plmapp/forms.py:540 
    539514msgid "only search results" 
    540515msgstr "seulement les résultats de recherche" 
    541516 
    542 #: plmapp/forms.py:516 
     517#: plmapp/forms.py:541 
    543518msgid "Hierarchical" 
    544519msgstr "Hiérarchique" 
    545520 
    546 #: plmapp/forms.py:517 
     521#: plmapp/forms.py:542 
    547522msgid "Radial 1" 
    548523msgstr "" 
    549524 
    550 #: plmapp/forms.py:518 
     525#: plmapp/forms.py:543 
    551526msgid "Radial 2" 
    552527msgstr "" 
    553528 
    554 #: plmapp/forms.py:521 
     529#: plmapp/forms.py:546 
    555530msgid "layout" 
    556531msgstr "disposition" 
    557532 
    558 #: plmapp/forms.py:528 
     533#: plmapp/forms.py:553 
    559534msgid "child" 
    560535msgstr "enfants" 
    561536 
    562 #: plmapp/forms.py:529 plmapp/models.py:684 
     537#: plmapp/forms.py:554 plmapp/models.py:684 
    563538msgid "parents" 
    564539msgstr "parents" 
    565540 
    566 #: plmapp/forms.py:530 plmapp/forms.py:550 
     541#: plmapp/forms.py:555 plmapp/forms.py:575 
    567542msgid "doc" 
    568543msgstr "doc" 
    569544 
    570 #: plmapp/forms.py:531 plmapp/forms.py:537 plmapp/forms.py:547 
     545#: plmapp/forms.py:556 plmapp/forms.py:562 plmapp/forms.py:572 
    571546#: plmapp/models.py:177 plmapp/models.py:230 plmapp/models.py:468 
    572 #: plmapp/models.py:649 plmapp/controllers/user.py:92 todolist/models.py:33 
     547#: plmapp/models.py:649 plmapp/controllers/user.py:92 
    573548msgid "owner" 
    574549msgstr "propriétaire" 
    575550 
    576 #: plmapp/forms.py:532 plmapp/forms.py:538 
     551#: plmapp/forms.py:557 plmapp/forms.py:563 
    577552msgid "signer" 
    578553msgstr "signataire" 
    579554 
    580 #: plmapp/forms.py:533 plmapp/forms.py:539 plmapp/views/main.py:982 
     555#: plmapp/forms.py:558 plmapp/forms.py:564 plmapp/views/main.py:1061 
    581556msgid "notified" 
    582557msgstr "notifié" 
    583558 
    584 #: plmapp/forms.py:536 plmapp/forms.py:549 
     559#: plmapp/forms.py:561 plmapp/forms.py:574 
    585560msgid "part" 
    586561msgstr "part" 
    587562 
    588 #: plmapp/forms.py:542 
     563#: plmapp/forms.py:567 
    589564msgid "owned" 
    590565msgstr "dont on est propriétaire" 
    591566 
    592 #: plmapp/forms.py:543 
     567#: plmapp/forms.py:568 
    593568msgid "to sign" 
    594569msgstr "à signer" 
    595570 
    596 #: plmapp/forms.py:544 
     571#: plmapp/forms.py:569 
    597572msgid "request notification from" 
    598573msgstr "dont on a demandé à être notifié" 
    599574 
    600 #: plmapp/forms.py:548 
     575#: plmapp/forms.py:573 
    601576msgid "user" 
    602 msgstr "" 
    603  
    604 #: plmapp/forms.py:571 templates/create.html:11 templates/edit.html:11 
     577msgstr "utilisateur" 
     578 
     579#: plmapp/forms.py:596 templates/create.html:11 templates/edit.html:11 
    605580#: templates/users/password.html:11 
    606581msgid "Type" 
    607582msgstr "Type" 
    608583 
    609 #: plmapp/forms.py:572 templates/groups/pending_invitations.html:9 
     584#: plmapp/forms.py:597 templates/groups/pending_invitations.html:9 
    610585#: templates/groups/users.html:28 templates/users/delegation.html:32 
    611586msgid "Username" 
    612587msgstr "Identifiant" 
    613588 
    614 #: plmapp/forms.py:616 
     589#: plmapp/forms.py:641 
    615590msgid "The new user will belong to the selected groups" 
    616591msgstr "Le nouvel utilisateur appartiendra aux groupes sélectionnés" 
    617592 
    618 #: plmapp/forms.py:624 
     593#: plmapp/forms.py:649 
    619594msgid "Email address must be unique." 
    620 msgstr "" 
    621  
    622 #: plmapp/forms.py:631 
     595msgstr "L'adresse mail doit être unique." 
     596 
     597#: plmapp/forms.py:656 
    623598msgid "Email's domain not valid" 
    624 msgstr "" 
    625  
    626 #: plmapp/forms.py:646 
     599msgstr "Doé maine du mail non valide" 
     600 
     601#: plmapp/forms.py:671 
    627602#, python-format 
    628603msgid "Warning! There are homonyms: %s!" 
    629 msgstr "" 
    630  
    631 #: plmapp/forms.py:678 
     604msgstr "Attention ! Il y a des homonymes: %s!" 
     605 
     606#: plmapp/forms.py:703 
    632607msgid "Columns must have distinct headers." 
    633 msgstr "" 
    634  
    635 #: plmapp/forms.py:697 
    636 #, fuzzy 
     608msgstr "Chaque colonne doit avoir un entête différent." 
     609 
     610#: plmapp/forms.py:722 
    637611msgid "Password" 
    638612msgstr "Mot de passe :" 
    639613 
    640 #: plmapp/forms.py:710 
     614#: plmapp/forms.py:735 
    641615msgid "Your password was entered incorrectly. Please enter it again." 
    642616msgstr "" 
     617"Votre mot de passe a été saisi de façon incorrect. SVP veuillez le saisir à " 
     618"nouveau." 
    643619 
    644620#: plmapp/models.py:135 
     
    654630msgstr "lecteur" 
    655631 
    656 #: plmapp/models.py:179 plmapp/models.py:470 plmapp/models.py:1496 
     632#: plmapp/models.py:179 plmapp/models.py:470 plmapp/models.py:1492 
    657633#: plmapp/controllers/user.py:89 
    658634msgid "date of creation" 
     
    660636 
    661637#: plmapp/models.py:181 plmapp/models.py:472 plmapp/controllers/user.py:88 
    662 #: todolist/models.py:36 
    663638msgid "date of last modification" 
    664639msgstr "date de la dernière modification" 
     
    707682#: plmapp/models.py:507 
    708683msgid "The object is at its last state." 
    709 msgstr "" 
     684msgstr "L'objet est à son dernier état" 
    710685 
    711686#: plmapp/models.py:612 
     
    727702#: plmapp/models.py:650 
    728703msgid "group" 
    729 msgstr "" 
     704msgstr "groupe" 
    730705 
    731706#: plmapp/models.py:684 
     
    739714#: plmapp/models.py:720 
    740715msgid "Some children are at a lower or draft state." 
    741 msgstr "" 
     716msgstr "Des parts enfants sont à un statut inférieur ou au statut brouillon." 
    742717 
    743718#: plmapp/models.py:732 
    744 #, fuzzy 
    745719msgid "There are no official documents attached." 
    746720msgstr "Il n'y a pas de document attaché à cette part" 
    747721 
    748 #: plmapp/models.py:916 
     722#: plmapp/models.py:912 
    749723msgid "This document has no files." 
    750 msgstr "" 
    751  
    752 #: plmapp/models.py:919 
     724msgstr "Ce document n'a pas de fichier." 
     725 
     726#: plmapp/models.py:915 
    753727msgid "Some files are locked." 
    754 msgstr "" 
    755  
    756 #: plmapp/models.py:926 
     728msgstr "Des fichiers sont verrouillés." 
     729 
     730#: plmapp/models.py:922 
    757731msgid "files" 
    758732msgstr "fichiers" 
    759733 
    760 #: plmapp/models.py:927 
     734#: plmapp/models.py:923 
    761735msgid "parts" 
    762736msgstr "parts" 
    763737 
    764 #: plmapp/models.py:1498 
    765 #, fuzzy 
     738#: plmapp/models.py:1494 
    766739msgid "date of validation" 
    767 msgstr "date de création" 
    768  
    769 #: plmapp/models.py:1499 
     740msgstr "date de validation" 
     741 
     742#: plmapp/models.py:1495 
    770743msgid "True if guest created the invitation" 
    771 msgstr "" 
     744msgstr "Vrai si l'invité a créé l'invitation" 
    772745 
    773746#: plmapp/units.py:5 
    774747msgid "Amounts" 
    775 msgstr "" 
     748msgstr "Montants" 
    776749 
    777750#: plmapp/units.py:6 
    778751msgid "Each" 
    779 msgstr "" 
     752msgstr "Pièce" 
    780753 
    781754#: plmapp/units.py:7 
    782755msgid "mol" 
    783 msgstr "" 
     756msgstr "Mole" 
    784757 
    785758#: plmapp/units.py:10 
    786759msgid "Lengths" 
    787 msgstr "" 
     760msgstr "Longueurs" 
    788761 
    789762#: plmapp/units.py:11 
     
    861834#: plmapp/management/commands/createcompany.py:27 
    862835msgid "Enter a valid e-mail address." 
    863 msgstr "" 
    864  
    865 #: plmapp/views/main.py:978 
     836msgstr "Veuillez saisir une adresse mail valide." 
     837 
     838#: plmapp/views/main.py:1057 
    866839msgid "signer level" 
    867840msgstr "signataire niveau" 
    868841 
    869 #: plmapp/views/main.py:980 
     842#: plmapp/views/main.py:1059 
    870843msgid "signer all levels" 
    871844msgstr "signataire tous niveaux" 
     
    873846#: subversion/models.py:19 
    874847msgid "Valid value are HEAD, a number or a date between brackets" 
    875 msgstr "" 
     848msgstr "Les valeurs correctes sont HEAD, un nombre ou une date entre crochets" 
    876849 
    877850#: subversion/templates/ajax_logs.html:6 
    878851msgid "" 
    879852"\n" 
    880 "            Sorry, openPLM cannot access to this repository.\n" 
    881 "        " 
    882 msgstr "" 
     853"Sorry, openPLM cannot access to this repository.\n" 
     854msgstr "" 
     855"\n" 
     856"Désolé, openPLM ne peut pas accéder à ce dépot.\n" 
    883857 
    884858#: subversion/templates/ajax_logs.html:11 
    885859msgid "Last changelogs:" 
    886 msgstr "" 
     860msgstr "Derniers changelogs" 
    887861 
    888862#: subversion/templates/logs.html:28 
    889863msgid "Loading..." 
    890 msgstr "" 
     864msgstr "Chargement..." 
    891865 
    892866#: subversion/templates/subversion_files.html:8 
    893867msgid "You cannot add a file to a SubversionRepository." 
    894 msgstr "" 
     868msgstr "Vous ne pouvez pas ajouter un fichier à un SubversionRepository" 
    895869 
    896870#: subversion/templates/subversion_files.html:12 
    897871msgid "To check-out this repository, run the following command:" 
    898 msgstr "" 
     872msgstr "Pour télécharger ce répertoire, exécutez la commande suivante :" 
    899873 
    900874#: subversion/templates/subversion_files.html:19 
    901875msgid "To export this repository, run the following command:" 
    902 msgstr "" 
     876msgstr "Pour exporter ce répertoire, exécutez la commande suivante :" 
    903877 
    904878#: templates/404.html:17 
     
    917891#: templates/attributes.html:39 
    918892msgid "Download PDF file" 
    919 msgstr "" 
     893msgstr "Télécharger fichier pdf" 
    920894 
    921895#: templates/attributes.html:46 
    922896msgid "Comments" 
    923 msgstr "" 
     897msgstr "Commentaires" 
    924898 
    925899#: templates/attributes.html:52 
    926900msgid "" 
    927901"\n" 
    928 "                        One comment has been posted.\n" 
    929 "                    " 
    930 msgstr "" 
     902"One comment has been posted.\n" 
     903msgstr "" 
     904"\n" 
     905"Un commentaire a été posté.\n" 
    931906 
    932907#: templates/attributes.html:56 
    933 #, python-format 
    934 msgid "" 
    935 "\n" 
    936 "                        %(comment_count)s comments have been posted.\n" 
    937 "                    " 
    938 msgstr "" 
     908msgid "" 
     909"\n" 
     910"%(comment_count)s comments have been posted.\n" 
     911msgstr "" 
     912"\n" 
     913"%(comment_count)s commentaires ont été postés.\n" 
    939914 
    940915#: templates/attributes.html:66 
    941916msgid "Posted by:" 
    942 msgstr "" 
     917msgstr "Posté par:" 
    943918 
    944919#: templates/attributes.html:70 
    945920msgid "Date:" 
    946 msgstr "" 
     921msgstr "Date:" 
    947922 
    948923#: templates/attributes.html:80 
    949924msgid "No comments have been posted yet." 
    950 msgstr "" 
     925msgstr "Aucun commentaire n'a encore été posté." 
    951926 
    952927#: templates/attributes.html:86 
    953928msgid "Post a comment" 
    954 msgstr "" 
     929msgstr "Poster un commentaire" 
    955930 
    956931#: templates/attributes.html:116 
     
    960935#: templates/base.html:44 
    961936msgid "Home page" 
    962 msgstr "" 
     937msgstr "Page d'accueil" 
    963938 
    964939#: templates/base.html:61 
    965 #, fuzzy 
    966940msgid "SEARCH" 
    967 msgstr "RECHERCHER !" 
     941msgstr "RECHERCHER" 
    968942 
    969943#: templates/base.html:67 templates/create.html:36 
     
    980954 
    981955#: templates/base.html:94 
    982 #, fuzzy 
    983956msgid "EXIT" 
    984957msgstr "SORTIR" 
    985958 
    986959#: templates/base.html:105 
    987 #, fuzzy 
    988960msgid "Create" 
    989 msgstr "créateur" 
     961msgstr "Créer" 
    990962 
    991963#: templates/base.html:115 
    992 #, fuzzy 
    993964msgid "Search" 
    994965msgstr "Recherche pour:" 
     
    996967#: templates/base.html:146 
    997968msgid "You are not allowed to see this object" 
    998 msgstr "" 
     969msgstr "Vous n'êtes pas autorisé à voir cet objet" 
    999970 
    1000971#: templates/create.html:7 
     
    1020991 
    1021992#: templates/error.html:18 
    1022 #, fuzzy 
    1023993msgid "Error :" 
    1024994msgstr "Erreur :" 
     
    10301000#: templates/home.html:19 
    10311001msgid "Your account" 
    1032 msgstr "" 
     1002msgstr "Votre compte" 
    10331003 
    10341004#: templates/home.html:22 
    10351005msgid "Click here to edit your personal information." 
    1036 msgstr "" 
     1006msgstr "Cliquez ici pour éditer vos informations personnelles." 
    10371007 
    10381008#: templates/home.html:28 
    10391009msgid "Last edited objects" 
    1040 msgstr "" 
     1010msgstr "Derniers objets édités" 
    10411011 
    10421012#: templates/home.html:61 
    10431013msgid "Groups that you own: pending invitations" 
    1044 msgstr "" 
     1014msgstr "Les groupes dont vous êtes propriétaire: invitations en attente" 
    10451015 
    10461016#: templates/home.html:72 
    10471017msgid "Groups that you may join: pending invitations" 
    1048 msgstr "" 
    1049  
    1050 #: templates/lifecycle.html:16 templates/revisions.html:17 
     1018msgstr "Les groupes que vous pouvez rejoindre: invitations en attente" 
     1019 
     1020#: templates/lifecycle.html:16 templates/documents/revisions.html:7 
     1021#: templates/parts/revisions.html:7 
    10511022msgid "Are you sure?" 
    1052 msgstr "" 
     1023msgstr "Êtes-vous sur ?" 
    10531024 
    10541025#: templates/lifecycle.html:21 
     
    10571028"action." 
    10581029msgstr "" 
     1030"Si vous validez ou refusez cet objet, vous pouvez ne plus être autorisé à " 
     1031"annuler cette action." 
    10591032 
    10601033#: templates/lifecycle.html:32 
    1061 #, fuzzy 
    10621034msgid "" 
    10631035"You can not demote this object since its state is official or more advanced." 
    1064 msgstr "Vous ne pouvez pas reviser car une révision plus récente existe déjà" 
     1036msgstr "" 
     1037"Vous ne pouvez pas refuser cet objet car son statut est officiel ou encore plus avancé." 
    10651038 
    10661039#: templates/lifecycle.html:35 
    10671040msgid "You do not have the permission to demote this object." 
    1068 msgstr "" 
     1041msgstr "Vous n'avez pas la permission de refuser cet objet." 
    10691042 
    10701043#: templates/lifecycle.html:42 
    10711044msgid "You can not promote this object:" 
    1072 msgstr "" 
     1045msgstr "Vous ne pouvez pas valider cet objet:" 
    10731046 
    10741047#: templates/lifecycle.html:46 
    10751048msgid "You do not have the permission to promote this object." 
    1076 msgstr "" 
     1049msgstr "Vous n'avez pas la permission de valider cet objet." 
    10771050 
    10781051#: templates/login.html:28 
    10791052msgid "Welcome to openPLM" 
    1080 msgstr "" 
     1053msgstr "Bienvenue dans openPLM" 
    10811054 
    10821055#: templates/login.html:38 templates/blocks/user.html:19 
     
    10861059#: templates/login.html:45 
    10871060msgid "is the first genuine open source PLM based on" 
    1088 msgstr "" 
     1061msgstr "est le premier véritable PLM open source s'appuyant sur" 
    10891062 
    10901063#: templates/login.html:46 
     
    10921065"intended to provide a starting point for companies CMS/PLM/BIM projects." 
    10931066msgstr "" 
     1067"avec l'intention de fournir une base de départ pour les projets CMS/PLM/BIM " 
     1068"des sociétés." 
    10941069 
    10951070#: templates/login.html:47 
     
    10991074"your company different." 
    11001075msgstr "" 
     1076"En intégrant de nombreuses fonctionalités standards et réutilisables pour " 
     1077"prendre en charge les choses que les sociétés ont en commun, cela vous " 
     1078"permet de vous concentrer sur ce qui fait votre spécificité." 
    11011079 
    11021080#: templates/login.html:51 
    11031081msgid "For more information about openPLM, see" 
    1104 msgstr "" 
     1082msgstr "Pour plus d'information concernant openPLM, voir" 
    11051083 
    11061084#: templates/login.html:54 
    1107 #, fuzzy 
    11081085msgid "openPLM connexion :" 
    1109 msgstr "Connexion à OpenPLM" 
     1086msgstr "Connexion à OpenPLM :" 
    11101087 
    11111088#: templates/login.html:63 
     
    11211098msgstr "ENTRER" 
    11221099 
     1100#: templates/login.html:77 
     1101msgid "" 
     1102"If you have any trouble logging in to your account, contact your " 
     1103"administrator" 
     1104msgstr "" 
     1105"Si vous aves des difficultés à vous loguer, contactez votre " 
     1106"administrateur" 
     1107 
    11231108#: templates/login.html:79 
    1124 #, fuzzy 
    11251109msgid "Professionals" 
    1126 msgstr "révisions" 
     1110msgstr "Professionels" 
    11271111 
    11281112#: templates/login.html:82 
     
    11321116"solutions plugins, for openPLM software maintenance, " 
    11331117msgstr "" 
     1118"Vous prévoyez d'utiliser openPLM dans un contexte professionnel. Si vous " 
     1119"voulez travailler avec d'autres professionnels pour votre gestion de projet, " 
     1120"pour des interfaces avec d'autres solutions, pour une maintenance logiciel " 
     1121"d'openPLM," 
    11341122 
    11351123#: templates/login.html:84 
    11361124msgid "contact us" 
    1137 msgstr "" 
     1125msgstr "contactez-nous" 
    11381126 
    11391127#: templates/management.html:13 
    11401128msgid "Notify" 
    1141 msgstr "" 
     1129msgstr "Notifier" 
    11421130 
    11431131#: templates/management.html:18 
    1144 #, fuzzy 
    11451132msgid "Unnotify me" 
    1146 msgstr "notifié" 
     1133msgstr "Ne plus me notifiez" 
    11471134 
    11481135#: templates/management.html:23 
    11491136msgid "Notify me" 
    1150 msgstr "" 
     1137msgstr "Notifiez moi" 
    11511138 
    11521139#: templates/management.html:51 
     
    11561143#: templates/management.html:58 
    11571144msgid "Delete" 
    1158 msgstr "Effacer ?" 
     1145msgstr "Effacer" 
    11591146 
    11601147#: templates/management_replace.html:7 
     
    11641151#: templates/navigate.html:38 
    11651152msgid "Attach document?" 
    1166 msgstr "" 
     1153msgstr "Attacher le document ?" 
    11671154 
    11681155#: templates/navigate.html:41 
    11691156msgid "Attach the selected document?" 
    1170 msgstr "" 
     1157msgstr "Attacher le document sélectionné ?" 
    11711158 
    11721159#: templates/navigate.html:56 
    11731160msgid "Close" 
    1174 msgstr "" 
     1161msgstr "Fermer" 
    11751162 
    11761163#: templates/navigate.html:75 templates/parts/bom.html:23 
     
    11831170msgstr "FILTRER" 
    11841171 
    1185 #: templates/revisions.html:22 
    1186 msgid "" 
    1187 "Here is the list of parts connected to the original document. Do you want to " 
    1188 "connect them to new document?" 
    1189 msgstr "" 
    1190  
    1191 #: templates/revisions.html:52 
     1172#: templates/revisions.html:15 
    11921173msgid "NEW" 
    11931174msgstr "NOUVEAU" 
    11941175 
    1195 #: templates/revisions.html:56 
     1176#: templates/revisions.html:19 
    11961177msgid "You can not revise this object since a revision already exist." 
    11971178msgstr "Vous ne pouvez pas reviser car une révision plus récente existe déjà" 
     
    11991180#: templates/blocks/creation.html:12 
    12001181msgid "Click here to create a new object." 
    1201 msgstr "" 
     1182msgstr "Cliquez ici pour créer un nouvel objet." 
    12021183 
    12031184#: templates/blocks/creation.html:13 
     
    12071188#: templates/blocks/creation.html:19 
    12081189msgid "Import" 
    1209 msgstr "" 
     1190msgstr "Importer" 
    12101191 
    12111192#: templates/blocks/creation.html:24 
    12121193msgid "Import a BOM" 
    1213 msgstr "" 
     1194msgstr "Importer un nomenclature" 
    12141195 
    12151196#: templates/blocks/creation.html:30 
    1216 #, fuzzy 
    12171197msgid "You can not create an object since you are not a contributor." 
    1218 msgstr "Vous ne pouvez pas reviser car une révision plus récente existe déjà" 
     1198msgstr "Vous ne pouvez pas créer un objet car vous n'êtes pas contributeur." 
    12191199 
    12201200#: templates/blocks/search.html:7 
     
    12241204#: templates/blocks/search.html:14 
    12251205msgid "Results for link creation:" 
    1226 msgstr "" 
     1206msgstr "Résultats pour la création de lien:" 
    12271207 
    12281208#: templates/blocks/search.html:21 
    12291209msgid "Results" 
    1230 msgstr "" 
     1210msgstr "Résultats" 
    12311211 
    12321212#: templates/blocks/search.html:23 
    1233 #, python-format 
    1234 msgid "" 
    1235 "\n" 
    1236 "                    (1 - %(count)s on %(total)s)\n" 
    1237 "                " 
    1238 msgstr "" 
     1213msgid "" 
     1214"\n" 
     1215"(1 - %(count)s on %(total)s)\n" 
     1216msgstr "" 
     1217"\n" 
     1218"(1 - %(count)s sur %(total)s)\n" 
    12391219 
    12401220#: templates/blocks/search.html:35 
    12411221msgid "Attach" 
    1242 msgstr "" 
     1222msgstr "Attacher" 
    12431223 
    12441224#: templates/blocks/search.html:36 
    1245 #, fuzzy 
    12461225msgid "Add child" 
    1247 msgstr "enfants" 
    1248  
    1249 #: templates/blocks/search.html:50 templates/documents/files.html:54 
     1226msgstr "Ajouter enfants" 
     1227 
     1228#: templates/blocks/search.html:50 templates/documents/files.html:53 
    12501229#: templates/documents/parts.html:11 templates/parts/bom.html:19 
    12511230msgid "ADD" 
     
    12561235msgstr "Aucun résultat ne correspond à la recherche" 
    12571236 
    1258 #: templates/documents/files.html:46 templates/parts/doccad.html:10 
     1237#: templates/documents/files.html:45 templates/parts/doccad.html:10 
    12591238msgid "Download all files" 
    1260 msgstr "" 
    1261  
    1262 #: templates/documents/files.html:57 
     1239msgstr "Télécharger tous les fichiers" 
     1240 
     1241#: templates/documents/files.html:56 
    12631242msgid "DELETE" 
    12641243msgstr "DETRUIRE" 
    12651244 
    1266 #: templates/documents/files.html:72 
     1245#: templates/documents/files.html:71 
    12671246msgid "Locked" 
    1268 msgstr "" 
    1269  
    1270 #: templates/documents/files.html:74 
     1247msgstr "Verrouillé" 
     1248 
     1249#: templates/documents/files.html:73 
    12711250msgid "Unlocked" 
    1272 msgstr "" 
    1273  
    1274 #: templates/documents/files.html:81 
     1251msgstr "Déverrouillé" 
     1252 
     1253#: templates/documents/files.html:80 
    12751254msgid "DOWNLOAD" 
    12761255msgstr "TELECHARGER" 
    12771256 
    1278 #: templates/documents/files.html:90 
     1257#: templates/documents/files.html:88 
    12791258msgid "CHECK-IN" 
    12801259msgstr "CHECK-IN" 
    12811260 
    1282 #: templates/documents/files.html:99 templates/documents/files.html.py:103 
     1261#: templates/documents/files.html:97 
    12831262msgid "CHECK-OUT" 
    12841263msgstr "CHECK-OUT" 
    12851264 
    1286 #: templates/documents/files.html:132 
     1265#: templates/documents/files.html:102 
     1266msgid "Native related file is locked" 
     1267msgstr "Le fichier natif lié est verrouillé" 
     1268 
     1269#: templates/documents/files.html:116 
    12871270msgid "No thumbnail available" 
    12881271msgstr "Aucun aperçu disponible" 
    12891272 
    1290 #: templates/documents/files.html:146 templates/parts/doccad.html:16 
     1273#: templates/documents/files.html:128 templates/parts/doccad.html:16 
    12911274msgid "Download merged PDF files" 
    1292 msgstr "" 
     1275msgstr "Télécharger les fichiers pdf fusionnés" 
     1276 
     1277#: templates/documents/files.html:134 
     1278msgid "Confirm CHECK-OUT" 
     1279msgstr "Confirmez CHECK-OUT" 
     1280 
     1281#: templates/documents/files.html:135 
     1282msgid "" 
     1283"You'are checking out a standardfile while a native file is available. Your " 
     1284"native file will be deprecated and will not be usable anymore." 
     1285msgstr "" 
     1286"Vous êtes en train de télécharger un fichier standard alors qu'un fichier " 
     1287"natif est disponible. Votrefichier natif sera déprécié et ne sera plus " 
     1288"utilisable." 
     1289 
     1290#: templates/documents/files.html:141 
     1291msgid "Deprecated files:" 
     1292msgstr "Fichiers dépréciés:" 
    12931293 
    12941294#: templates/documents/files_add.html:7 
     
    13041304msgstr "Il n'y a pas de part attachée à ce document" 
    13051305 
     1306#: templates/documents/revisions.html:12 
     1307msgid "" 
     1308"Here is the list of parts you may want to attach to the new revision. Select " 
     1309"parts you want to attach and validate to make a new revision." 
     1310msgstr "" 
     1311"Ceci est la liste des parts que vous pourriez attacher à la nouvelle " 
     1312"révision. Selectionnezles parts que vous voulez attacher et validez pour " 
     1313"créer une nouvelle révision." 
     1314 
    13061315#: templates/groups/accept_invitation.html:5 
    13071316msgid "Accept invitation ?" 
    1308 msgstr "" 
     1317msgstr "Accepter l'invitation ?" 
    13091318 
    13101319#: templates/groups/accept_invitation.html:7 
    1311 #, python-format 
    1312 msgid "" 
    1313 "\n" 
    1314 "            Accept that %(guest)s join the group %(group)s ?\n" 
    1315 "        " 
    1316 msgstr "" 
     1320msgid "" 
     1321"\n" 
     1322"Accept that %(guest)s join the group %(group)s ?\n" 
     1323msgstr "" 
     1324"\n" 
     1325"Accepter que %(guest)s rejoigne le groupe %(group)s ?\n" 
    13171326 
    13181327#: templates/groups/accept_invitation.html:11 
    1319 #, python-format 
    1320 msgid "" 
    1321 "\n" 
    1322 "            Accept to join the group %(group)s ?\n" 
    1323 "        " 
    1324 msgstr "" 
     1328msgid "" 
     1329"\n" 
     1330"Accept to join the group %(group)s ?\n" 
     1331msgstr "" 
     1332"\n" 
     1333"Accepter de rejoindre le groupe %(group)s ?\n" 
    13251334 
    13261335#: templates/groups/add_user.html:6 
    13271336msgid "Invite a new user to the group:" 
    1328 msgstr "" 
     1337msgstr "Inviter un nouvel utilisateur dans le groupe:" 
    13291338 
    13301339#: templates/groups/add_user.html:7 
    13311340msgid "The user will receive an e-mail and could accept your invitation." 
    1332 msgstr "" 
     1341msgstr "L'utilisateur va recevoir un mail et pourra accepter votre invitation." 
    13331342 
    13341343#: templates/groups/add_user.html:12 
    13351344msgid "You can not invite an user to this group." 
    1336 msgstr "" 
     1345msgstr "Vous ne pouvez pas inviter un utilisateur à ce groupe." 
    13371346 
    13381347#: templates/groups/ask_to_join.html:6 
    13391348msgid "You are already in this group." 
    1340 msgstr "" 
     1349msgstr "Vous êtes déjà dans ce groupe." 
    13411350 
    13421351#: templates/groups/ask_to_join.html:8 
    13431352msgid "Join this group ?" 
    1344 msgstr "" 
     1353msgstr "Rejoindre ce groupe ?" 
     1354 
     1355#: templates/groups/pending_invitations.html:7 
     1356msgid "Group" 
     1357msgstr "Groupe" 
    13451358 
    13461359#: templates/groups/pending_invitations.html:10 templates/groups/users.html:29 
     
    13491362 
    13501363#: templates/groups/pending_invitations.html:11 templates/groups/users.html:30 
    1351 #, fuzzy 
    13521364msgid "Last name" 
    13531365msgstr "Prénom" 
    13541366 
    13551367#: templates/groups/pending_invitations.html:12 
    1356 #, fuzzy 
    13571368msgid "Date of the invitation" 
    1358 msgstr "date de création" 
     1369msgstr "Date de l'invitation" 
    13591370 
    13601371#: templates/groups/pending_invitations.html:37 
    13611372msgid "Resend" 
    1362 msgstr "" 
     1373msgstr "Renvoyer" 
    13631374 
    13641375#: templates/groups/pending_invitations.html:45 
    13651376msgid "Accept" 
    1366 msgstr "" 
     1377msgstr "Accepter" 
    13671378 
    13681379#: templates/groups/pending_invitations.html:50 
    13691380msgid "Refuse" 
    1370 msgstr "" 
     1381msgstr "Refuser" 
    13711382 
    13721383#: templates/groups/refuse_invitation.html:5 
    13731384msgid "Refuse invitation ?" 
    1374 msgstr "" 
     1385msgstr "Refuser l'invitation ?" 
    13751386 
    13761387#: templates/groups/refuse_invitation.html:7 
    1377 #, python-format 
    1378 msgid "" 
    1379 "\n" 
    1380 "            Refuse that %(guest)s join the group %(group)s ?\n" 
    1381 "        " 
    1382 msgstr "" 
     1388msgid "" 
     1389"\n" 
     1390"Refuse that %(guest)s join the group %(group)s ?\n" 
     1391msgstr "" 
     1392"\n" 
     1393"Refuser que %(guest)s rejoigne le groupe %(group)s ?\n" 
    13831394 
    13841395#: templates/groups/refuse_invitation.html:11 
    1385 #, python-format 
    1386 msgid "" 
    1387 "\n" 
    1388 "            Refuse to join the group %(group)s ?\n" 
    1389 "        " 
    1390 msgstr "" 
     1396msgid "" 
     1397"\n" 
     1398"Refuse to join the group %(group)s ?\n" 
     1399msgstr "" 
     1400"\n" 
     1401" Refuser de rejoindre le groupe %(group)s ?\n" 
    13911402 
    13921403#: templates/groups/users.html:8 
    1393 #, fuzzy 
    13941404msgid "Users" 
    1395 msgstr "Identifiant" 
     1405msgstr "Utilisateurs" 
    13961406 
    13971407#: templates/groups/users.html:13 
    13981408msgid "Invite another user" 
    1399 msgstr "" 
     1409msgstr "Inviter un autre utilisateur" 
    14001410 
    14011411#: templates/groups/users.html:15 
    14021412msgid "Remove selected users" 
    1403 msgstr "" 
     1413msgstr "Retirer les utilisateurs sélectionnés" 
    14041414 
    14051415#: templates/groups/users.html:19 
    14061416msgid "Ask to join this group" 
    1407 msgstr "" 
     1417msgstr "Demander de rejoindre ce groupe" 
    14081418 
    14091419#: templates/groups/users.html:71 
    14101420msgid "Pending invitations" 
    1411 msgstr "" 
     1421msgstr "Invitations en attente" 
    14121422 
    14131423#: templates/groups/users.html:72 
     
    14161426"them." 
    14171427msgstr "" 
     1428"Les invitations suivantes ont été envoyées mais les destinataires ne les ont " 
     1429"pas encore acceptées." 
    14181430 
    14191431#: templates/import/csv.html:6 
    14201432msgid "Import of a csv file" 
    1421 msgstr "" 
     1433msgstr "Importer un fichier csv" 
    14221434 
    14231435#: templates/import/csv.html:16 
    1424 #, fuzzy 
    14251436msgid "Error" 
    1426 msgstr "Erreur :" 
     1437msgstr "Erreur" 
    14271438 
    14281439#: templates/import/csv.html:22 
    14291440#, python-format 
    14301441msgid " Line %(line)s " 
    1431 msgstr "" 
     1442msgstr "Ligne %(line)s" 
    14321443 
    14331444#: templates/import/csv.html:32 
    14341445msgid "Error while reading the CSV file. Try upload a new file." 
    14351446msgstr "" 
     1447"Erreur de lecture du fichier csv. Essayez de télécharger un nouveau fichier." 
    14361448 
    14371449#: templates/import/csv.html:35 
     
    14401452"encoding." 
    14411453msgstr "" 
     1454"L'encodage fourni ne semble pas correct. Essayez de télécharger un nouveau " 
     1455"fichier avec l'encodage correct." 
    14421456 
    14431457#: templates/import/csv.html:50 
    14441458msgid "Field" 
    1445 msgstr "" 
     1459msgstr "Champ" 
    14461460 
    14471461#: templates/import/csv.html:51 
    14481462msgid "Header" 
    1449 msgstr "" 
     1463msgstr "Entête" 
    14501464 
    14511465#: templates/import/csv.html:53 
    14521466msgid "Row" 
    1453 msgstr "" 
     1467msgstr "Ligne" 
    14541468 
    14551469#: templates/import/csv.html:68 templates/parts/bom_edit.html:48 
     
    14601474#: templates/import/done.html:6 
    14611475msgid "Data have been successfully imported." 
    1462 msgstr "" 
     1476msgstr "Les données ont été importées avec succès." 
    14631477 
    14641478#: templates/import/done.html:8 
    14651479msgid "Click here to import a new CSV file." 
    1466 msgstr "" 
     1480msgstr "Cliquez ici pour importer un nouveau fichier csv." 
    14671481 
    14681482#: templates/import/done.html:9 
    14691483msgid "Click here to import a new BOM." 
    1470 msgstr "" 
     1484msgstr "Cliquez ici pour importer une nouvelle nomenclature." 
    14711485 
    14721486#: templates/import/done.html:10 
    14731487msgid "Click here to sponsor more users." 
    1474 msgstr "" 
     1488msgstr "Cliquez ici pour parrainer plus d'utilisateurs." 
    14751489 
    14761490#: templates/mails/history.html:6 
    14771491msgid "Message from openPLM" 
    1478 msgstr "" 
     1492msgstr "Message venant d'openPLM" 
    14791493 
    14801494#: templates/mails/history.html:8 
    14811495msgid "Object:" 
    1482 msgstr "" 
     1496msgstr "Objet:" 
    14831497 
    14841498#: templates/mails/history.html:11 
    14851499msgid "A new action has been done:" 
    1486 msgstr "" 
     1500msgstr "Une nouvelle action a été exécutée:" 
    14871501 
    14881502#: templates/mails/history.html:14 
    1489 #, fuzzy 
    14901503msgid "Details" 
    1491 msgstr "détail technique" 
     1504msgstr "Détails" 
    14921505 
    14931506#: templates/mails/history.html:32 
    14941507msgid "Click here to see more details." 
    1495 msgstr "" 
     1508msgstr "Cliquez ici pour avoir plus de détails." 
    14961509 
    14971510#: templates/mails/invitation1.html:5 templates/mails/invitation2.html:5 
    1498 #, python-format 
    1499 msgid "" 
    1500 "\n" 
    1501 "        %(s_first_name)s %(s_last_name)s (%(s_username)s) ask you to join " 
     1511msgid "" 
     1512"\n" 
     1513"%(s_first_name)s %(s_last_name)s (%(s_username)s) ask you to join " 
    15021514"the group %(group)s.\n" 
    1503 "        " 
    1504 msgstr "" 
     1515msgstr "" 
     1516"\n" 
     1517" %(s_first_name)s %(s_last_name)s (%(s_username)s) vous demande de rejoindre " 
     1518"le groupe %(group)s.\n" 
    15051519 
    15061520#: templates/mails/invitation1.html:11 templates/mails/invitation2.html:11 
    15071521msgid "Click here to accept the invitation" 
    1508 msgstr "" 
     1522msgstr "Cliquez ici pour accepter l'invitation" 
    15091523 
    15101524#: templates/mails/invitation1.html:12 templates/mails/invitation2.html:12 
    15111525msgid "Accept invitation" 
    1512 msgstr "" 
     1526msgstr "Accepter l'invitation" 
    15131527 
    15141528#: templates/mails/invitation1.html:14 templates/mails/invitation2.html:14 
    15151529msgid "Click here to refuse the invitation" 
    1516 msgstr "" 
     1530msgstr "Cliquez ici pour refuser l'invitation" 
    15171531 
    15181532#: templates/mails/invitation1.html:15 templates/mails/invitation2.html:15 
    15191533msgid "Refuse invitation" 
    1520 msgstr "" 
     1534msgstr "Refuser l'invitation" 
    15211535 
    15221536#: templates/mails/new_account.html:5 
    1523 #, python-format 
    1524 msgid "" 
    1525 "\n" 
    1526 "            Welcome %(first_name)s %(last_name)s!\n" 
    1527 "            " 
    1528 msgstr "" 
     1537msgid "" 
     1538"\n" 
     1539"Welcome %(first_name)s %(last_name)s!\n" 
     1540msgstr "" 
     1541"\n" 
     1542" Bienvenue %(first_name)s %(last_name)s!\n" 
    15291543 
    15301544#: templates/mails/new_account.html:10 
    1531 #, python-format 
    1532 msgid "" 
    1533 "\n" 
    1534 "        %(s_first_name)s %(s_last_name)s (%(s_username)s) added you to " 
     1545msgid "" 
     1546"\n" 
     1547"%(s_first_name)s %(s_last_name)s (%(s_username)s) added you to " 
    15351548"OpenPLM.\n" 
    1536 "        " 
    1537 msgstr "" 
     1549msgstr "" 
     1550"\n" 
     1551" %(s_first_name)s %(s_last_name)s (%(s_username)s) vous a ajouté à openPLM.\n" 
    15381552 
    15391553#: templates/mails/new_account.html:16 
    15401554msgid "Your credentials:" 
    1541 msgstr "" 
     1555msgstr "Vos informations d'identification" 
    15421556 
    15431557#: templates/mails/new_account.html:21 
    1544 #, fuzzy 
    15451558msgid "Username:" 
    1546 msgstr "Identifiant" 
     1559msgstr "Identifiant:" 
    15471560 
    15481561#: templates/mails/new_account.html:25 
    1549 #, fuzzy 
    15501562msgid "Password:" 
    15511563msgstr "Mot de passe :" 
     
    15531565#: templates/mails/new_account.html:30 
    15541566msgid "You should change your password!" 
    1555 msgstr "" 
     1567msgstr "Vous devez changer votre mot de passe!" 
    15561568 
    15571569#: templates/mails/new_account.html:32 
    15581570msgid "Click here to change your password" 
    1559 msgstr "" 
     1571msgstr "Cliquez ici pour changer votre mot de passe" 
    15601572 
    15611573#: templates/parts/bom.html:27 templates/parts/parents.html:13 
     
    15651577#: templates/parts/bom.html:31 
    15661578msgid "Level" 
    1567 msgstr "" 
     1579msgstr "Niveau" 
    15681580 
    15691581#: templates/parts/bom.html:32 
     
    15771589#: templates/parts/bom.html:34 templates/parts/bom_edit.html:13 
    15781590msgid "Unit" 
    1579 msgstr "" 
     1591msgstr "Unité" 
    15801592 
    15811593#: templates/parts/bom_edit.html:11 
     
    15921604 
    15931605#: templates/parts/doccad.html:49 
    1594 #, fuzzy 
    15951606msgid "There are no documents attached to this part." 
    1596 msgstr "Il n'y a pas de document attaché à cette part" 
     1607msgstr "Il n'y a pas de document attaché à cette part." 
    15971608 
    15981609#: templates/parts/doccad.html:58 
    15991610msgid "Attach another document" 
    1600 msgstr "" 
     1611msgstr "Attacher un autre document" 
    16011612 
    16021613#: templates/parts/doccad_add.html:7 
     
    16041615msgstr "Connecter nouveau document :" 
    16051616 
     1617#: templates/parts/revisions.html:13 
     1618msgid "" 
     1619"Here is the list of documents you may want to attach to the new revision. " 
     1620"Select documents you want to attach." 
     1621msgstr "" 
     1622"Voici la liste des documents que vous pourriez attacher à la nouvelle " 
     1623"révision.Sélectionnez les documents que vous voulez attacher." 
     1624 
     1625#: templates/parts/revisions.html:35 
     1626msgid "" 
     1627"Here is the list of parts you may want to add to the new revision. Select " 
     1628"parts you want to add." 
     1629msgstr "" 
     1630"Voici la liste des parts que vous pourriez attacher à la nouvelle révision. " 
     1631"Sélectionnez les parts que vous voulez attacher." 
     1632 
     1633#: templates/parts/revisions.html:57 
     1634msgid "" 
     1635"Here is the list of the current parents you may want to update to link to " 
     1636"the new revision. Select parts you want to update." 
     1637msgstr "" 
     1638"Voici la liste des parents actuels que vous pourriez vouloir mettre à jour " 
     1639"en les liantà la nouvelle révision. Sélectionnez les parts que vous voulez " 
     1640"mettre à jour." 
     1641 
    16061642#: templates/users/delegation.html:8 
    16071643msgid "START DELEGATE :" 
     
    16301666#: templates/users/delegation.html:26 
    16311667msgid "Sponsor" 
    1632 msgstr "" 
     1668msgstr "Parrain" 
    16331669 
    16341670#: templates/users/delegation.html:33 
     
    16501686#: templates/users/delegation.html:56 
    16511687msgid "Resend mail" 
    1652 msgstr "" 
     1688msgstr "Envoyer le mail à nouveau" 
    16531689 
    16541690#: templates/users/password.html:7 
     
    16671703msgid "Click here to sponsor several users from a CSV file." 
    16681704msgstr "" 
     1705"Cliquez ici pour parrainer plusieurs utilisateurs en utilisant un fichier " 
     1706"csv." 
    16691707 
    16701708#: templates/users/sponsor.html:16 
    16711709msgid "You can not sponsor a new user since you are not a contributor." 
    16721710msgstr "" 
    1673  
    1674 #: todolist/models.py:10 
    1675 msgid "defect" 
    1676 msgstr "" 
    1677  
    1678 #: todolist/models.py:11 
    1679 #, fuzzy 
    1680 msgid "enhancement" 
    1681 msgstr "gestion" 
    1682  
    1683 #: todolist/models.py:12 
    1684 msgid "task" 
    1685 msgstr "" 
    1686  
    1687 #: todolist/models.py:16 
    1688 msgid "blocker" 
    1689 msgstr "" 
    1690  
    1691 #: todolist/models.py:17 
    1692 msgid "critical" 
    1693 msgstr "" 
    1694  
    1695 #: todolist/models.py:18 
    1696 msgid "major" 
    1697 msgstr "" 
    1698  
    1699 #: todolist/models.py:19 
    1700 msgid "minor" 
    1701 msgstr "" 
    1702  
    1703 #: todolist/models.py:20 
    1704 msgid "trivial" 
    1705 msgstr "" 
    1706  
    1707 #~ msgid "cad" 
    1708 #~ msgstr "cao" 
    1709  
    1710 #~ msgid "FIND" 
    1711 #~ msgstr "TROUVER" 
    1712  
    1713 #~ msgid "Reference" 
    1714 #~ msgstr "Référence" 
    1715  
    1716 #~ msgid "Rev." 
    1717 #~ msgstr "Rév." 
    1718  
    1719 #~ msgid "Name" 
    1720 #~ msgstr "Nom" 
    1721  
    1722 #~ msgid "active,inactive" 
    1723 #~ msgstr "actif,inactif" 
    1724  
    1725 #~ msgid "Revision" 
    1726 #~ msgstr "Révision" 
    1727  
    1728 #~ msgid "e-mail." 
    1729 #~ msgstr "e-mail." 
    1730  
    1731 #~ msgid "e-mail" 
    1732 #~ msgstr "e-mail" 
    1733  
    1734 #~ msgid "Add" 
    1735 #~ msgstr "Ajouter" 
     1711"Vous ne pouvez pas parrainer un nouvel utilisateur car vous n'êtes pas un " 
     1712"contributeur" 
     1713 
  • branches/3D/openPLM/media/js/confirm.js

    r817 r870  
    1515    $("#" + form.attr("id") + "-dialog" ).dialog({ 
    1616                        resizable: false, 
    17                         height:300, 
    1817                        modal: true, 
     18            width: 600, 
    1919                        buttons: { 
    2020                                Cancel: function() { 
  • branches/3D/openPLM/office/models.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/office/tests.py

    r472 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/office/views.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/pdfgen/forms.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    2323################################################################################ 
    2424 
    25 import re 
    26 from collections import defaultdict 
    27  
    2825from django import forms 
    29 from django.conf import settings 
    30 from django.forms.formsets import formset_factory, BaseFormSet 
    31 from django.forms.models import modelform_factory, inlineformset_factory, \ 
    32         BaseModelFormSet 
    33 from django.forms import ValidationError 
    34 from django.utils.translation import ugettext_lazy as _ 
    35 from django.contrib.sites.models import Site 
    36 from django.utils.functional import memoize 
     26from django.forms.models import inlineformset_factory 
    3727 
    3828import openPLM.plmapp.models as m 
     
    4636 
    4737SelectPdfFormset = inlineformset_factory(m.Document, m.DocumentFile, 
    48         form=SelectPdfForm, extra=0) 
     38        form=SelectPdfForm, extra=0, can_delete=False) 
    4939 
    5040def get_pdf_formset(controller, data=None, **kwargs): 
  • branches/3D/openPLM/pdfgen/tests.py

    r817 r870  
    44from pyPdf import PdfFileReader 
    55 
     6from openPLM.plmapp.controllers import DocumentController, PartController 
    67from openPLM.plmapp.tests.views import CommonViewTest 
    78 
    8  
    9 class PdfAttributesViewTestCase(CommonViewTest): 
     9from openPLM.pdfgen.views import download_merged_pdf 
     10 
     11PDF = """%PDF-1.1 
     12 
     131 0 obj 
     14  << /Type /Catalog /Pages 2 0 R >> 
     15endobj 
     16 
     172 0 obj 
     18  << /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 300 144] >> 
     19endobj 
     20 
     213 0 obj 
     22  <<  /Type /Page 
     23      /Parent 2 0 R /Resources 
     24       << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Times-Roman >> 
     25           >> 
     26       >> 
     27      /Contents [ 
     28        << /Length 105 >> 
     29        stream 
     30          BT 
     31            /F1 18 Tf 
     32            0 0 Td 
     33            (Hello world.) Tj 
     34          ET 
     35        endstream ] 
     36  >> 
     37endobj 
     38 
     39xref 
     400 4 
     410000000000 65535 f  
     420000000010 00000 n  
     430000000062 00000 n  
     440000000146 00000 n  
     45trailer 
     46  <<  /Root 1 0 R /Size 4 >> 
     47startxref 
     48496 
     49%%EOF""" 
     50 
     51 
     52class PdfTestCase(CommonViewTest): 
    1053 
    1154    def check_pdf(self, response, num_pages=1): 
     
    2164        warnings.simplefilter('default', DeprecationWarning) 
    2265 
     66 
     67class PdfAttributesViewTestCase(PdfTestCase): 
     68 
    2369    def test_user_attributes(self): 
    2470        response = self.client.get("/pdf/user/%s/attributes/" % self.user.username) 
     
    3581# TODO test merged pdfs 
    3682 
     83class PdfMergeTestCase(PdfTestCase): 
     84 
     85    def setUp(self): 
     86        super(PdfMergeTestCase, self).setUp() 
     87        self.doc = DocumentController.create("Doc1", "Document", "q", 
     88                self.user, self.DATA, True, True) 
     89        self.doc_url = self.doc.plmobject_url 
     90 
     91    def test_download_merged_pdf(self): 
     92        for i in range(4): 
     93            response = download_merged_pdf(self.doc, self.doc.files) 
     94            self.check_pdf(response, i + 1) 
     95            self.doc.add_file(self.get_file("hello%d.pdf" % i, data=PDF)) 
     96            self.assertEqual(i, response.content.count("world")) 
     97 
     98    def test_select_pdf_empty_document_html(self): 
     99        response = self.get(self.doc_url + "pdf/")  
     100        formset = response.context["pdf_formset"] 
     101        self.assertEqual(0, formset.total_form_count()) 
     102         
     103    def test_select_pdf_empty_document_pdf(self): 
     104        data = { 
     105                "Download" : "download", 
     106                'documentfile_set-TOTAL_FORMS': "0",  
     107                'documentfile_set-INITIAL_FORMS': "0", 
     108                } 
     109        response = self.client.get(self.doc_url + "pdf/", data=data) 
     110        self.check_pdf(response, 1) 
     111     
     112    def test_select_pdf_one_pdf_document_html(self): 
     113        df = self.doc.add_file(self.get_file("hello.pdf", data=PDF)) 
     114        response = self.get(self.doc_url + "pdf/")  
     115        formset = response.context["pdf_formset"] 
     116        self.assertEqual(1, formset.total_form_count()) 
     117        form = formset.forms[0] 
     118        self.assertTrue(form.fields["selected"].initial) 
     119        self.assertEqual(form.instance, df) 
     120 
     121    def test_select_pdf_one_pdf_document_pdf(self): 
     122        df = self.doc.add_file(self.get_file("hello.pdf", data=PDF)) 
     123        data = { 
     124                "Download" : "download", 
     125                'documentfile_set-TOTAL_FORMS': '1',  
     126                'documentfile_set-INITIAL_FORMS': '1', 
     127                'documentfile_set-0-id' : df.id, 
     128                'documentfile_set-0-document' : self.doc.id, 
     129                'documentfile_set-0-selected' : 'on', 
     130                } 
     131        response = self.client.get(self.doc_url + "pdf/", data=data) 
     132        self.check_pdf(response, 2) 
     133        self.assertTrue("world" in response.content) 
     134 
     135    def test_select_pdf_one_unselected_pdf_document_pdf(self): 
     136        df = self.doc.add_file(self.get_file("hello.pdf", data=PDF)) 
     137        data = { 
     138                "Download" : "download", 
     139                'documentfile_set-TOTAL_FORMS': '1',  
     140                'documentfile_set-INITIAL_FORMS': '1', 
     141                'documentfile_set-0-id' : df.id, 
     142                'documentfile_set-0-document' : self.doc.id, 
     143                'documentfile_set-0-selected' : '', 
     144                } 
     145        response = self.client.get(self.doc_url + "pdf/", data=data) 
     146        self.check_pdf(response, 1) 
     147        self.assertFalse("world" in response.content) 
     148 
     149    def test_select_pdf_two_pdf_document_html(self): 
     150        df1 = self.doc.add_file(self.get_file("hello1.pdf", data=PDF)) 
     151        df2 = self.doc.add_file(self.get_file("hello2.pdf", data=PDF)) 
     152        response = self.get(self.doc_url + "pdf/")  
     153        formset = response.context["pdf_formset"] 
     154        self.assertEqual(2, formset.total_form_count()) 
     155        for form in formset.forms: 
     156            self.assertTrue(form.fields["selected"].initial) 
     157            self.assertTrue(form.instance in (df1, df2))  
     158     
     159    def test_select_pdf_two_pdf_document_pdf(self): 
     160        df1 = self.doc.add_file(self.get_file("hello1.pdf", 
     161            data=PDF.replace("world", "worl1"))) 
     162        df2 = self.doc.add_file(self.get_file("hello2.pdf", 
     163            data=PDF.replace("world", "worl2"))) 
     164        data = { 
     165                "Download" : "download", 
     166                'documentfile_set-TOTAL_FORMS': '2',  
     167                'documentfile_set-INITIAL_FORMS': '1', 
     168                'documentfile_set-0-id' : df1.id, 
     169                'documentfile_set-0-document' : self.doc.id, 
     170                'documentfile_set-0-selected' : 'on', 
     171                'documentfile_set-1-id' : df2.id, 
     172                'documentfile_set-1-document' : self.doc.id, 
     173                'documentfile_set-1-selected' : '', 
     174                } 
     175        response = self.client.get(self.doc_url + "pdf/", data=data) 
     176        self.check_pdf(response, 2) 
     177        self.assertTrue("worl1" in response.content) 
     178        self.assertFalse("worl2" in response.content) 
     179 
     180    def test_select_pdf_one_pdf_part_html(self): 
     181        df = self.doc.add_file(self.get_file("hello.pdf", data=PDF)) 
     182        self.controller.attach_to_document(self.doc) 
     183        response = self.get(self.base_url + "pdf/")  
     184        formset = response.context["children"][0][1].formsets[0] 
     185        self.assertEqual(1, formset.total_form_count()) 
     186        form = formset.forms[0] 
     187        prefix = "pdf_self_%d-0" % self.doc.id 
     188        self.assertEqual(form.prefix, prefix) 
     189        self.assertTrue(form.fields["selected"].initial) 
     190        self.assertEqual(form.instance, df) 
     191 
     192    def test_select_pdf_one_pdf_part_pdf(self): 
     193        df = self.doc.add_file(self.get_file("hello.pdf", data=PDF)) 
     194        self.controller.attach_to_document(self.doc) 
     195        prefix = "pdf_self_%d" % self.doc.id 
     196        data = { 
     197                "Download" : "download", 
     198                prefix + '-TOTAL_FORMS': '1',  
     199                prefix + '-INITIAL_FORMS': '1', 
     200                prefix + '-0-id' : df.id, 
     201                prefix + '-0-document' : self.doc.id, 
     202                prefix + '-0-selected' : 'on', 
     203                } 
     204        response = self.client.get(self.base_url + "pdf/", data=data) 
     205        self.check_pdf(response, 2) 
     206        self.assertTrue("world" in response.content) 
     207 
     208    def test_select_pdf_chidlren_part_pdf(self): 
     209        child = PartController.create("Child", "Part", "f", 
     210                self.user, self.DATA, True, True) 
     211        self.doc.attach_to_part(child) 
     212        self.controller.add_child(child, 1, 1, 'm') 
     213        df = self.doc.add_file(self.get_file("hello.pdf", data=PDF)) 
     214        prefix = "pdf_%d_%d" % (self.controller.get_children()[0].link.id, 
     215                self.doc.id) 
     216        data = { 
     217                "Download" : "download", 
     218                prefix + '-TOTAL_FORMS': '1',  
     219                prefix + '-INITIAL_FORMS': '1', 
     220                prefix + '-0-id' : df.id, 
     221                prefix + '-0-document' : self.doc.id, 
     222                prefix + '-0-selected' : 'on', 
     223                } 
     224        response = self.client.get(self.base_url + "pdf/", data=data) 
     225        self.check_pdf(response, 2) 
     226        self.assertTrue("world" in response.content) 
     227 
  • branches/3D/openPLM/pdfgen/urls.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/pdfgen/views.py

    r817 r870  
    1818# 
    1919#    You should have received a copy of the GNU General Public License 
    20 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     20#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    2121# 
    2222# Contact : 
     
    272272    return r2r("select_pdf_doc.html", ctx, request) 
    273273 
     274class FakeLink(object): 
     275 
     276    def __init__(self, id, child): 
     277        self.id = id 
     278        self.child = child 
    274279 
    275280def select_pdf_part(request, ctx, obj): 
     
    283288    children = obj.get_children(-1) 
    284289    formsets = [] 
    285     self_link = ParentChildLink(child=obj.object) 
    286     self_link.id = "self" 
     290    self_link = FakeLink("self", child=obj.object) 
    287291    for level, link in [(0, self_link)] + list(children): 
    288292        link.formsets = [] 
  • branches/3D/openPLM/plmapp/admin.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/base_views.py

    r817 r870  
    1818# 
    1919#    You should have received a copy of the GNU General Public License 
    20 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     20#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    2121# 
    2222# Contact : 
  • branches/3D/openPLM/plmapp/controllers/__init__.py

    r595 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/controllers/base.py

    r595 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    134134    HISTORY = models.AbstractHistory 
    135135 
    136     def __init__(self, obj, user): 
    137         self._mail_blocked = False 
     136    def __init__(self, obj, user, block_mails=False, no_index=False): 
     137        self._mail_blocked = block_mails 
    138138        self._pending_mails = deque() 
    139139        self._user = user 
     
    143143        self.__permissions = {} 
    144144        self.object = obj 
     145        if no_index: 
     146            obj.no_index = True 
    145147 
    146148    def __setattr__(self, attr, value): 
  • branches/3D/openPLM/plmapp/controllers/document.py

    r842 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    333333        return can_detach 
    334334 
     335    def get_suggested_parts(self): 
     336        """ 
     337        Returns a QuerySet of parts an user may want to attach to 
     338        a future revision. 
     339        """ 
     340        attached_parts = self.get_attached_parts().select_related("part", 
     341                "part__state", "part__lifecycle").only("part") 
     342        parts = [] 
     343        for link in attached_parts: 
     344            part = link.part 
     345            try: 
     346                new = models.RevisionLink.objects.get(old=part).new 
     347                if new.is_draft: 
     348                    parts.append(new) 
     349                while models.RevisionLink.objects.filter(old=new).exists(): 
     350                    new = models.RevisionLink.objects.get(old=new).new 
     351                    if new.is_draft: 
     352                        parts.append(new) 
     353            except models.RevisionLink.DoesNotExist: 
     354                if not part.is_deprecated: 
     355                    parts.append(part) 
     356        qs = models.Part.objects.filter(id__in=(p.id for p in parts)) 
     357        qs = qs.select_related('type', 'reference', 'revision', 'name') 
     358        return qs 
     359 
    335360    def revise(self, new_revision, selected_parts=()): 
    336361        # same as PLMObjectController + duplicate files (and their thumbnails) 
     
    353378            new_doc.locker = None 
    354379            new_doc.save() 
     380        # attach the given parts 
    355381        for part in selected_parts: 
    356             if part.is_editable: 
    357                 rev.documentpartlink_document.create(part=part) 
     382            rev.documentpartlink_document.create(part=part) 
    358383 
    359384        return rev 
     
    457482                    self.delete_file(filename) 
    458483 
    459  
    460  
    461  
    462      
    463      
    464      
    465484    def cancel(self): 
    466485        """ 
  • branches/3D/openPLM/plmapp/controllers/group.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    6262    HISTORY = models.GroupHistory 
    6363 
    64     def __init__(self, obj, user): 
     64    def __init__(self, obj, user, block_mails=False, no_index=False): 
    6565        if hasattr(obj, "groupinfo"): 
    6666            obj = obj.groupinfo 
    67         super(GroupController, self).__init__(obj, user) 
     67        super(GroupController, self).__init__(obj, user, block_mails, no_index) 
    6868    
    6969    @classmethod 
     
    216216        if invitation.state != models.Invitation.PENDING: 
    217217            raise ValueError("Invalid invitation") 
     218        if self._user != invitation.guest: 
     219            raise PermissionError("You can not send this invitation.") 
    218220        ctx = { "group" : self.object, 
    219221                "invitation" : invitation, 
    220222                "guest" : self._user, 
    221223                } 
    222         subject = "[PLM] %s ask you to join the group %s" % (self._user, self.name)  
     224        subject = "[PLM] %s asks you to join the group %s" % (self._user, self.name)  
    223225        self._send_mail(send_mail, subject, [self.owner], ctx, "mails/invitation2") 
    224226 
     
    235237        if invitation.state != models.Invitation.PENDING: 
    236238            raise ValueError("Invalid invitation") 
     239        if self._user != invitation.owner: 
     240            raise PermissionError("You can not send this invitation.") 
    237241        ctx = { "group" : self.object, 
    238242                "invitation" : invitation, 
  • branches/3D/openPLM/plmapp/controllers/part.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    337337                            **form.extensions) 
    338338 
    339     def revise(self, new_revision): 
     339    def revise(self, new_revision, child_links=None, documents=(), 
     340            parents=()): 
     341        """ 
     342        Revises the part. Does the same thing as :meth:`.PLMObjectController.revise` 
     343        and: 
     344             
     345            * copies all :class:`.ParentChildLink` of *child_links*, with the 
     346              new revision as the new parent. If *child_links* is None (the 
     347              default), all current children are copied. If an empty sequence 
     348              is given, no links are copied. 
     349 
     350            * attaches all document of *documents*, by default, no documents 
     351              are attached. The method :meth:`get_suggested_documents` returns a 
     352              list of documents that should be interesting. 
     353 
     354            * replaces all parent links in *parents*. This arguments must be 
     355              a list of tuples (link (an instance of :class:`.ParentChildLink`), 
     356              parent (an instance of :class:`.PLMObject`)) where *parent* is 
     357              the parent whose the bom will be modified and *link* is the 
     358              source of data (quantity, unit, order...). *link* will be 
     359              ended if *parent* is a parent of the current part. 
     360              The method :meth:`get_suggested_parents` returns a list of 
     361              tuples that may interest the user who revises this part. 
     362        """ 
    340363        # same as PLMObjectController + add children 
    341364        new_controller = super(PartController, self).revise(new_revision) 
    342         for level, link in self.get_children(1): 
     365        # adds the children 
     366        if child_links is None: 
     367            child_links = (x.link for x in self.get_children(1)) 
     368        for link in child_links: 
    343369            link.clone(save=True, parent=new_controller.object) 
     370        # attach the documents 
     371        for doc in documents: 
     372            models.DocumentPartLink.objects.create(part=new_controller.object, 
     373                    document=doc) 
     374        # for each parent, replace its child with the new revision 
     375        now = datetime.datetime.today() 
     376        for link, parent in parents: 
     377            link.clone(save=True, parent=parent, child=new_controller.object) 
     378            if link.parent_id == parent.id: 
     379                link.end_time = now 
     380                link.save() 
    344381        return new_controller 
     382 
     383    def get_suggested_documents(self): 
     384        """ 
     385        Returns a QuerySet of documents that should be suggested when the 
     386        user revises the part. 
     387 
     388        A document is suggested if: 
     389         
     390            a. it is attached to the current part and: 
     391              
     392                1. it is a *draft* and its superior revisions, if they exist, 
     393                   are *not* attached to the part  
     394 
     395                   or 
     396 
     397                2. it is *official* and its superior revisions, if they exist, 
     398                   are *not* attached to the part 
     399 
     400                   or 
     401 
     402                3. it is *official* and a superior revision is attached *and* 
     403                   another superior revision is not attached to the part 
     404 
     405            b. it is *not* attached to the current part, an inferior revision 
     406               is attached to the part and: 
     407 
     408                1. it is a draft 
     409 
     410                   or 
     411 
     412                2. it is official 
     413                 
     414        """ 
     415        docs = [] 
     416        links = self.get_attached_documents() 
     417        attached_documents = set(link.document_id for link in links) 
     418        for link in links: 
     419            document = link.document 
     420            ctrl = PLMObjectController(document, self._user) 
     421            revisions = ctrl.get_next_revisions() 
     422            attached_revisions = [d for d in revisions if d.id in attached_documents] 
     423            other_revisions = set(revisions).difference(attached_revisions) 
     424            if not attached_revisions: 
     425                if document.is_draft or document.is_official: 
     426                    docs.append(document.id) 
     427            else: 
     428                if document.is_official and not other_revisions: 
     429                    docs.append(document.id) 
     430            for rev in other_revisions: 
     431                if rev.is_official or rev.is_draft: 
     432                    docs.append(rev.id) 
     433        return models.Document.objects.filter(id__in=docs) 
     434 
     435    def get_suggested_parents(self): 
     436        """ 
     437        Returns a list of suggested parents that should be suggested 
     438        when the part is revised. 
     439 
     440        This method returns a list of tuple (link (an instance of 
     441        :class:`.ParentChildLink`), parent (an instance of :class:`.PLMObject`)). 
     442        It does not returns a list of links, since it may suggest a part 
     443        that is not a parent but whose one of its previous revision is a parent. 
     444        We need a link to copy its data (order, quantity, unit and extensions). 
     445 
     446        A part is suggested as a parent if: 
     447 
     448            a. it is already a parent and: 
     449 
     450                1. no superior revisions are a parent and its state is draft 
     451                   or official 
     452     
     453                   or 
     454                 
     455                2. no superior revisions exist and its state is proposed. 
     456 
     457            b. it is not a parent, a previous revision is a parent, its state 
     458               is a draft or a parent. In that case, the link of the most 
     459               superior parent revision is used. 
     460 
     461        """ 
     462        parents = self.get_parents(1) 
     463        links = [] 
     464        ids = set(p.link.parent_id for p in parents) 
     465        for level, link in parents: 
     466            parent = link.parent 
     467            ctrl = PLMObjectController(parent, self._user) 
     468            revisions = ctrl.get_next_revisions() 
     469            attached_revisions = [d for d in revisions if d.id in ids] 
     470            other_revisions = set(revisions).difference(attached_revisions) 
     471            if not attached_revisions: 
     472                if parent.is_draft or parent.is_official or \ 
     473                    (parent.is_proposed and not other_revisions): 
     474                    links.append((link, parent)) 
     475            for p in other_revisions: 
     476                if p.is_draft or p.is_official: 
     477                    links.append((link, p.part)) 
     478        # it is possible that some parts are suggested twice or more 
     479        # if they are not a parent (they are a superior revision of a parent) 
     480        # so we must clean up links 
     481        links2 = dict() # id -> (link, parent) 
     482        for link, parent in links: 
     483            if parent.id in ids: 
     484                links2[parent.id] = (link, parent) 
     485            else: 
     486                # it is not a parent 
     487                try: 
     488                    l, p = links2[parent.id] 
     489                    if l.parent.ctime < link.parent.ctime: 
     490                        # true if parent is a superior revision 
     491                        links2[parent.id] = (link, parent) 
     492                except KeyError: 
     493                    links2[parent.id] = (link, parent) 
     494        return links2.values() 
    345495 
    346496    def attach_to_document(self, document): 
  • branches/3D/openPLM/plmapp/controllers/plmobject.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/controllers/user.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    6666    __slots__ = Controller.__slots__ + ("creator", "owner", "mtime", "ctime") 
    6767 
    68     def __init__(self, obj, user): 
    69         super(UserController, self).__init__(obj, user) 
     68    def __init__(self, obj, user, block_mails=False, no_index=False): 
     69        super(UserController, self).__init__(obj, user, block_mails, no_index) 
    7070        self.creator = obj 
    7171        self.owner = obj 
  • branches/3D/openPLM/plmapp/exceptions.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/filehandlers/__init__.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/filehandlers/base.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/filehandlers/odfhandler.py

    r296 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/filehandlers/pdfhandler.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/forms.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    222222            label=_("Select a type")) 
    223223 
    224 class TypeSearchForm(TypeForm): 
    225     pass 
    226224 
    227225class FakeItems(object): 
     
    368366            for field in fields: 
    369367                field_name = "%s_%s" % (PCLE._meta.module_name, field) 
    370                 data[field] = self.cleaned_data[field_name] 
     368                try: 
     369                    data[field] = self.cleaned_data[field_name] 
     370                except KeyError: 
     371                    # the form is invalid 
     372                    pass 
    371373            self.extensions[PCLE._meta.module_name] = data 
    372374        return self.cleaned_data 
     
    400402            for field in fields: 
    401403                field_name = "%s_%s" % (PCLE._meta.module_name, field) 
    402                 data[field] = self.cleaned_data[field_name] 
     404                try: 
     405                    data[field] = self.cleaned_data[field_name] 
     406                except KeyError: 
     407                    # the form is invalid 
     408                    pass 
    403409            self.extensions[PCLE._meta.module_name] = data 
    404410        return self.cleaned_data 
     
    450456        fields = ["document", "part"] 
    451457 
    452 class SelectPLMObjectForm(RelPartForm): 
     458class SelectPartForm(forms.ModelForm): 
    453459    selected = forms.BooleanField(required=False, initial=True) 
    454          
    455 SelectPLMObjectFormset = modelformset_factory(m.DocumentPartLink, 
    456                         form=SelectPLMObjectForm, extra=0) 
     460    class Meta: 
     461        model = m.Part 
     462        fields = ["selected"] 
     463         
     464SelectPartFormset = modelformset_factory(m.Part, form=SelectPartForm, extra=0) 
     465 
     466 
     467class SelectDocumentForm(forms.Form): 
     468    selected = forms.BooleanField(required=False, initial=True) 
     469    document = forms.ModelChoiceField(queryset=m.Document.objects.all(), 
     470                                   widget=forms.HiddenInput()) 
     471         
     472SelectDocumentFormset = formset_factory(form=SelectDocumentForm, extra=0) 
     473 
     474class SelectChildForm(forms.Form): 
     475    selected = forms.BooleanField(required=False, initial=True) 
     476    link = forms.ModelChoiceField(queryset=m.ParentChildLink.objects.all(), 
     477                                   widget=forms.HiddenInput()) 
     478SelectChildFormset = formset_factory(form=SelectChildForm, extra=0) 
     479 
     480 
     481class SelectParentForm(SelectChildForm): 
     482    selected = forms.BooleanField(required=False, initial=False) 
     483    new_parent = forms.ModelChoiceField(queryset=m.Part.objects.all(), 
     484                                   widget=forms.HiddenInput()) 
     485SelectParentFormset = formset_factory(form=SelectParentForm, extra=0) 
     486 
     487 
    457488 
    458489class AddRelPartForm(PLMObjectForm): 
     
    563594 
    564595class OpenPLMUserChangeForm(forms.ModelForm): 
    565     #username = forms.RegexField(widget=forms.HiddenInput()) 
     596 
    566597    class Meta: 
    567598        model = User 
    568         exclude = ('username','is_staff', 'is_active', 'is_superuser', 'last_login', 'date_joined', 'groups', 'user_permissions', 'password') 
     599        exclude = ('username','is_staff', 'is_active', 'is_superuser', 
     600                'last_login', 'date_joined', 'groups', 'user_permissions', 
     601                'password') 
    569602 
    570603class SelectUserForm(forms.Form): 
  • branches/3D/openPLM/plmapp/lifecycle.py

    r438 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/mail.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/models.py

    r842 r870  
    1818# 
    1919#    You should have received a copy of the GNU General Public License 
    20 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     20#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    2121# 
    2222# Contact : 
     
    743743 
    744744def _get_all_subclasses(base, d): 
    745     if base.__name__ not in d: 
     745    if base.__name__ not in d and not base._deferred: 
    746746        d[base.__name__] = base 
    747747    for part in base.__subclasses__(): 
     
    879879                if native == name and ext in native_to_standards[native_ext.lower()]: 
    880880                    return False                                 
     881      
    881882        return True 
    882883         
  • branches/3D/openPLM/plmapp/navigate.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    3434from collections import defaultdict 
    3535 
    36 from django.contrib.auth.models import User 
     36from django.contrib.auth.models import User, Group 
    3737from django.template.loader import render_to_string 
    3838from django.utils.html import linebreaks 
     
    4242from openPLM.plmapp import models 
    4343from openPLM.plmapp.controllers import PLMObjectController, PartController,\ 
    44                                        DocumentController, GroupController 
     44                                       GroupController 
    4545from openPLM.plmapp.controllers.user import UserController 
    4646 
     
    130130        # a PLMObject and an user may have the same id, so we add a variable 
    131131        # which tells if results contains users 
    132         self.users_result = False 
     132        self.users_result = self.groups_result = False 
    133133        if results: 
    134134            self.users_result = hasattr(results[0], "username") 
    135         self.options_list = ("child", "parents", "doc", "cad", "owner", "signer", 
    136                              "notified", "part", "owned", "to_sign", 
    137                              "request_notification_from", OSR)  
    138         self.options = dict.fromkeys(self.options_list, False) 
     135            self.groups_result = isinstance(results[0], Group) 
     136        self.plmobjects_result = not (self.groups_result or self.users_result) 
     137        options = ("child", "parents", "doc", "owner", "signer", 
     138                   "notified", "part", "owned", "to_sign", 
     139                   "request_notification_from", OSR)  
     140        self.options = dict.fromkeys(options, False) 
    139141        self.options["prog"] = "dot" 
    140142        self.options["doc_parts"] = [] 
     
    145147        self.graph.node_attr.update(self.NODE_ATTRIBUTES) 
    146148        self.graph.edge_attr.update(self.EDGE_ATTRIBUTES) 
    147         self.title_to_nodes = {} 
     149        self._title_to_node = {} 
    148150        self._part_to_node = {} 
    149151 
     
    164166             parents    If True, adds recursively all parents of the root 
    165167             doc        If True, adds documents attached to the parts 
    166              cad        Not yet implemented 
    167168             owner      If True, adds the owner of the root 
    168169             signer     If True, adds the signers of the root 
     
    200201        
    201202    def _create_child_edges(self, obj, *args): 
    202         if self.options[OSR] and self.users_result: 
     203        if self.options[OSR] and not self.plmobjects_result: 
    203204            return 
    204205        for child_l in obj.get_children(max_level=-1, related=("child",)): 
     
    215216     
    216217    def _create_parents_edges(self, obj, *args): 
    217         if self.options[OSR] and self.users_result: 
     218        if self.options[OSR] and not self.plmobjects_result: 
    218219            return 
    219220        for parent_l in obj.get_parents(max_level=-1, related=("parent",)): 
     
    226227            label = "Qty: %.2f %s\\nOrder: %d" % (link.quantity, 
    227228                    link.get_shortened_unit(), link.order)  
    228             self.edges.add((parent.id, obj.id, label)) 
     229            self.edges.add((parent.id, link.child_id, label)) 
    229230            self._set_node_attributes(parent) 
    230231    
    231232    def _create_part_edges(self, obj, *args): 
    232         if self.options[OSR] and self.users_result: 
     233        if self.options[OSR] and not self.plmobjects_result: 
    233234            return 
    234235        if isinstance(obj, GroupController): 
     
    251252     
    252253    def _create_doc_edges(self, obj, obj_id=None, *args): 
    253         if self.options[OSR] and self.users_result: 
     254        if self.options[OSR] and not self.plmobjects_result: 
    254255            return 
    255256        if isinstance(obj, GroupController): 
     
    262263                self._set_node_attributes(doc) 
    263264        else: 
    264             for document_item in obj.get_attached_documents().select_related("document").only(*_documents_attrs): 
    265                 if self.options[OSR] and document_item.document.id not in self.results: 
     265            links = obj.get_attached_documents().select_related("document") 
     266            for link in links.only(*_documents_attrs): 
     267                if self.options[OSR] and link.document_id not in self.results: 
    266268                    continue 
    267                 document = DocumentController(document_item.document, None) 
    268                 self.edges.add((obj_id or obj.id, document.id, " ")) 
    269                 self._set_node_attributes(document) 
     269                self.edges.add((obj_id or obj.id, link.document_id, " ")) 
     270                self._set_node_attributes(link.document) 
    270271 
    271272    def _create_user_edges(self, obj, role): 
     
    290291 
    291292    def _create_object_edges(self, obj, role): 
    292         if self.options[OSR] and self.users_result: 
     293        if self.options[OSR] and not self.plmobjects_result: 
    293294            return 
    294295        node = "User%d" % obj.id 
     
    308309                if plmobject.is_part: 
    309310                    if plmobject.id in self.options["doc_parts"]: 
    310                         plmobject = PartController(plmobject.part, None) 
     311                        plmobject = PartController(plmobject.part, None, True, True) 
    311312                        self._create_doc_edges(plmobject, part_doc_id) 
    312313                self._set_node_attributes(plmobject, part_doc_id) 
     
    323324                if part_doc.is_part: 
    324325                    if part_doc.id in self.options["doc_parts"]: 
    325                         part_doc = PartController(part_doc.part, None) 
     326                        part_doc = PartController(part_doc.part, None, True, True) 
    326327                        self._create_doc_edges(part_doc, part_doc_id) 
    327328                self._set_node_attributes(part_doc, part_doc_id) 
     
    339340        else: 
    340341            id_ = self.object.id 
    341         #self.graph.add_node(id_) 
    342342        node = self.nodes[id_] 
    343343        self._set_node_attributes(self.object, id_) 
     
    345345        node["width"] = 110. / 96  
    346346        node["height"] = 80. / 96  
    347         functions_dic = {'child':(self._create_child_edges, None), 
    348                          'parents':(self._create_parents_edges, None), 
    349                          'owner':(self._create_user_edges, 'owner'), 
    350                          'signer':(self._create_user_edges, 'sign'), 
    351                          'notified':(self._create_user_edges, 'notified'), 
    352                          'user':(self._create_user_edges, 'member'), 
    353                          'part': (self._create_part_edges, None), 
    354                          'owned':(self._create_object_edges, 'owner'), 
    355                          'to_sign':(self._create_object_edges, 'sign'), 
    356                          'request_notification_from':(self._create_object_edges, 'notified'), 
    357                          } 
     347        opt_to_meth = { 
     348            'child' : (self._create_child_edges, None), 
     349            'parents' : (self._create_parents_edges, None), 
     350            'owner' : (self._create_user_edges, 'owner'), 
     351            'signer' : (self._create_user_edges, 'sign'), 
     352            'notified' : (self._create_user_edges, 'notified'), 
     353            'user' : (self._create_user_edges, 'member'), 
     354            'part' : (self._create_part_edges, None), 
     355            'owned' : (self._create_object_edges, 'owner'), 
     356            'to_sign' : (self._create_object_edges, 'sign'), 
     357            'request_notification_from' : (self._create_object_edges, 'notified'), 
     358        } 
    358359        for field, value in self.options.iteritems(): 
    359             if value and field in functions_dic: 
    360                 function, argument = functions_dic[field] 
     360            if value and field in opt_to_meth: 
     361                function, argument = opt_to_meth[field] 
    361362                function(self.object, argument) 
    362363        # now that all parts have been added, we can add the documents 
    363364        if self.options["doc"]: 
    364             if isinstance(self.object, GroupController): 
    365                 self._create_doc_edges(self.object, None) 
    366             links = models.DocumentPartLink.objects.\ 
    367                     filter(part__in=self._part_to_node.keys()) 
    368             for link in links.select_related("document"): 
    369                 if self.options[OSR] and link.document_id not in self.results: 
    370                     continue 
    371                  
    372                 self.edges.add((link.part_id, link.document_id, " ")) 
    373                 self._set_node_attributes(link.document) 
     365            if not (self.options[OSR] and not self.plmobjects_result): 
     366                if isinstance(self.object, GroupController): 
     367                    self._create_doc_edges(self.object, None) 
     368                links = models.DocumentPartLink.objects.\ 
     369                        filter(part__in=self._part_to_node.keys()) 
     370                for link in links.select_related("document"): 
     371                    if self.options[OSR] and link.document_id not in self.results: 
     372                        continue 
     373                     
     374                    self.edges.add((link.part_id, link.document_id, " ")) 
     375                    self._set_node_attributes(link.document) 
    374376 
    375377        elif not isinstance(self.object, UserController): 
    376             ids = self.options["doc_parts"].intersection(self._part_to_node.keys())  
    377             links = models.DocumentPartLink.objects.filter(part__in=ids) 
    378             for link in links.select_related("document"): 
    379                 if self.options[OSR] and link.document_id not in self.results: 
    380                     continue 
    381                  
    382                 self.edges.add((link.part_id, link.document_id, " ")) 
    383                 self._set_node_attributes(link.document) 
     378            if not (self.options[OSR] and not self.plmobjects_result): 
     379                ids = self.options["doc_parts"].intersection(self._part_to_node.keys())  
     380                links = models.DocumentPartLink.objects.filter(part__in=ids) 
     381                for link in links.select_related("document"): 
     382                    if self.options[OSR] and link.document_id not in self.results: 
     383                        continue 
     384                     
     385                    self.edges.add((link.part_id, link.document_id, " ")) 
     386                    self._set_node_attributes(link.document) 
    384387 
    385388        # treats the parts to see if they have an attached document 
     
    404407            # already treated 
    405408            return 
    406         # data and title_to_nodes are used to retrieve usefull data (url, tooltip) 
    407         # in convert_map 
     409        # data and _title_to_node are used to retrieve usefull data (url, tooltip) 
     410        # in _convert_map 
    408411        data = {} 
    409412         
     
    439442                id=id_, 
    440443                ) 
    441         self.title_to_nodes[id_] = data 
    442  
    443     def convert_map(self, map_string): 
     444        self._title_to_node[id_] = data 
     445 
     446    def _convert_map(self, map_string): 
    444447        elements = [] 
    445448        ajax_navigate = "/ajax/navigate/" + get_path(self.object) 
     
    456459                continue 
    457460 
    458             data = self.title_to_nodes.get(area.get("id"), {}) 
     461            data = self._title_to_node.get(area.get("id"), {}) 
    459462             
    460463            # compute css position of the div 
     
    482485        return u"\n".join(elements) 
    483486 
    484     def parse_svg(self, svg): 
     487    def _parse_svg(self, svg): 
    485488        # TODO: optimize this function 
    486489        edges = [] 
     
    512515    def render(self): 
    513516        """ 
    514         Renders an image of the graph 
    515  
    516         :returns: a tuple (image map data, url of the image, path of the image) 
     517        Renders an image of the graph. 
     518 
     519        :returns: a tuple (html content, javascript content) 
    517520        """ 
    518521        warnings.simplefilter('ignore', RuntimeWarning) 
     
    536539        map_string = s.read() 
    537540        self.graph.clear() 
    538         return self.convert_map(map_string), self.parse_svg(svg.read()) 
     541        warnings.simplefilter('default', RuntimeWarning) 
     542        return self._convert_map(map_string), self._parse_svg(svg.read()) 
     543 
  • branches/3D/openPLM/plmapp/tests/__init__.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    4141from openPLM.plmapp.tests.pcle import * 
    4242from openPLM.plmapp.tests.gestion_document_native import * 
     43from openPLM.plmapp.tests.navigate import * 
    4344 
    4445import openPLM.plmapp.models 
  • branches/3D/openPLM/plmapp/tests/closure.py

    r261 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/tests/controllers/document.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/tests/controllers/group.py

    r472 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/tests/controllers/part.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    471471        self.assertFalse(doc.get_attached_parts()) 
    472472 
    473  
     473    def test_get_suggested_documents_official(self): 
     474        # self.document is official and has not been revised 
     475        # self.controller and self.controller2 must suggest it 
     476        self.assertTrue(self.document.is_official) 
     477        for ctrl in (self.controller, self.controller2): 
     478            suggested = ctrl.get_suggested_documents() 
     479            self.assertEqual(1, suggested.count()) 
     480            self.assertTrue(self.document.object in suggested) 
     481        # self.controller3 has no document attached: it suggests an empty list 
     482        suggested = self.controller3.get_suggested_documents() 
     483        self.assertFalse(suggested) 
     484 
     485    def test_get_suggested_documents_draft(self): 
     486        self.document.object.state = self.document.lifecycle.first_state 
     487        self.document.object.save() 
     488        self.assertTrue(self.document.is_draft) 
     489        # self.controller and self.controller2 must suggest it 
     490        for ctrl in (self.controller, self.controller2): 
     491            suggested = ctrl.get_suggested_documents() 
     492            self.assertEqual(1, suggested.count()) 
     493            self.assertTrue(self.document.object in suggested) 
     494        # self.controller3 has no document attached: it suggests an empty list 
     495        suggested = self.controller3.get_suggested_documents() 
     496        self.assertFalse(suggested) 
     497 
     498    def test_get_suggested_documents_proposed(self): 
     499        lcl = LifecycleList("dpop","official", "draft",  
     500               "proposed", "official", "deprecated") 
     501        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     502        self.document.object.lifecycle = lc 
     503        self.document.object.state = models.State.objects.get(name="proposed") 
     504        self.document.object.save() 
     505        self.assertTrue(self.document.is_proposed) 
     506        for ctrl in (self.controller, self.controller2, self.controller3): 
     507            suggested = ctrl.get_suggested_documents() 
     508            self.assertFalse(suggested) 
     509 
     510    def test_get_suggested_documents_deprecated(self): 
     511        self.document.promote() 
     512        self.assertTrue(self.document.is_deprecated) 
     513        for ctrl in (self.controller, self.controller2, self.controller3): 
     514            suggested = ctrl.get_suggested_documents() 
     515            self.assertFalse(suggested) 
     516 
     517    def test_get_suggested_documents_revision_attached(self): 
     518        lcl = LifecycleList("dpop","official", "draft",  
     519               "proposed", "official", "deprecated") 
     520        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     521        self.document.object.lifecycle = lc 
     522        self.document.object.state = lc.first_state 
     523        self.document.object.save() 
     524        rev = self.document.revise('b') 
     525        self.controller.attach_to_document(rev) 
     526        for state in lcl: 
     527            self.document.object.state = models.State.objects.get(name=state) 
     528            self.document.object.save() 
     529            suggested = self.controller.get_suggested_documents() 
     530            if state == "official": 
     531                self.assertEqual(2, suggested.count()) 
     532                self.assertTrue(rev.object in suggested) 
     533                self.assertTrue(self.document.object in suggested) 
     534            else: 
     535                self.assertEqual(1, suggested.count()) 
     536                self.assertTrue(rev.object in suggested) 
     537                self.assertFalse(self.document.object in suggested) 
     538 
     539    def test_get_suggested_documents_revision_not_attached(self): 
     540        lcl = LifecycleList("dpop","official", "draft",  
     541               "proposed", "official", "deprecated") 
     542        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     543        self.document.object.lifecycle = lc 
     544        self.document.object.state = lc.first_state 
     545        self.document.object.save() 
     546        rev = self.document.revise('b') 
     547        for state in lcl: 
     548            self.document.object.state = models.State.objects.get(name=state) 
     549            self.document.object.save() 
     550            for rev_state in lcl: 
     551                rev.object.state = models.State.objects.get(name=rev_state) 
     552                rev.object.save() 
     553 
     554                suggested = self.controller.get_suggested_documents() 
     555                if state in ("draft", "official"): 
     556                    self.assertTrue(self.document.object in suggested) 
     557                else: 
     558                    self.assertFalse(self.document.object in suggested) 
     559                if rev_state in ("draft", "official"): 
     560                    self.assertTrue(rev.object in suggested) 
     561                else: 
     562                    self.assertFalse(rev.object in suggested) 
     563    
     564    def test_get_suggested_documents_two_revisions(self): 
     565        lcl = LifecycleList("dpop","official", "draft",  
     566               "proposed", "official", "deprecated") 
     567        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     568        self.document.object.lifecycle = lc 
     569        self.document.object.state = lc.first_state 
     570        self.document.object.save() 
     571        revb = self.document.revise('b') 
     572        revc = revb.revise('c') 
     573        revb.attach_to_part(self.controller) 
     574        for state in lcl: 
     575            self.document.object.state = models.State.objects.get(name=state) 
     576            self.document.object.save() 
     577            for revb_state in lcl: 
     578                revb.object.state = models.State.objects.get(name=revb_state) 
     579                revb.object.save() 
     580                for revc_state in lcl: 
     581                    revc.object.state = models.State.objects.get(name=revc_state) 
     582                    revc.object.save() 
     583 
     584                    suggested = self.controller.get_suggested_documents() 
     585                    self.assertFalse(self.document.object in suggested) 
     586                    if revb_state in ("draft", "official"): 
     587                        self.assertTrue(revb.object in suggested) 
     588                    else: 
     589                        self.assertFalse(revb.object in suggested) 
     590 
     591                    if revc_state in ("draft", "official"): 
     592                        self.assertTrue(revc.object in suggested) 
     593                    else: 
     594                        self.assertFalse(revc.object in suggested) 
     595 
     596    def test_revise_attached_documents(self): 
     597        """ 
     598        Revises a part with two attached documents, both are selected. 
     599        """ 
     600        document = DocumentController.create("DocDSDS", "Document", "a", 
     601                self.user, self.DATA) 
     602        document.attach_to_part(self.controller) 
     603 
     604        rev = self.controller.revise("b", documents=(self.document.object, 
     605            document.object)) 
     606        attached1 = self.controller.get_attached_documents() 
     607        attached2 = rev.get_attached_documents() 
     608        for attached in (attached1, attached2): 
     609            self.assertEqual(2, attached.count()) 
     610            docs = attached.values_list("document", flat=True) 
     611            self.assertTrue(self.document.id in docs) 
     612            self.assertTrue(document.id in docs) 
     613 
     614    def test_revise_attached_documents_one_selected(self): 
     615        """ 
     616        Revises a part with two attached documents, one is selected. 
     617        """ 
     618        document = DocumentController.create("DocDSDS", "Document", "a", 
     619                self.user, self.DATA) 
     620        document.attach_to_part(self.controller) 
     621 
     622        rev = self.controller.revise("b", documents=(self.document.object, )) 
     623             
     624        attached1 = self.controller.get_attached_documents() 
     625        docs = attached1.values_list("document", flat=True) 
     626 
     627        self.assertEqual(2, attached1.count()) 
     628        self.assertTrue(self.document.id in docs) 
     629        self.assertTrue(document.id in docs) 
     630 
     631        attached2 = rev.get_attached_documents() 
     632        docs = attached2.values_list("document", flat=True) 
     633        self.assertEqual(1, attached2.count()) 
     634        self.assertTrue(self.document.id in docs) 
     635        self.assertFalse(document.id in docs) 
     636 
     637    def test_revise_attached_documents_none_selected(self): 
     638        """ 
     639        Revises a part with two attached documents, none are selected. 
     640        """ 
     641        document = DocumentController.create("DocDSDS", "Document", "a", 
     642                self.user, self.DATA) 
     643        document.attach_to_part(self.controller) 
     644 
     645        rev = self.controller.revise("b", documents=()) 
     646             
     647        attached1 = self.controller.get_attached_documents() 
     648        docs = attached1.values_list("document", flat=True) 
     649 
     650        self.assertEqual(2, attached1.count()) 
     651        self.assertTrue(self.document.id in docs) 
     652        self.assertTrue(document.id in docs) 
     653 
     654        attached2 = rev.get_attached_documents() 
     655        self.assertFalse(attached2) 
     656 
     657    def test_revise_no_child(self): 
     658        """ 
     659        Revises a part and chooses to not add current children. 
     660        """ 
     661        self.add_child() 
     662        rev = self.controller.revise("b", child_links=()) 
     663        self.assertFalse(rev.get_children()) 
     664 
     665    def test_revise_one_child(self): 
     666        """ 
     667        Revises a part and chooses to only add one child. 
     668        """ 
     669        self.controller.add_child(self.controller2, 10, 15, "m") 
     670        self.controller.add_child(self.controller3, 20, 35, "kg") 
     671        links = [c.link for c in self.controller.get_children(1)] 
     672        rev = self.controller.revise('b', child_links=(links[0],)) 
     673        links2 = [c.link for c in rev.get_children(1)] 
     674        self.assertEqual(1, len(links2)) 
     675        self.assertEqual(links[0].child, links2[0].child) 
     676        self.assertEqual(links[0].quantity, links2[0].quantity) 
     677        self.assertEqual(links[0].order, links2[0].order) 
     678        self.assertEqual(links[0].unit, links2[0].unit) 
     679 
     680    def test_get_suggested_parents_no_revision(self): 
     681        lcl = LifecycleList("dpop","official", "draft",  
     682               "proposed", "official", "deprecated") 
     683        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     684        self.add_child() 
     685        self.controller.object.lifecycle = lc 
     686        for state in lcl[:-1]: 
     687            self.controller.object.state = models.State.objects.get(name=state) 
     688            self.controller.object.save() 
     689 
     690            suggested = self.controller2.get_suggested_parents() 
     691            self.assertEqual(1, len(suggested)) 
     692            link, parent = suggested[0] 
     693            self.assertEqual(parent.id, self.controller.id) 
     694            self.assertEqual(link, self.controller.get_children(1)[0].link) 
     695        # deprecated state 
     696        self.controller.object.state = models.State.objects.get(name="deprecated") 
     697        self.controller.object.save() 
     698        suggested = self.controller2.get_suggested_parents() 
     699        self.assertFalse(suggested) 
     700 
     701    def test_get_suggested_parents_revision_attached(self): 
     702        lcl = LifecycleList("dpop","official", "draft",  
     703               "proposed", "official", "deprecated") 
     704        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     705        self.controller.object.lifecycle = lc 
     706        self.controller.object.state = lc.first_state 
     707        self.controller.object.save() 
     708        self.add_child() 
     709        rev = self.controller.revise('b') 
     710        self.assertEqual(1, len(rev.get_children(1))) 
     711        for state in lcl: 
     712            self.controller.object.state = models.State.objects.get(name=state) 
     713            self.controller.object.save() 
     714            suggested = self.controller2.get_suggested_parents() 
     715            self.assertEqual(1, len(suggested)) 
     716            link, parent = suggested[0] 
     717            self.assertEqual(parent.id, rev.id) 
     718 
     719    def test_get_suggested_parents_revision_not_attached(self): 
     720        lcl = LifecycleList("dpop","official", "draft",  
     721               "proposed", "official", "deprecated") 
     722        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     723        self.controller.object.lifecycle = lc 
     724        self.controller.object.state = lc.first_state 
     725        self.controller.object.save() 
     726        self.add_child() 
     727        rev = self.controller.revise('b', child_links=()) 
     728        for state in lcl: 
     729            self.controller.object.state = models.State.objects.get(name=state) 
     730            self.controller.object.save() 
     731            for rev_state in lcl: 
     732                rev.object.state = models.State.objects.get(name=rev_state) 
     733                rev.object.save() 
     734 
     735                suggested = self.controller2.get_suggested_parents() 
     736                parents = set(p[1].id for p in suggested) 
     737                if state in ("draft", "official"): 
     738                    self.assertTrue(self.controller.id in parents) 
     739                else: 
     740                    self.assertFalse(self.controller.id in parents) 
     741                if rev_state in ("draft", "official"): 
     742                    self.assertTrue(rev.id in parents) 
     743                else: 
     744                    self.assertFalse(rev.id in parents) 
     745    
     746    def test_get_suggested_parents_two_revisions(self): 
     747        lcl = LifecycleList("dpop","official", "draft",  
     748               "proposed", "official", "deprecated") 
     749        lc = models.Lifecycle.from_lifecyclelist(lcl) 
     750        self.controller.object.lifecycle = lc 
     751        self.controller.object.state = lc.first_state 
     752        self.controller.object.save() 
     753        self.add_child() 
     754        revb = self.controller.revise('b') 
     755        revc = revb.revise('c', child_links=()) 
     756        expected_link = models.ParentChildLink.objects.get(parent=revb.id, 
     757                child=self.controller2.id) 
     758        for state in lcl: 
     759            self.controller.object.state = models.State.objects.get(name=state) 
     760            self.controller.object.save() 
     761            for revb_state in lcl: 
     762                revb.object.state = models.State.objects.get(name=revb_state) 
     763                revb.object.save() 
     764                for revc_state in lcl: 
     765                    revc.object.state = models.State.objects.get(name=revc_state) 
     766                    revc.object.save() 
     767 
     768                    suggested = self.controller2.get_suggested_parents() 
     769                    parents = set(p[1].id for p in suggested) 
     770                    self.assertFalse(self.controller.id in parents) 
     771                    if revb_state in ("draft", "official"): 
     772                        self.assertTrue(revb.id in parents) 
     773                    else: 
     774                        self.assertFalse(revb.id in parents) 
     775 
     776                    if revc_state in ("draft", "official"): 
     777                        self.assertTrue(revc.id in parents) 
     778                        link = [p[0] for p in suggested if p[1].id == revc.id][0] 
     779                        self.assertEqual(link, expected_link) 
     780                    else: 
     781                        self.assertFalse(revc.id in parents) 
     782 
     783    def test_revise_no_parent(self): 
     784        """ 
     785        Revises a part and chooses to not change parents links. 
     786        """ 
     787        self.add_child() 
     788        rev = self.controller2.revise("b", child_links=()) 
     789        self.assertFalse(rev.get_children()) 
     790        links = [c.link for c in self.controller.get_children(1)] 
     791        self.assertEqual(1, len(links)) 
     792        self.assertEqual(self.controller.id, links[0].parent_id) 
     793        self.assertEqual(self.controller2.id, links[0].child_id) 
     794 
     795    def test_revise_one_parent(self): 
     796        """ 
     797        Revises a part and chooses to change one parent. 
     798        """ 
     799        self.controller.add_child(self.controller3, 10, 15, "m") 
     800        self.controller2.add_child(self.controller3, 20, 35, "kg") 
     801        links = [c.link for c in self.controller.get_children(1)] 
     802        if links[0].id == self.controller.id: 
     803            parent, other = self.controller, self.controller2 
     804        else: 
     805            parent, other = self.controller2, self.controller 
     806        rev = self.controller3.revise('b', parents=((links[0], parent.object),)) 
     807     
     808        parents = [p.link for p in rev.get_parents(1)] 
     809        self.assertEqual(1, len(parents)) 
     810        self.assertEqual(parents[0].child_id, rev.id) 
     811        self.assertEqual(parents[0].parent_id, parent.id) 
     812         
     813        children = [c.link for c in parent.get_children(1)] 
     814        self.assertEqual(1, len(children)) 
     815        self.assertEqual(children[0].child_id, rev.id) 
     816 
     817        children_o = [c.link for c in other.get_children(1)] 
     818        self.assertEqual(1, len(children)) 
     819        self.assertEqual(children_o[0].child_id, self.controller3.id) 
     820 
  • branches/3D/openPLM/plmapp/tests/controllers/plmobject.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/tests/filehandlers.py

    r472 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/tests/gestion_document_native.py

    r846 r870  
    11 
    22from openPLM.plmapp.controllers.document import DocumentController 
     3from openPLM.plmapp.exceptions import LockError 
    34from openPLM.plmapp.tests.base import BaseTestCase 
    4 from openPLM.plmapp.exceptions import LockError 
    5  
    65class DocNativeTestCase(BaseTestCase): 
    76 
  • branches/3D/openPLM/plmapp/tests/lifecycle.py

    r472 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/tests/pcle.py

    r674 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    340340        self.assertEqual(3, models.ParentChildLink.objects.count()) 
    341341        self.assertEqual(2, MockExtension.objects.count()) 
     342 
     343    def test_bom_view(self): 
     344        """ 
     345        Tests the bom view with a custom attribute. 
     346        """ 
     347        self.controller.add_child(self.controller2, 10, 15, "-", 
     348                **{mockext:{"custom_attribute":"val1"}}) 
     349        self.client.login(username=self.user.username, password="password") 
     350        response = self.client.get(self.controller.plmobject_url + "BOM-child/") 
     351        children = response.context["children"] 
     352        self.assertEqual(1, len(children)) 
     353        extra_columns = response.context["extra_columns"] 
     354        self.assertEqual([(u"custom_attribute", "custom attribute")], extra_columns) 
     355        extension_data = response.context["extension_data"] 
     356        link = self.controller.get_children()[0].link 
     357        self.assertEqual({link : {u"custom_attribute" : "val1"}}, extension_data) 
     358         
     359    def test_bom_edit_post(self): 
     360        fname = mockext + "_custom_attribute" 
     361        self.controller.add_child(self.controller2, 10, 15, "-", 
     362                **{mockext:{"custom_attribute":"val1"}}) 
     363        self.client.login(username=self.user.username, password="password") 
     364        data = { 
     365            'form-TOTAL_FORMS': u'1', 
     366            'form-INITIAL_FORMS': u'1', 
     367            'form-MAX_NUM_FORMS': u'', 
     368            'form-0-child' : self.controller2.id, 
     369            'form-0-id' : self.controller.get_children()[0].link.id, 
     370            'form-0-order' : 45, 
     371            'form-0-parent': self.controller.id, 
     372            'form-0-quantity' : '45.0', 
     373            'form-0-unit' : 'cm', 
     374            'form-0-%s' % fname : 'new_value', 
     375        } 
     376        response = self.client.post(self.controller.plmobject_url + "BOM-child/edit/", 
     377                data, follow=True) 
     378        self.assertEqual(response.status_code, 200) 
     379        link = self.controller.get_children()[0].link 
     380        self.assertEqual(45.0, link.quantity) 
     381        self.assertEqual(45, link.order) 
     382        self.assertEqual('cm', link.unit) 
     383        self.assertEqual("new_value", link.extensions[0].custom_attribute) 
     384 
     385    def test_bom_edit_post_error_invalid_value(self): 
     386        fname = mockext + "_custom_attribute" 
     387        self.controller.add_child(self.controller2, 10, 15, "-", 
     388                **{mockext:{"custom_attribute":"val1"}}) 
     389        self.client.login(username=self.user.username, password="password") 
     390        data = { 
     391            'form-TOTAL_FORMS': u'1', 
     392            'form-INITIAL_FORMS': u'1', 
     393            'form-MAX_NUM_FORMS': u'', 
     394            'form-0-child' :   self.controller2.id, 
     395            'form-0-id'  : self.controller.get_children()[0].link.id, 
     396            'form-0-order'  :  45, 
     397            'form-0-parent' :  self.controller.id, 
     398            'form-0-quantity' :  '45.0', 
     399            'form-0-unit' :  'cm', 
     400            'form-0-%s' % fname : 'new_value' * 10, # too long value 
     401        } 
     402        formset = forms.get_children_formset(self.controller, data) 
     403        response = self.client.post(self.controller.plmobject_url + "BOM-child/edit/", 
     404                data, follow=True) 
     405        link = self.controller.get_children()[0].link 
     406        self.assertEqual(10, link.quantity) 
     407        self.assertEqual(15, link.order) 
     408        self.assertEqual('-', link.unit) 
     409        self.assertEqual("val1", link.extensions[0].custom_attribute) 
     410        self.assertFalse(response.context["children_formset"].is_valid()) 
     411 
  • branches/3D/openPLM/plmapp/tests/views.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
     
    2626This module contains some tests for openPLM. 
    2727""" 
     28import datetime 
    2829from django.contrib.auth.models import User 
     30from django.core import mail 
    2931from django.test import TestCase 
     32from django.core.files.base import File 
    3033 
    3134from openPLM.plmapp import forms 
     
    3336import openPLM.plmapp.models as m 
    3437from openPLM.plmapp.controllers import DocumentController, PartController, \ 
    35         UserController 
     38        UserController, GroupController 
    3639from openPLM.plmapp.lifecycle import LifecycleList 
    3740 
     
    5255                                              self.controller.reference, 
    5356                                              self.controller.revision) 
    54         brian = User.objects.create(username="Brian", password="life") 
     57        brian = User.objects.create_user(username="Brian", password="life", 
     58                email="brian@example.net") 
    5559        brian.get_profile().is_contributor = True 
    5660        brian.get_profile().save() 
     
    159163        self.assertEqual(attributes["Creator"], self.user) 
    160164 
    161     def test_edit_attributes(self): 
     165    def test_edit_attributes_get(self): 
     166        response = self.get(self.base_url + "modify/") 
     167        form = response.context["modification_form"] 
     168        self.assertTrue("name" in form.fields) 
     169 
     170    def test_edit_attributes_post(self): 
    162171        data = self.DATA.copy() 
    163172        data.update(type=self.TYPE, name="new_name") 
     
    165174        obj = m.get_all_plmobjects()[self.TYPE].objects.all()[0] 
    166175        self.assertEqual(obj.name, data["name"]) 
     176 
     177    def test_edit_attributes_post_error(self): 
     178        # the name is too looonnnnnnngggggg 
     179        name = self.controller.name 
     180        data = self.DATA.copy() 
     181        data.update(type=self.TYPE, name="new_name" * 300) 
     182        response = self.post(self.base_url + "modify/", data) 
     183        obj = m.get_all_plmobjects()[self.TYPE].objects.all()[0] 
     184        self.assertEqual(obj.name, name) 
     185        form = response.context["modification_form"] 
     186        self.assertFalse(form.is_valid()) 
    167187 
    168188    def test_lifecycle(self): 
     
    215235        self.assertFalse(response.context["password_form"].is_valid()) 
    216236 
    217     def test_revisions(self): 
     237    def test_revisions_get(self): 
    218238        response = self.get(self.base_url + "revisions/") 
    219239        revisions = response.context["revisions"] 
    220240        self.assertEqual(revisions, [self.controller.object]) 
    221         # check add_revision_form 
    222         add_revision_form = response.context["add_revision_form"] 
    223         self.assertEqual(add_revision_form.data, {"revision": "b"}) 
    224         response = self.post(self.base_url + "revisions/", {"revision" :"b"}) 
    225         m.get_all_plmobjects()[self.TYPE].objects.get(reference=self.controller.reference, 
    226                 revision="b") 
     241        self.assertTrue(response.context["add_revision_form"] is not None) 
     242        # add a new revision 
     243        rev = self.controller.revise("jf") 
     244        response = self.get(self.base_url + "revisions/") 
     245        revisions = response.context["revisions"] 
     246        self.assertEqual(revisions, [self.controller.object, rev.plmobject_ptr]) 
     247        self.assertTrue(response.context["add_revision_form"] is None) 
    227248     
    228249    def test_history(self): 
     
    332353        self.assertEqual([part.id], 
    333354            [f.instance.part.id for f in response.context["parts_formset"].forms]) 
     355 
     356    def test_related_parts_update_post(self): 
     357        part1 = PartController.create("part1", "Part", "a", self.user, self.DATA) 
     358        part2 = PartController.create("part2", "Part", "a", self.user, self.DATA) 
     359        self.controller.attach_to_part(part1) 
     360        self.controller.attach_to_part(part2) 
     361        data = { 
     362                'form-TOTAL_FORMS' : '2', 
     363                'form-INITIAL_FORMS' : '2', 
     364                'form-MAX_NUM_FORMS' : '', 
     365                 
     366                'form-0-id' : part1.get_attached_documents()[0].id, 
     367                'form-0-part' : part1.id, 
     368                'form-0-document' : self.controller.id, 
     369                'form-0-delete' : 'on', 
     370 
     371                'form-1-id' : part2.get_attached_documents()[0].id, 
     372                'form-1-part' : part2.id, 
     373                'form-1-document' : self.controller.id, 
     374                'form-1-delete' : '', 
     375            } 
     376        response = self.post(self.base_url + "parts/", data, page="parts") 
     377        self.assertEqual(1, response.context["all_parts"].count()) 
     378        self.assertEqual(list(part2.get_attached_documents()),  
     379                         list(response.context["all_parts"])) 
     380        forms_ = response.context["forms"] 
     381        self.assertEqual([part2.id], 
     382                [f.instance.part.id for f in forms_.values()])  
     383 
     384    def test_parts_update_post_empty_selection(self): 
     385        part1 = PartController.create("part1", "Part", "a", self.user, self.DATA) 
     386        part2 = PartController.create("part2", "Part", "a", self.user, self.DATA) 
     387        self.controller.attach_to_part(part1) 
     388        self.controller.attach_to_part(part2) 
     389        data = { 
     390                'form-TOTAL_FORMS' : '2', 
     391                'form-INITIAL_FORMS' : '2', 
     392                'form-MAX_NUM_FORMS' : '', 
     393                 
     394                'form-0-id' : part1.get_attached_documents()[0].id, 
     395                'form-0-part' : part1.id, 
     396                'form-0-document' : self.controller.id, 
     397                'form-0-delete' : '', 
     398 
     399                'form-1-id' : part2.get_attached_documents()[0].id, 
     400                'form-1-part' : part2.id, 
     401                'form-1-document' : self.controller.id, 
     402                'form-1-delete' : '', 
     403            } 
     404        response = self.post(self.base_url + "parts/", data, page="parts") 
     405        self.assertEqual(2, response.context["all_parts"].count()) 
     406        forms_ = response.context["forms"] 
     407        self.assertEqual(set((part1.id, part2.id)),  
     408                set(f.instance.part.id for f in forms_.values()))  
     409 
     410    def test_doc_cad_update_post_all_selected(self): 
     411        part1 = PartController.create("part1", "Part", "a", self.user, self.DATA) 
     412        part2 = PartController.create("part2", "Part", "a", self.user, self.DATA) 
     413        self.controller.attach_to_part(part1) 
     414        self.controller.attach_to_part(part2) 
     415        data = { 
     416                'form-TOTAL_FORMS' : '2', 
     417                'form-INITIAL_FORMS' : '2', 
     418                'form-MAX_NUM_FORMS' : '', 
     419                 
     420                'form-0-id' : part1.get_attached_documents()[0].id, 
     421                'form-0-part' : part1.id, 
     422                'form-0-document' : self.controller.id, 
     423                'form-0-delete' : 'on', 
     424 
     425                'form-1-id' : part2.get_attached_documents()[0].id, 
     426                'form-1-part' : part2.id, 
     427                'form-1-document' : self.controller.id, 
     428                'form-1-delete' : 'on', 
     429            } 
     430        response = self.post(self.base_url + "parts/", data, page="parts") 
     431        self.assertEqual(0, response.context["all_parts"].count()) 
     432        self.assertFalse(response.context["forms"]) 
    334433         
    335434    def test_add_related_part_get(self): 
     
    381480        self.assertEqual([df2.id], [df.id for df in self.controller.files]) 
    382481 
     482    def test_checkin_get(self): 
     483        df1 = self.controller.add_file(self.get_file()) 
     484        self.controller.lock(df1) 
     485        response = self.get(self.base_url + "files/checkin/%d/" % df1.id) 
     486        form = response.context["add_file_form"] 
     487 
     488    def test_checkin_post(self): 
     489        df1 = self.controller.add_file(self.get_file()) 
     490        self.controller.lock(df1) 
     491        mock_file = self.get_file(data="robert") 
     492        # plop is here so that django see a post request 
     493        # it's not a problem since a real browser will also send a 
     494        # csrf token 
     495        response = self.post(self.base_url + "files/checkin/%d/" % df1.id, 
     496                dict(filename=mock_file, plop="m")) 
     497        files = self.controller.files 
     498        self.assertEqual(1, files.count()) 
     499        df = files[0] 
     500        self.assertFalse(df.locked) 
     501        self.assertEqual("robert", df.file.read()) 
     502 
     503    def test_checkout(self): 
     504        df1 = self.controller.add_file(self.get_file(data="oh oh oh")) 
     505        response = self.client.get(self.base_url + "files/checkout/%d/" % df1.id) 
     506        files = self.controller.files 
     507        self.assertEqual(1, files.count()) 
     508        df = files[0] 
     509        self.assertTrue(df.locked) 
     510        self.assertEqual("oh oh oh", response.content) 
     511 
    383512    def test_add_file_get(self): 
    384513        response = self.get(self.base_url + "files/add/") 
     
    397526        self.controller.add_file(self.get_file()) 
    398527        super(DocumentViewTestCase, self).test_lifecycle() 
     528 
     529    def test_revise_no_attached_part_get(self): 
     530        """ 
     531        Tests the "revisions/" page and checks that if the document has no 
     532        attached parts, it is not necessary to confirm the form. 
     533        """ 
     534        response = self.get(self.base_url + "revisions/") 
     535        # checks that is not necessary to confirm the revision  
     536        self.assertFalse(response.context["confirmation"]) 
     537 
     538    def test_revise_no_attached_part_post(self): 
     539        """ 
     540        Tests a post request to revise a document which has no attached parts. 
     541        """ 
     542        response = self.post(self.base_url + "revisions/", 
     543                {"revision" : "b"}) 
     544        revisions = self.controller.get_next_revisions() 
     545        self.assertEqual(1, len(revisions)) 
     546        rev = revisions[0] 
     547        self.assertEqual("b", rev.revision) 
     548 
     549    def test_revise_one_attached_part_get(self): 
     550        """ 
     551        Tests a get request to revise a document which has one attached part. 
     552        This part must be suggested when the user revises the document. 
     553        """ 
     554        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     555        self.controller.attach_to_part(part) 
     556        response = self.get(self.base_url + "revisions/") 
     557        # checks that it is necessary to confirm the revision  
     558        self.assertTrue(response.context["confirmation"]) 
     559        formset = response.context["part_formset"] 
     560        self.assertEqual(1, formset.total_form_count()) 
     561        form = formset.forms[0] 
     562        self.assertTrue(form.fields["selected"].initial) 
     563        self.assertEqual(part.id, form.instance.id) 
     564 
     565    def test_revise_one_attached_part_post_selected(self): 
     566        """ 
     567        Tests a post request to revise a document with one attached part which 
     568        is selected. 
     569        """ 
     570        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     571        self.controller.attach_to_part(part) 
     572        data = { "revision" : "b", 
     573                 "form-TOTAL_FORMS" : "1", 
     574                 "form-INITIAL_FORMS" : "1", 
     575                 "form-0-selected" : "on", 
     576                 "form-0-plmobject_ptr" : part.id, 
     577                 } 
     578        response = self.post(self.base_url + "revisions/", data) 
     579        revisions = self.controller.get_next_revisions() 
     580        self.assertEqual(1, len(revisions)) 
     581        rev = revisions[0].document 
     582        self.assertEqual("b", rev.revision) 
     583        # ensure part is still attached to the old revision 
     584        parts = self.controller.get_attached_parts().values_list("part", flat=True) 
     585        self.assertEqual([part.id], list(parts)) 
     586        # ensure part is attached to the new revision 
     587        parts = rev.documentpartlink_document.values_list("part", flat=True) 
     588        self.assertEqual([part.id], list(parts)) 
     589        # ensure both documents are attached to the part 
     590        self.assertEqual([self.controller.id, rev.id], 
     591            sorted(part.get_attached_documents().values_list("document", flat=True))) 
     592 
     593    def test_revise_one_attached_part_post_unselected(self): 
     594        """ 
     595        Tests a post request to revise a document with one attached part which 
     596        is not selected. 
     597        """ 
     598        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     599        self.controller.attach_to_part(part) 
     600        data = { "revision" : "b", 
     601                 "form-TOTAL_FORMS" : "1", 
     602                 "form-INITIAL_FORMS" : "1", 
     603                 "form-0-selected" : "", 
     604                 "form-0-plmobject_ptr" : part.id, 
     605                 } 
     606        response = self.post(self.base_url + "revisions/", data) 
     607        revisions = self.controller.get_next_revisions() 
     608        self.assertEqual(1, len(revisions)) 
     609        rev = revisions[0].document 
     610        self.assertEqual("b", rev.revision) 
     611        # ensure part is still attached to the old revision 
     612        parts = self.controller.get_attached_parts().values_list("part", flat=True) 
     613        self.assertEqual([part.id], list(parts)) 
     614        # ensure part is not attached to the new revision 
     615        self.assertFalse(bool(rev.documentpartlink_document.all())) 
     616        # ensure only the old revision is attached to part  
     617        self.assertEqual([self.controller.id], 
     618            list(part.get_attached_documents().values_list("document", flat=True))) 
     619 
     620    def test_revise_two_attached_parts_get(self): 
     621        """ 
     622        Tests a get request to revise a document with two attached parts. 
     623        One part is a draft, the other is official. 
     624        """ 
     625        p1 = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     626        self.controller.attach_to_part(p1) 
     627        p2 = PartController.create("part_2", "Part", "a", self.user, self.DATA) 
     628        self.controller.attach_to_part(p2) 
     629        p2.object.is_promotable = lambda: True 
     630        p2.promote() 
     631        response = self.get(self.base_url + "revisions/") 
     632        # checks that it is necessary to confirm the revision  
     633        self.assertTrue(response.context["confirmation"]) 
     634        formset = response.context["part_formset"] 
     635        self.assertEqual(2, formset.total_form_count()) 
     636        form1, form2 = formset.forms 
     637        self.assertTrue(form1.fields["selected"].initial) 
     638        self.assertTrue(form2.fields["selected"].initial) 
     639        self.assertEqual([p1.id, p2.id], sorted([form1.instance.id, form2.instance.id])) 
     640 
     641    def test_revise_two_attached_parts_post(self): 
     642        """ 
     643        Tests a post request to revise a document with two attached parts. 
     644        One part is a draft and not selected, the other is official and selected. 
     645        """ 
     646        p1 = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     647        self.controller.attach_to_part(p1) 
     648        p2 = PartController.create("part_2", "Part", "a", self.user, self.DATA) 
     649        self.controller.attach_to_part(p2) 
     650        p2.object.is_promotable = lambda: True 
     651        p2.promote() 
     652        data = { 
     653            "revision" : "b", 
     654             "form-TOTAL_FORMS" : "2", 
     655             "form-INITIAL_FORMS" : "2", 
     656             "form-0-selected" : "", 
     657             "form-0-plmobject_ptr" : p1.id, 
     658             "form-1-selected" : "on", 
     659             "form-1-plmobject_ptr" : p2.id, 
     660             } 
     661        response = self.post(self.base_url + "revisions/", data) 
     662        revisions = self.controller.get_next_revisions() 
     663        self.assertEqual(1, len(revisions)) 
     664        rev = revisions[0].document 
     665        self.assertEqual("b", rev.revision) 
     666        # ensure p1 and p2 are still attached to the old revision 
     667        parts = self.controller.get_attached_parts().values_list("part", flat=True) 
     668        self.assertEqual([p1.id, p2.id], sorted(parts)) 
     669        # ensure p2 is attached to the new revision 
     670        parts = rev.documentpartlink_document.values_list("part", flat=True) 
     671        self.assertEqual([p2.id], list(parts)) 
     672        # ensure both documents are attached to p2 
     673        self.assertEqual([self.controller.id, rev.id], 
     674            sorted(p2.get_attached_documents().values_list("document", flat=True))) 
     675 
     676    def test_revise_one_deprecated_part_attached_get(self): 
     677        """ 
     678        Tests a get request to revise a document which has one deprecated  
     679        attached part. 
     680        This part must not be suggested when the user revises the document. 
     681        """ 
     682        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     683        self.controller.attach_to_part(part) 
     684        part.object.state = part.lifecycle.last_state 
     685        part.object.save() 
     686        response = self.get(self.base_url + "revisions/") 
     687        # checks that it is necessary to confirm the revision  
     688        self.assertFalse(response.context["confirmation"]) 
     689 
     690    def test_revise_one_deprecated_part_attached_post(self): 
     691        """ 
     692        Tests a post request to revise a document which has one deprecated  
     693        attached part. 
     694        This part must not be suggested when the user revises the document. 
     695        """ 
     696        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     697        self.controller.attach_to_part(part) 
     698        part.object.state = part.lifecycle.last_state 
     699        part.object.save() 
     700        data = { "revision" : "b", 
     701                 "form-TOTAL_FORMS" : "1", 
     702                 "form-INITIAL_FORMS" : "1", 
     703                 "form-0-selected" : "", 
     704                 "form-0-plmobject_ptr" : part.id, 
     705                 } 
     706        response = self.post(self.base_url + "revisions/", data) 
     707        # even if we submit a formset, it should not be parsed 
     708        revisions = self.controller.get_next_revisions() 
     709        self.assertEqual(1, len(revisions)) 
     710        rev = revisions[0].document 
     711        # ensure part is still attached to the old revision 
     712        parts = self.controller.get_attached_parts().values_list("part", flat=True) 
     713        self.assertEqual([part.id], list(parts)) 
     714        # ensure part is not attached to the new revision 
     715        self.assertFalse(bool(rev.documentpartlink_document.all())) 
     716        # ensure only the old revision is attached to part  
     717        self.assertEqual([self.controller.id], 
     718            list(part.get_attached_documents().values_list("document", flat=True))) 
     719 
     720    def test_revise_one_attached_revised_part_get(self): 
     721        """ 
     722        Tests a get request to revise a document which has one attached part. 
     723        The part has been revised and so its revision must be suggested and 
     724        the part must not be suggested when the user revises the document. 
     725        """ 
     726        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     727        self.controller.attach_to_part(part) 
     728        p2 = part.revise("b") 
     729        response = self.get(self.base_url + "revisions/") 
     730        # checks that it is necessary to confirm the revision  
     731        self.assertTrue(response.context["confirmation"]) 
     732        formset = response.context["part_formset"] 
     733        self.assertEqual(1, formset.total_form_count()) 
     734        form = formset.forms[0] 
     735        self.assertTrue(form.fields["selected"].initial) 
     736        self.assertEqual(p2.id, form.instance.id) 
     737 
     738    def test_revise_one_attached_revised_part_post(self): 
     739        """ 
     740        Tests a post request to revise a document which has one attached part. 
     741        The part has been revised and its revision is selected. 
     742        """ 
     743        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     744        self.controller.attach_to_part(part) 
     745        p2 = part.revise("b") 
     746        data = { "revision" : "b", 
     747                 "form-TOTAL_FORMS" : "1", 
     748                 "form-INITIAL_FORMS" : "1", 
     749                 "form-0-selected" : "on", 
     750                 "form-0-plmobject_ptr" : p2.id, 
     751                 } 
     752        response = self.post(self.base_url + "revisions/", data) 
     753        revisions = self.controller.get_next_revisions() 
     754        self.assertEqual(1, len(revisions)) 
     755        rev = revisions[0].document 
     756        self.assertEqual("b", rev.revision) 
     757        # ensure part is still attached to the old revision 
     758        parts = self.controller.get_attached_parts().values_list("part", flat=True) 
     759        self.assertEqual([part.id], list(parts)) 
     760        # ensure p2 is attached to the new revision 
     761        parts = rev.documentpartlink_document.values_list("part", flat=True) 
     762        self.assertEqual([p2.id], list(parts)) 
     763 
     764    def test_revise_one_attached_revised_part_post_error(self): 
     765        """ 
     766        Tests a post request to revise a document which has one attached part. 
     767        The part has been revised and has been selected instead of its revision.  
     768        """ 
     769        part = PartController.create("RefPart", "Part", "a", self.user, self.DATA) 
     770        self.controller.attach_to_part(part) 
     771        p2 = part.revise("b") 
     772        data = { "revision" : "b", 
     773                 "form-TOTAL_FORMS" : "1", 
     774                 "form-INITIAL_FORMS" : "1", 
     775                 "form-0-selected" : "on", 
     776                 "form-0-plmobject_ptr" : part.id, 
     777                 } 
     778        response = self.post(self.base_url + "revisions/", data) 
     779        revisions = self.controller.get_next_revisions() 
     780        # no revisions have been created 
     781        self.assertEqual([], revisions) 
     782        # ensure part is still attached to the old revision 
     783        parts = self.controller.get_attached_parts().values_list("part", flat=True) 
     784        self.assertEqual([part.id], list(parts)) 
     785 
    399786 
    400787class PartViewTestCase(ViewTest): 
     
    466853        response = self.get(self.base_url + "doc-cad/", page="doc-cad") 
    467854        self.assertEqual(2, response.context["all_docs"].count()) 
    468         forms = response.context["forms"] 
    469         self.assertEqual([doc1.id], [f.instance.document.id for f in forms.values()])  
     855        forms_ = response.context["forms"] 
     856        self.assertEqual([doc1.id], [f.instance.document.id for f in forms_.values()])  
    470857 
    471858    def test_doc_add_add_get(self): 
     
    481868        document = self.controller.get_attached_documents()[0].document 
    482869        self.assertEqual(doc1.object, document) 
     870 
     871    def test_doc_cad_update_post(self): 
     872        doc1 = DocumentController.create("doc1", "Document", "a", self.user, 
     873                self.DATA) 
     874        doc2 = DocumentController.create("doc2", "Document", "a", self.user, 
     875                self.DATA) 
     876        self.controller.attach_to_document(doc1) 
     877        self.controller.attach_to_document(doc2) 
     878        data = { 
     879                'form-TOTAL_FORMS' : '2', 
     880                'form-INITIAL_FORMS' : '2', 
     881                'form-MAX_NUM_FORMS' : '', 
     882                 
     883                'form-0-id' : doc1.get_attached_parts()[0].id, 
     884                'form-0-part' : self.controller.id, 
     885                'form-0-document' : doc1.id, 
     886                'form-0-delete' : 'on', 
     887 
     888                'form-1-id' : doc2.get_attached_parts()[0].id, 
     889                'form-1-part' : self.controller.id, 
     890                'form-1-document' : doc2.id, 
     891                'form-1-delete' : '', 
     892            } 
     893        response = self.post(self.base_url + "doc-cad/", data, page="doc-cad") 
     894        self.assertEqual(1, response.context["all_docs"].count()) 
     895        self.assertEqual(list(doc2.get_attached_parts()),  
     896                         list(response.context["all_docs"])) 
     897        forms_ = response.context["forms"] 
     898        self.assertEqual([doc2.id], [f.instance.document.id for f in forms_.values()])  
    483899         
    484  
     900    def test_doc_cad_update_post_empty_selection(self): 
     901        doc1 = DocumentController.create("doc1", "Document", "a", self.user, 
     902                self.DATA) 
     903        doc2 = DocumentController.create("doc2", "Document", "a", self.user, 
     904                self.DATA) 
     905        self.controller.attach_to_document(doc1) 
     906        self.controller.attach_to_document(doc2) 
     907        data = { 
     908                'form-TOTAL_FORMS' : '2', 
     909                'form-INITIAL_FORMS' : '2', 
     910                'form-MAX_NUM_FORMS' : '', 
     911                 
     912                'form-0-id' : doc1.get_attached_parts()[0].id, 
     913                'form-0-part' : self.controller.id, 
     914                'form-0-document' : doc1.id, 
     915                'form-0-delete' : '', 
     916 
     917                'form-1-id' : doc2.get_attached_parts()[0].id, 
     918                'form-1-part' : self.controller.id, 
     919                'form-1-document' : doc2.id, 
     920                'form-1-delete' : '', 
     921            } 
     922        response = self.post(self.base_url + "doc-cad/", data, page="doc-cad") 
     923        self.assertEqual(2, response.context["all_docs"].count()) 
     924        forms_ = response.context["forms"] 
     925        self.assertEqual(set((doc1.id, doc2.id)),  
     926                set(f.instance.document.id for f in forms_.values()))  
     927 
     928    def test_doc_cad_update_post_all_selected(self): 
     929        doc1 = DocumentController.create("doc1", "Document", "a", self.user, 
     930                self.DATA) 
     931        doc2 = DocumentController.create("doc2", "Document", "a", self.user, 
     932                self.DATA) 
     933        self.controller.attach_to_document(doc1) 
     934        self.controller.attach_to_document(doc2) 
     935        data = { 
     936                'form-TOTAL_FORMS' : '2', 
     937                'form-INITIAL_FORMS' : '2', 
     938                'form-MAX_NUM_FORMS' : '', 
     939                 
     940                'form-0-id' : doc1.get_attached_parts()[0].id, 
     941                'form-0-part' : self.controller.id, 
     942                'form-0-document' : doc1.id, 
     943                'form-0-delete' : 'on', 
     944 
     945                'form-1-id' : doc2.get_attached_parts()[0].id, 
     946                'form-1-part' : self.controller.id, 
     947                'form-1-document' : doc2.id, 
     948                'form-1-delete' : 'on', 
     949            } 
     950        response = self.post(self.base_url + "doc-cad/", data, page="doc-cad") 
     951        self.assertEqual(0, response.context["all_docs"].count()) 
     952        self.assertFalse(response.context["forms"]) 
     953 
     954    def test_revise_no_attached_document_get(self): 
     955        """ 
     956        Tests the "revisions/" page and checks that if the part has no 
     957        attached documents, it is not necessary to confirm the form. 
     958        """ 
     959        response = self.get(self.base_url + "revisions/") 
     960        # checks that is not necessary to confirm the revision  
     961        self.assertFalse(response.context["confirmation"]) 
     962 
     963    def test_revise_no_attached_document_post(self): 
     964        """ 
     965        Tests a post request to revise a part which has no attached documents. 
     966        """ 
     967        response = self.post(self.base_url + "revisions/", 
     968                {"revision" : "b"}) 
     969        revisions = self.controller.get_next_revisions() 
     970        self.assertEqual(1, len(revisions)) 
     971        rev = revisions[0] 
     972        self.assertEqual("b", rev.revision) 
     973 
     974    def test_revise_one_attached_document_get(self): 
     975        """ 
     976        Tests a get request to revise a part which has one attached document. 
     977        This document must be suggested when the user revises the part. 
     978        """ 
     979        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     980        self.controller.attach_to_document(document) 
     981        response = self.get(self.base_url + "revisions/") 
     982        # checks that it is necessary to confirm the revision  
     983        self.assertTrue(response.context["confirmation"]) 
     984        formset = response.context["doc_formset"] 
     985        self.assertEqual(1, formset.total_form_count()) 
     986        form = formset.forms[0] 
     987        self.assertTrue(form.fields["selected"].initial) 
     988        self.assertEqual(document.id, form.initial["document"].id) 
     989 
     990    def test_revise_one_attached_document_post_selected(self): 
     991        """ 
     992        Tests a post request to revise a part with one attached document which 
     993        is selected. 
     994        """ 
     995        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     996        self.controller.attach_to_document(document) 
     997        data = { "revision" : "b", 
     998                 "parents-TOTAL_FORMS" : "0", 
     999                 "parents-INITIAL_FORMS" : "0", 
     1000                 "children-TOTAL_FORMS" : "0", 
     1001                 "children-INITIAL_FORMS" : "0", 
     1002                 "documents-TOTAL_FORMS" : "1", 
     1003                 "documents-INITIAL_FORMS" : "1", 
     1004                 "documents-0-selected" : "on", 
     1005                 "documents-0-document" : document.id, 
     1006                 } 
     1007        response = self.post(self.base_url + "revisions/", data) 
     1008        revisions = self.controller.get_next_revisions() 
     1009        self.assertEqual(1, len(revisions)) 
     1010        rev = revisions[0].part 
     1011        self.assertEqual("b", rev.revision) 
     1012        # ensure document is still attached to the old revision 
     1013        documents = self.controller.get_attached_documents().values_list("document", flat=True) 
     1014        self.assertEqual([document.id], list(documents)) 
     1015        # ensure document is attached to the new revision 
     1016        documents = rev.documentpartlink_part.values_list("document", flat=True) 
     1017        self.assertEqual([document.id], list(documents)) 
     1018        # ensure both documents are attached to the document 
     1019        self.assertEqual([self.controller.id, rev.id], 
     1020            sorted(document.get_attached_parts().values_list("part", flat=True))) 
     1021 
     1022    def test_revise_one_attached_document_post_unselected(self): 
     1023        """ 
     1024        Tests a post request to revise a part with one attached document which 
     1025        is not selected. 
     1026        """ 
     1027        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1028        self.controller.attach_to_document(document) 
     1029        data = { "revision" : "b", 
     1030                 "parents-TOTAL_FORMS" : "0", 
     1031                 "parents-INITIAL_FORMS" : "0", 
     1032                 "children-TOTAL_FORMS" : "0", 
     1033                 "children-INITIAL_FORMS" : "0", 
     1034                 "documents-TOTAL_FORMS" : "1", 
     1035                 "documents-INITIAL_FORMS" : "1", 
     1036                 "documents-0-selected" : "", 
     1037                 "documents-0-document" : document.id, 
     1038                 } 
     1039        response = self.post(self.base_url + "revisions/", data) 
     1040        revisions = self.controller.get_next_revisions() 
     1041        self.assertEqual(1, len(revisions)) 
     1042        rev = revisions[0].part 
     1043        self.assertEqual("b", rev.revision) 
     1044        # ensure document is still attached to the old revision 
     1045        documents = self.controller.get_attached_documents().values_list("document", flat=True) 
     1046        self.assertEqual([document.id], list(documents)) 
     1047        # ensure document is not attached to the new revision 
     1048        self.assertFalse(bool(rev.documentpartlink_part.all())) 
     1049        # ensure only the old revision is attached to document  
     1050        self.assertEqual([self.controller.id], 
     1051            list(document.get_attached_parts().values_list("part", flat=True))) 
     1052 
     1053    def test_revise_two_attached_documents_get(self): 
     1054        """ 
     1055        Tests a get request to revise a part with two attached documents. 
     1056        One document is a draft, the other is official. 
     1057        """ 
     1058        d1 = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1059        self.controller.attach_to_document(d1) 
     1060        d2 = DocumentController.create("document_2", "Document", "a", self.user, self.DATA) 
     1061        self.controller.attach_to_document(d2) 
     1062        d2.object.is_promotable = lambda: True 
     1063        d2.promote() 
     1064        response = self.get(self.base_url + "revisions/") 
     1065        # checks that it is necessary to confirm the revision  
     1066        self.assertTrue(response.context["confirmation"]) 
     1067        formset = response.context["doc_formset"] 
     1068        self.assertEqual(2, formset.total_form_count()) 
     1069        form1, form2 = formset.forms 
     1070        self.assertTrue(form1.fields["selected"].initial) 
     1071        self.assertTrue(form2.fields["selected"].initial) 
     1072        self.assertEqual([d1.id, d2.id], 
     1073                sorted([form1.initial["document"].id, form2.initial["document"].id])) 
     1074 
     1075    def test_revise_two_attached_documents_post(self): 
     1076        """ 
     1077        Tests a post request to revise a part with two attached documents. 
     1078        One document is a draft and not selected, the other is official and selected. 
     1079        """ 
     1080        d1 = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1081        self.controller.attach_to_document(d1) 
     1082        d2 = DocumentController.create("document_2", "Document", "a", self.user, self.DATA) 
     1083        self.controller.attach_to_document(d2) 
     1084        d2.object.is_promotable = lambda: True 
     1085        d2.promote() 
     1086        data = { 
     1087            "revision" : "b", 
     1088             "parents-TOTAL_FORMS" : "0", 
     1089             "parents-INITIAL_FORMS" : "0", 
     1090             "children-TOTAL_FORMS" : "0", 
     1091             "children-INITIAL_FORMS" : "0", 
     1092             "documents-TOTAL_FORMS" : "2", 
     1093             "documents-INITIAL_FORMS" : "2", 
     1094             "documents-0-selected" : "", 
     1095             "documents-0-document" : d1.id, 
     1096             "documents-1-selected" : "on", 
     1097             "documents-1-document" : d2.id, 
     1098             } 
     1099        response = self.post(self.base_url + "revisions/", data) 
     1100        revisions = self.controller.get_next_revisions() 
     1101        self.assertEqual(1, len(revisions)) 
     1102        rev = revisions[0].part 
     1103        self.assertEqual("b", rev.revision) 
     1104        # ensure d1 and d2 are still attached to the old revision 
     1105        documents = self.controller.get_attached_documents().values_list("document", flat=True) 
     1106        self.assertEqual([d1.id, d2.id], sorted(documents)) 
     1107        # ensure d2 is attached to the new revision 
     1108        documents = rev.documentpartlink_part.values_list("document", flat=True) 
     1109        self.assertEqual([d2.id], list(documents)) 
     1110        # ensure both documents are attached to d2 
     1111        self.assertEqual([self.controller.id, rev.id], 
     1112            sorted(d2.get_attached_parts().values_list("part", flat=True))) 
     1113 
     1114    def test_revise_one_deprecated_document_attached_get(self): 
     1115        """ 
     1116        Tests a get request to revise a part which has one deprecated  
     1117        attached document. 
     1118        This document must not be suggested when the user revises the part. 
     1119        """ 
     1120        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1121        self.controller.attach_to_document(document) 
     1122        document.object.state = document.lifecycle.last_state 
     1123        document.object.save() 
     1124        response = self.get(self.base_url + "revisions/") 
     1125        # checks that it is necessary to confirm the revision  
     1126        self.assertFalse(response.context["confirmation"]) 
     1127 
     1128    def test_revise_one_deprecated_document_attached_post(self): 
     1129        """ 
     1130        Tests a post request to revise a part which has one deprecated  
     1131        attached document. 
     1132        This document must not be suggested when the user revises the part. 
     1133        """ 
     1134        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1135        self.controller.attach_to_document(document) 
     1136        document.object.state = document.lifecycle.last_state 
     1137        document.object.save() 
     1138        data = { "revision" : "b", 
     1139                 "parents-TOTAL_FORMS" : "0", 
     1140                 "parents-INITIAL_FORMS" : "0", 
     1141                 "children-TOTAL_FORMS" : "0", 
     1142                 "children-INITIAL_FORMS" : "0", 
     1143                 "documents-TOTAL_FORMS" : "1", 
     1144                 "documents-INITIAL_FORMS" : "1", 
     1145                 "documents-0-selected" : "", 
     1146                 "documents-0-document" : document.id, 
     1147                 } 
     1148        response = self.post(self.base_url + "revisions/", data) 
     1149        # even if we submit a formset, it should not be parsed 
     1150        revisions = self.controller.get_next_revisions() 
     1151        self.assertEqual(1, len(revisions)) 
     1152        rev = revisions[0].part 
     1153        # ensure document is still attached to the old revision 
     1154        documents = self.controller.get_attached_documents().values_list("document", flat=True) 
     1155        self.assertEqual([document.id], list(documents)) 
     1156        # ensure document is not attached to the new revision 
     1157        self.assertFalse(bool(rev.documentpartlink_part.all())) 
     1158        # ensure only the old revision is attached to document  
     1159        self.assertEqual([self.controller.id], 
     1160            list(document.get_attached_parts().values_list("part", flat=True))) 
     1161 
     1162    def test_revise_one_attached_revised_document_get(self): 
     1163        """ 
     1164        Tests a get request to revise a part which has one attached document. 
     1165        The document has been revised and so its revision must be suggested and 
     1166        the document must not be suggested when the user revises the oart. 
     1167        """ 
     1168        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1169        self.controller.attach_to_document(document) 
     1170        d2 = document.revise("b") 
     1171        response = self.get(self.base_url + "revisions/") 
     1172        # checks that it is necessary to confirm the revision  
     1173        self.assertTrue(response.context["confirmation"]) 
     1174        formset = response.context["doc_formset"] 
     1175        self.assertEqual(2, formset.total_form_count()) 
     1176 
     1177    def test_revise_one_attached_revised_document_post(self): 
     1178        """ 
     1179        Tests a post request to revise a part which has one attached document. 
     1180        The document has been revised and its revision is selected. 
     1181        """ 
     1182        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1183        self.controller.attach_to_document(document) 
     1184        d2 = document.revise("b") 
     1185        data = { "revision" : "b", 
     1186                 "parents-TOTAL_FORMS" : "0", 
     1187                 "parents-INITIAL_FORMS" : "0", 
     1188                 "children-TOTAL_FORMS" : "0", 
     1189                 "children-INITIAL_FORMS" : "0", 
     1190                 "documents-TOTAL_FORMS" : "1", 
     1191                 "documents-INITIAL_FORMS" : "1", 
     1192                 "documents-0-selected" : "on", 
     1193                 "documents-0-document" : d2.id, 
     1194                 } 
     1195        response = self.post(self.base_url + "revisions/", data) 
     1196        revisions = self.controller.get_next_revisions() 
     1197        self.assertEqual(1, len(revisions)) 
     1198        rev = revisions[0].part 
     1199        self.assertEqual("b", rev.revision) 
     1200        # ensure document is still attached to the old revision 
     1201        documents = self.controller.get_attached_documents().values_list("document", flat=True) 
     1202        self.assertEqual([document.id], list(documents)) 
     1203        # ensure d2 is attached to the new revision 
     1204        documents = rev.documentpartlink_part.values_list("document", flat=True) 
     1205        self.assertEqual([d2.id], list(documents)) 
     1206 
     1207    def test_revise_one_attached_revised_document_post_error(self): 
     1208        """ 
     1209        Tests a post request to revise a part which has one attached document. 
     1210        The document has been revised and has been selected instead of its revision.  
     1211        """ 
     1212        document = DocumentController.create("RefDocument", "Document", "a", self.user, self.DATA) 
     1213        self.controller.attach_to_document(document) 
     1214        d2 = document.revise("b") 
     1215        self.controller.attach_to_document(d2) 
     1216        data = { "revision" : "b", 
     1217                 "parents-TOTAL_FORMS" : "0", 
     1218                 "parents-INITIAL_FORMS" : "0", 
     1219                 "children-TOTAL_FORMS" : "0", 
     1220                 "children-INITIAL_FORMS" : "0", 
     1221                 "documents-TOTAL_FORMS" : "1", 
     1222                 "documents-INITIAL_FORMS" : "1", 
     1223                 "documents-0-selected" : "on", 
     1224                 "documents-0-document" : document.id, 
     1225                 } 
     1226        response = self.post(self.base_url + "revisions/", data) 
     1227        revisions = self.controller.get_next_revisions() 
     1228        # no revisions have been created 
     1229        self.assertEqual([], revisions) 
     1230        # ensure documents are still attached to the old revision 
     1231        documents = self.controller.get_attached_documents().values_list("document", flat=True) 
     1232        self.assertEqual(set((document.id, d2.id)), set(documents)) 
     1233 
     1234    def test_revise_one_child_get(self): 
     1235        """ 
     1236        Tests a get request to revise a part which has one child. 
     1237        This child must be suggested when the user revises the part. 
     1238        """ 
     1239        child = PartController.create("RefChild", "Part", "a", self.user, self.DATA) 
     1240        self.controller.add_child(child, 10, 25, "-") 
     1241        response = self.get(self.base_url + "revisions/") 
     1242        # checks that it is necessary to confirm the revision  
     1243        self.assertTrue(response.context["confirmation"]) 
     1244        formset = response.context["children_formset"] 
     1245        self.assertEqual(1, formset.total_form_count()) 
     1246        form = formset.forms[0] 
     1247        self.assertTrue(form.fields["selected"].initial) 
     1248        self.assertEqual(child.id, form.initial["link"].child_id) 
     1249 
     1250    def test_revise_one_child_post_selected(self): 
     1251        """ 
     1252        Tests a post request to revise a part with one child which is selected. 
     1253        """ 
     1254        child = PartController.create("RefChild", "Part", "a", self.user, self.DATA) 
     1255        self.controller.add_child(child, 10, 25, "-") 
     1256        link = self.controller.get_children(1)[0].link 
     1257        data = { "revision" : "b", 
     1258                 "parents-TOTAL_FORMS" : "0", 
     1259                 "parents-INITIAL_FORMS" : "0", 
     1260                 "children-TOTAL_FORMS" : "1", 
     1261                 "children-INITIAL_FORMS" : "1", 
     1262                 "children-0-selected" : "on", 
     1263                 "children-0-link" : link.id, 
     1264                 "documents-TOTAL_FORMS" : "0", 
     1265                 "documents-INITIAL_FORMS" : "0", 
     1266                 } 
     1267        response = self.post(self.base_url + "revisions/", data) 
     1268        revisions = self.controller.get_next_revisions() 
     1269        self.assertEqual(1, len(revisions)) 
     1270        rev = revisions[0].part 
     1271        self.assertEqual("b", rev.revision) 
     1272        # ensure the part is still a child of the old revision 
     1273        children = self.controller.get_children(1) 
     1274        self.assertEqual([(1, link)], children) 
     1275        # ensure the part is a child of the new revision 
     1276        children = PartController(rev, self.user).get_children(1) 
     1277        self.assertEqual(1, len(children)) 
     1278        link2 = children[0].link 
     1279        self.assertEqual(link2.child, link.child) 
     1280        self.assertEqual(link2.order, link.order) 
     1281        self.assertEqual(link2.quantity, link.quantity) 
     1282        self.assertEqual(link2.unit, link.unit) 
     1283 
     1284    def test_revise_one_child_post_unselected(self): 
     1285        """ 
     1286        Tests a post request to revise a part with child which is not selected. 
     1287        """ 
     1288        child = PartController.create("RefChild", "Part", "a", self.user, self.DATA) 
     1289        self.controller.add_child(child, 10, 25, "-") 
     1290        link = self.controller.get_children(1)[0].link 
     1291        data = { "revision" : "b", 
     1292                 "parents-TOTAL_FORMS" : "0", 
     1293                 "parents-INITIAL_FORMS" : "0", 
     1294                 "children-TOTAL_FORMS" : "1", 
     1295                 "children-INITIAL_FORMS" : "1", 
     1296                 "children-0-selected" : "", 
     1297                 "children-0-link" : link.id, 
     1298                 "documents-TOTAL_FORMS" : "0", 
     1299                 "documents-INITIAL_FORMS" : "0", 
     1300                 } 
     1301        response = self.post(self.base_url + "revisions/", data) 
     1302        revisions = self.controller.get_next_revisions() 
     1303        self.assertEqual(1, len(revisions)) 
     1304        rev = revisions[0].part 
     1305        self.assertEqual("b", rev.revision) 
     1306        # ensure the part is still a child of the old revision 
     1307        children = self.controller.get_children(1) 
     1308        self.assertEqual([(1, link)], children) 
     1309        # ensure the part is not a child of the new revision 
     1310        children = PartController(rev, self.user).get_children(1) 
     1311        self.assertEqual(0, len(children)) 
     1312 
     1313    def test_revise_two_childrens_get(self): 
     1314        """ 
     1315        Tests a get request to revise a part with two children. 
     1316        """ 
     1317        child1 = PartController.create("RefChild1", "Part", "a", self.user, self.DATA) 
     1318        self.controller.add_child(child1, 10, 25, "-") 
     1319        child2 = PartController.create("RefChild2", "Part", "a", self.user, self.DATA) 
     1320        self.controller.add_child(child2, 10, 55, "-") 
     1321 
     1322        response = self.get(self.base_url + "revisions/") 
     1323        # checks that it is necessary to confirm the revision  
     1324        self.assertTrue(response.context["confirmation"]) 
     1325        formset = response.context["children_formset"] 
     1326 
     1327        self.assertEqual(2, formset.total_form_count()) 
     1328        form1, form2 = formset.forms 
     1329        self.assertTrue(form1.fields["selected"].initial) 
     1330        self.assertTrue(form1.initial["link"].child_id in (child1.id, child2.id)) 
     1331        self.assertTrue(form2.fields["selected"].initial) 
     1332        self.assertTrue(form2.initial["link"].child_id in (child1.id, child2.id)) 
     1333        self.assertNotEqual(form2.initial["link"], form1.initial["link"]) 
     1334 
     1335    def test_revise_two_childrens_post(self): 
     1336        """ 
     1337        Tests a post request to revise a part with two children. 
     1338        Only one child is selected. 
     1339        """ 
     1340        child1 = PartController.create("RefChild1", "Part", "a", self.user, self.DATA) 
     1341        self.controller.add_child(child1, 10, 25, "-") 
     1342        child2 = PartController.create("RefChild2", "Part", "a", self.user, self.DATA) 
     1343        self.controller.add_child(child2, 10, 55, "-") 
     1344 
     1345        link1, link2 = [c.link for c in self.controller.get_children(1)] 
     1346        data = { "revision" : "b", 
     1347                 "parents-TOTAL_FORMS" : "0", 
     1348                 "parents-INITIAL_FORMS" : "0", 
     1349                 "children-TOTAL_FORMS" : "1", 
     1350                 "children-INITIAL_FORMS" : "1", 
     1351                 "children-0-selected" : "on", 
     1352                 "children-0-link" : link1.id, 
     1353                 "children-1-selected" : "", 
     1354                 "children-1-link" : link2.id, 
     1355                 "documents-TOTAL_FORMS" : "0", 
     1356                 "documents-INITIAL_FORMS" : "0", 
     1357                 } 
     1358        response = self.post(self.base_url + "revisions/", data) 
     1359        revisions = self.controller.get_next_revisions() 
     1360        self.assertEqual(1, len(revisions)) 
     1361        rev = revisions[0].part 
     1362        self.assertEqual("b", rev.revision) 
     1363        # ensure the part is still a child of the old revision 
     1364        children = self.controller.get_children(1) 
     1365        self.assertEqual(set(((1, link1), (1, link2))), set(children)) 
     1366        children = PartController(rev, self.user).get_children(1) 
     1367        self.assertEqual(1, len(children)) 
     1368 
     1369        link = children[0].link 
     1370        self.assertEqual(link1.child, link.child) 
     1371        self.assertEqual(link1.order, link.order) 
     1372        self.assertEqual(link1.quantity, link.quantity) 
     1373        self.assertEqual(link1.unit, link.unit) 
     1374 
     1375 
     1376    def test_revise_one_parent_get(self): 
     1377        """ 
     1378        Tests a get request to revise a part which has one parent. 
     1379        This parent must be suggested when the user revises the part. 
     1380        """ 
     1381        parent = PartController.create("RefParent", "Part", "a", self.user, self.DATA) 
     1382        parent.add_child(self.controller, 10, 25, "-") 
     1383        response = self.get(self.base_url + "revisions/") 
     1384        # checks that it is necessary to confirm the revision  
     1385        self.assertTrue(response.context["confirmation"]) 
     1386        formset = response.context["parents_formset"] 
     1387        self.assertEqual(1, formset.total_form_count()) 
     1388        form = formset.forms[0] 
     1389        self.assertFalse(form.fields["selected"].initial) 
     1390        self.assertEqual(parent.id, form.initial["link"].parent_id) 
     1391        self.assertEqual(parent.id, form.initial["new_parent"].id) 
     1392    
     1393    def test_revise_one_parent_post_unselected(self): 
     1394        """ 
     1395        Tests a post request to revise a part which has one parent. 
     1396        This parent is not selected, and so its bom should not change. 
     1397        """ 
     1398        parent = PartController.create("RefParent", "Part", "a", self.user, self.DATA) 
     1399        parent.add_child(self.controller, 10, 25, "-") 
     1400        link = parent.get_children(1)[0].link 
     1401        data = { "revision" : "b", 
     1402                 "parents-TOTAL_FORMS" : "1", 
     1403                 "parents-INITIAL_FORMS" : "1", 
     1404                 "parents-0-selected" : "", 
     1405                 "parents-0-link" : link.id, 
     1406                 "parents-0-new_parent" : parent.id, 
     1407                 "children-TOTAL_FORMS" : "0", 
     1408                 "children-INITIAL_FORMS" : "0", 
     1409                 "documents-TOTAL_FORMS" : "0", 
     1410                 "documents-INITIAL_FORMS" : "0", 
     1411                 } 
     1412        response = self.post(self.base_url + "revisions/", data) 
     1413        revisions = self.controller.get_next_revisions() 
     1414        self.assertEqual(1, len(revisions)) 
     1415        rev = revisions[0].part 
     1416        self.assertEqual("b", rev.revision) 
     1417        # ensure the old revision is still a child of the parent 
     1418        children = parent.get_children(1) 
     1419        self.assertEqual([(1, link)], children) 
     1420        self.assertFalse(PartController(rev, self.user).get_parents()) 
     1421 
     1422    def test_revise_one_parent_post_selected(self): 
     1423        """ 
     1424        Tests a post request to revise a part which has one parent. 
     1425        This parent is selected, and so its bom must be updated. 
     1426        """ 
     1427        parent = PartController.create("RefParent", "Part", "a", self.user, self.DATA) 
     1428        parent.add_child(self.controller, 10, 25, "-") 
     1429        link = parent.get_children(1)[0].link 
     1430        data = { "revision" : "b", 
     1431                 "parents-TOTAL_FORMS" : "1", 
     1432                 "parents-INITIAL_FORMS" : "1", 
     1433                 "parents-0-selected" : "on", 
     1434                 "parents-0-link" : link.id, 
     1435                 "parents-0-new_parent" : parent.id, 
     1436                 "children-TOTAL_FORMS" : "0", 
     1437                 "children-INITIAL_FORMS" : "0", 
     1438                 "documents-TOTAL_FORMS" : "0", 
     1439                 "documents-INITIAL_FORMS" : "0", 
     1440                 } 
     1441        response = self.post(self.base_url + "revisions/", data) 
     1442        revisions = self.controller.get_next_revisions() 
     1443        self.assertEqual(1, len(revisions)) 
     1444        rev = revisions[0].part 
     1445        self.assertEqual("b", rev.revision) 
     1446        # ensure the old revision is still a child of the parent 
     1447        children = parent.get_children(1) 
     1448        self.assertNotEqual([(1, link)], children) 
     1449        self.assertEqual(1, len(children)) 
     1450        level, link2 = children[0] 
     1451        self.assertEqual(parent.id, link2.parent_id) 
     1452        self.assertEqual(link.order, link2.order) 
     1453        self.assertEqual(link.quantity, link2.quantity) 
     1454        self.assertEqual(link.unit, link2.unit) 
     1455        self.assertEqual(rev.id, link2.child_id) 
     1456        self.assertFalse(self.controller.get_parents()) 
     1457 
     1458    def test_revise_one_revised_parent_get(self): 
     1459        """ 
     1460        Tests a get request to revise a part which has one parent which has been 
     1461        revised. 
     1462        """ 
     1463        parent = PartController.create("RefParent", "Part", "a", self.user, self.DATA) 
     1464        parent.add_child(self.controller, 10, 25, "-") 
     1465        parent2 = parent.revise("the game") 
     1466        response = self.get(self.base_url + "revisions/") 
     1467        # checks that it is necessary to confirm the revision  
     1468        self.assertTrue(response.context["confirmation"]) 
     1469        formset = response.context["parents_formset"] 
     1470        self.assertEqual(1, formset.total_form_count()) 
     1471        form = formset.forms[0] 
     1472        self.assertFalse(form.fields["selected"].initial) 
     1473        self.assertEqual(parent2.id, form.initial["link"].parent_id) 
     1474        self.assertEqual(parent2.id, form.initial["new_parent"].id) 
     1475 
     1476    def test_revise_one_revised_parent_get2(self): 
     1477        """ 
     1478        Tests a get request to revise a part which has one parent which has been 
     1479        revised before the bom was built. 
     1480        """ 
     1481        parent = PartController.create("RefParent", "Part", "a", self.user, self.DATA) 
     1482        parent2 = parent.revise("the game") 
     1483        parent.add_child(self.controller, 10, 25, "-") 
     1484        response = self.get(self.base_url + "revisions/") 
     1485        # checks that it is necessary to confirm the revision  
     1486        self.assertTrue(response.context["confirmation"]) 
     1487        formset = response.context["parents_formset"] 
     1488        self.assertEqual(2, formset.total_form_count()) 
     1489        form1, form2 = formset.forms 
     1490        self.assertFalse(form1.fields["selected"].initial) 
     1491        self.assertEqual(parent.id, form1.initial["link"].parent_id) 
     1492        self.assertEqual(parent.id, form1.initial["new_parent"].id) 
     1493        self.assertFalse(form2.fields["selected"].initial) 
     1494        self.assertEqual(parent.id, form2.initial["link"].parent_id) 
     1495        self.assertEqual(parent2.id, form2.initial["new_parent"].id) 
     1496 
     1497    def test_revise_one_revised_parent_post2(self): 
     1498        """ 
     1499        Tests a post request to revise a part which has one parent which has been 
     1500        revised before the bom was built. 
     1501        """ 
     1502        parent = PartController.create("RefParent", "Part", "a", self.user, self.DATA) 
     1503        parent2 = parent.revise("the game") 
     1504        parent.add_child(self.controller, 10, 25, "-") 
     1505        link = parent.get_children(1)[0].link 
     1506        response = self.get(self.base_url + "revisions/") 
     1507        # checks that it is necessary to confirm the revision  
     1508        self.assertTrue(response.context["confirmation"]) 
     1509        formset = response.context["parents_formset"] 
     1510        self.assertEqual(2, formset.total_form_count()) 
     1511        data = { "revision" : "b", 
     1512                 "parents-TOTAL_FORMS" : "2", 
     1513                 "parents-INITIAL_FORMS" : "2", 
     1514                 "parents-0-selected" : "", 
     1515                 "parents-0-link" : link.id, 
     1516                 "parents-0-new_parent" : parent.id, 
     1517                 "parents-1-selected" : "on", 
     1518                 "parents-1-link" : link.id, 
     1519                 "parents-1-new_parent" : parent2.id, 
     1520                 "children-TOTAL_FORMS" : "0", 
     1521                 "children-INITIAL_FORMS" : "0", 
     1522                 "documents-TOTAL_FORMS" : "0", 
     1523                 "documents-INITIAL_FORMS" : "0", 
     1524                 } 
     1525        response = self.post(self.base_url + "revisions/", data) 
     1526        revisions = self.controller.get_next_revisions() 
     1527        self.assertEqual(1, len(revisions)) 
     1528        rev = revisions[0].part 
     1529        self.assertEqual("b", rev.revision) 
     1530        # ensure the old revision is still a child of the parent 
     1531        children = parent.get_children(1) 
     1532        self.assertEqual(1, len(children)) 
     1533        level, link2 = children[0] 
     1534        self.assertEqual(parent.id, link2.parent_id) 
     1535        self.assertEqual(link.order, link2.order) 
     1536        self.assertEqual(link.quantity, link2.quantity) 
     1537        self.assertEqual(link.unit, link2.unit) 
     1538        self.assertEqual(self.controller.id, link2.child_id) 
     1539        # ensure the new revisison is a child of the parent 
     1540        children = parent2.get_children(1) 
     1541        self.assertEqual(1, len(children)) 
     1542        level, link2 = children[0] 
     1543        self.assertEqual(parent2.id, link2.parent_id) 
     1544        self.assertEqual(link.order, link2.order) 
     1545        self.assertEqual(link.quantity, link2.quantity) 
     1546        self.assertEqual(link.unit, link2.unit) 
     1547        self.assertEqual(rev.id, link2.child_id) 
     1548 
     1549     
    4851550class UserViewTestCase(CommonViewTest): 
    4861551 
     
    6101675            m.DelegationLink.objects.get(role=role, delegator=self.user, 
    6111676                    delegatee=self.brian) 
     1677 
     1678    def test_resend_sponsor_mail(self): 
     1679        user = User(username="dede", email="dede@example.net") 
     1680        self.controller.sponsor(user) 
     1681        link = m.DelegationLink.objects.get(role="sponsor", delegatee=user) 
     1682        pwd = user.password 
     1683        mail.outbox = [] 
     1684        self.post(self.user_url + 'delegation/sponsor/mail/', 
     1685                {"link_id" : link.id}) 
     1686        self.assertEqual(1, len(mail.outbox)) 
     1687        self.assertEqual(mail.outbox[0].to, [user.email]) 
     1688        user = User.objects.get(username="dede") 
     1689        self.assertNotEqual(user.password, pwd) 
     1690 
     1691    def test_resend_sponsor_error_user_connected(self): 
     1692        user = User(username="dede", email="dede@example.net") 
     1693        self.controller.sponsor(user) 
     1694        user.last_login = datetime.datetime.now() 
     1695        user.save() 
     1696        link = m.DelegationLink.objects.get(role="sponsor", delegatee=user) 
     1697        pwd = user.password 
     1698        mail.outbox = [] 
     1699        self.post(self.user_url + 'delegation/sponsor/mail/', 
     1700                {"link_id" : link.id}, status_code=403) 
     1701        self.assertEqual(0, len(mail.outbox)) 
     1702        user = User.objects.get(username="dede") 
     1703        self.assertEqual(user.password, pwd) 
     1704 
     1705    def test_resend_sponsor_error_not_sponsor(self): 
     1706        user = User(username="dede", email="dede@example.net") 
     1707        UserController(self.cie, self.cie).sponsor(user) 
     1708        link = m.DelegationLink.objects.get(role="sponsor", delegatee=user) 
     1709        pwd = user.password 
     1710        mail.outbox = [] 
     1711        self.post(self.user_url + 'delegation/sponsor/mail/', 
     1712                {"link_id" : link.id}, status_code=403) 
     1713        self.assertEqual(0, len(mail.outbox)) 
     1714        user = User.objects.get(username="dede") 
     1715        self.assertEqual(user.password, pwd) 
     1716 
     1717 
     1718class GroupViewTestCase(CommonViewTest): 
     1719 
     1720    def setUp(self): 
     1721        super(GroupViewTestCase, self).setUp() 
     1722        self.part_controller = self.controller 
     1723        self.group_url = "/group/%s/" % self.group.name 
     1724        self.controller = GroupController(self.group, self.user) 
     1725         
     1726    def test_group_attributes(self): 
     1727        response = self.get(self.group_url + "attributes/", page="attributes") 
     1728        attributes = dict((x.capitalize(), y) for (x,y) in  
     1729                          response.context["object_attributes"]) 
     1730        self.assertEqual(attributes["Description"], self.group.description) 
     1731        self.assertTrue(response.context["is_owner"]) 
     1732 
     1733    def test_users(self): 
     1734        response = self.get(self.group_url + "users/", page="users") 
     1735        user_formset = response.context["user_formset"] 
     1736        self.assertEqual(0, user_formset.total_form_count()) 
     1737        self.assertTrue(response.context["in_group"]) 
     1738 
     1739    def test_users_get(self): 
     1740        self.brian.groups.add(self.group) 
     1741        response = self.get(self.group_url + "users/", page="users") 
     1742        user_formset = response.context["user_formset"] 
     1743        self.assertEqual(1, user_formset.total_form_count()) 
     1744        form = user_formset.forms[0] 
     1745        self.assertFalse(form.fields["delete"].initial) 
     1746        self.assertEqual(self.brian, form.initial["user"]) 
     1747        self.assertEqual(self.group, form.initial["group"]) 
     1748 
     1749    def test_users_post(self): 
     1750        self.brian.groups.add(self.group) 
     1751        data = { 
     1752            'form-TOTAL_FORMS' : '1', 
     1753            'form-INITIAL_FORMS' : '1', 
     1754            'form-MAX_NUM_FORMS' : 1, 
     1755            'form-0-group' : self.group.id, 
     1756            'form-0-user' : self.brian.id, 
     1757            'form-0-delete' : 'on', 
     1758            } 
     1759        response = self.post(self.group_url + "users/", data) 
     1760        self.assertEqual([], list(self.brian.groups.all())) 
    6121761     
     1762    def test_users_post_nodeletetion(self): 
     1763        self.brian.groups.add(self.group) 
     1764        data = { 
     1765            'form-TOTAL_FORMS' : '1', 
     1766            'form-INITIAL_FORMS' : '1', 
     1767            'form-MAX_NUM_FORMS' : 1, 
     1768            'form-0-group' : self.group.id, 
     1769            'form-0-user' : self.brian.id, 
     1770            'form-0-delete' : '', 
     1771            } 
     1772        response = self.post(self.group_url + "users/", data) 
     1773        self.assertTrue(self.brian.groups.filter(id=self.group.id).exists()) 
     1774        user_formset = response.context["user_formset"] 
     1775        self.assertEqual(1, user_formset.total_form_count()) 
     1776        form = user_formset.forms[0] 
     1777        self.assertFalse(form.fields["delete"].initial) 
     1778        self.assertEqual(self.brian, form.initial["user"]) 
     1779        self.assertEqual(self.group, form.initial["group"]) 
     1780 
     1781    def test_plmobjects(self): 
     1782        response = self.get(self.group_url + "objects/", page="objects") 
     1783        objects = response.context["objects"] 
     1784        self.assertEqual([self.part_controller.plmobject_ptr], list(objects)) 
     1785        # create a new group 
     1786        group = m.GroupInfo(name="grp2", owner=self.user, creator=self.user, 
     1787                description="grp") 
     1788        group.save() 
     1789        self.user.groups.add(group) 
     1790        # create another part which bellows to another group 
     1791        p2 = PartController.create("Part2", "Part", "a", self.user, 
     1792                dict(group=group)) 
     1793        response = self.get(self.group_url + "objects/", page="objects") 
     1794        objects = response.context["objects"] 
     1795        self.assertEqual([self.part_controller.plmobject_ptr], list(objects)) 
     1796         
     1797    def test_history(self): 
     1798        response = self.get(self.group_url + "history/", page="history") 
     1799         
     1800    def test_navigate(self): 
     1801        response = self.get(self.group_url + "navigate/") 
     1802 
     1803    def test_user_add_get(self): 
     1804        """ 
     1805        Tests the page to add an user to the group, get version. 
     1806        """ 
     1807        response = self.get(self.group_url + "users/add/", page="users", 
     1808                link=True) 
     1809        form = response.context["add_user_form"] 
     1810 
     1811    def test_user_add_post(self): 
     1812        """ 
     1813        Tests the page to add an user to the group, post version. 
     1814        """ 
     1815        mail.outbox = [] 
     1816        data = {"type" : "User", "username" : self.brian.username} 
     1817        response = self.post(self.group_url + "users/add/", data=data) 
     1818        inv = m.Invitation.objects.get(group=self.group, 
     1819                guest=self.brian, owner=self.user) 
     1820        self.assertFalse(inv.guest_asked) 
     1821        self.assertEqual(m.Invitation.PENDING, inv.state) 
     1822        self.assertFalse(self.brian.groups.count()) 
     1823        # get the users page 
     1824        response = self.get(self.group_url + "users/") 
     1825        pending = response.context["pending_invitations"] 
     1826        self.assertEqual([inv], list(pending)) 
     1827        # check a mail has been sent to brian 
     1828        self.assertEqual(1, len(mail.outbox)) 
     1829        self.assertEqual(mail.outbox[0].to, [self.brian.email]) 
     1830     
     1831    def test_user_join_get(self): 
     1832        """ 
     1833        Tests the page to ask to join the group, get version. 
     1834        """ 
     1835        authenticated = self.client.login(username="Brian", password="life") 
     1836        self.assertTrue(authenticated) 
     1837        response = self.get(self.group_url + "users/join/", page="users") 
     1838        self.assertFalse(response.context["in_group"]) 
     1839 
     1840    def test_user_join_post(self): 
     1841        """ 
     1842        Tests the page to ask to join the group, post version. 
     1843        """ 
     1844        mail.outbox = [] 
     1845        self.client.login(username="Brian", password="life") 
     1846        data = {"type" : "User", "username" : self.brian.username} 
     1847        response = self.post(self.group_url + "users/join/", data=data) 
     1848        inv = m.Invitation.objects.get(group=self.group, 
     1849                guest=self.brian, owner=self.user) 
     1850        self.assertTrue(inv.guest_asked) 
     1851        self.assertEqual(m.Invitation.PENDING, inv.state) 
     1852        self.assertFalse(self.brian.groups.count()) 
     1853        # get the users page 
     1854        response = self.get(self.group_url + "users/") 
     1855        pending = response.context["pending_invitations"] 
     1856        self.assertEqual([inv], list(pending)) 
     1857        # check a mail has been sent to brian 
     1858        self.assertEqual(1, len(mail.outbox)) 
     1859        self.assertEqual(mail.outbox[0].to, [self.user.email]) 
     1860 
     1861    def _do_test_accept_invitation_get(self): 
     1862        mail.outbox = [] 
     1863        inv = m.Invitation.objects.get(group=self.group, 
     1864                guest=self.brian, owner=self.user) 
     1865        response = self.get(self.group_url + "invitation/accept/%s/" % inv.token, 
     1866                page="users") 
     1867        self.assertEqual(inv, response.context["invitation"]) 
     1868        form = response.context["invitation_form"] 
     1869        self.assertEqual(form.initial["invitation"], inv) 
     1870        # check that brian does not belong to the group 
     1871        self.assertFalse(self.brian.groups.count()) 
     1872        self.assertFalse(mail.outbox) 
     1873     
     1874    def test_accept_invitation_from_guest_get(self): 
     1875        """ 
     1876        Tests the page to accept an invitation, get version. 
     1877        """ 
     1878        GroupController(self.group, self.brian).ask_to_join() 
     1879        self._do_test_accept_invitation_get() 
     1880 
     1881    def _do_test_accept_invitation_post_error(self): 
     1882        mail.outbox = [] 
     1883        inv = m.Invitation.objects.get(group=self.group, 
     1884                guest=self.brian, owner=self.user) 
     1885        data = {"invitation" : inv.pk } 
     1886        response = self.client.post(self.group_url + "invitation/accept/%s/" % inv.token, 
     1887                data=data) 
     1888        self.assertTemplateUsed(response, "error.html") 
     1889        # checks that brian does not belong to the group 
     1890        self.assertFalse(self.brian.groups.filter(id=self.group.id).exists()) 
     1891        self.assertEqual(0, len(mail.outbox)) 
     1892        inv = m.Invitation.objects.get(group=self.group, 
     1893                guest=self.brian, owner=self.user) 
     1894        self.assertEqual(m.Invitation.PENDING, inv.state) 
     1895 
     1896    def test_accept_invitation_from_guest_post_error(self): 
     1897        """ 
     1898        Tests the page to accept an invitation, post version, 
     1899        Error: not the guest asks and accepts. 
     1900        """ 
     1901        GroupController(self.group, self.brian).ask_to_join() 
     1902        self.client.login(username="Brian", password="life") 
     1903        self._do_test_accept_invitation_post_error() 
     1904 
     1905    def test_accept_invitation_from_owner_post_error(self): 
     1906        """ 
     1907        Tests the page to accept an invitation, post version. 
     1908        Error: the owner adds and accepts. 
     1909        """ 
     1910        self.controller.add_user(self.brian) 
     1911        self._do_test_accept_invitation_post_error() 
     1912 
     1913    def _do_test_accept_invitation_post(self): 
     1914        mail.outbox = [] 
     1915        inv = m.Invitation.objects.get(group=self.group, 
     1916                guest=self.brian, owner=self.user) 
     1917        data = {"invitation" : inv.pk } 
     1918        response = self.post(self.group_url + "invitation/accept/%s/" % inv.token, 
     1919                page="users", data=data) 
     1920        # checks that brian belongs to the group 
     1921        self.assertFalse(response.context["pending_invitations"]) 
     1922        form = response.context["user_formset"].forms[0] 
     1923        self.assertEqual(self.brian, form.initial["user"]) 
     1924        self.assertTrue(self.brian.groups.filter(id=self.group.id).exists()) 
     1925        self.assertEqual(1, len(mail.outbox)) 
     1926        # a notification is sent to the owner and to the guest 
     1927        self.assertEqual(set(mail.outbox[0].to),  
     1928                set([self.user.email, self.brian.email])) 
     1929        inv = m.Invitation.objects.get(group=self.group, 
     1930                guest=self.brian, owner=self.user) 
     1931        self.assertEqual(m.Invitation.ACCEPTED, inv.state) 
     1932 
     1933    def test_accept_invitation_from_guest_post(self): 
     1934        """ 
     1935        Tests the page to accept an invitation, post version. 
     1936        """ 
     1937        GroupController(self.group, self.brian).ask_to_join() 
     1938        self._do_test_accept_invitation_post() 
     1939     
     1940    def test_accept_invitation_from_owner_get(self): 
     1941        """ 
     1942        Tests the page to accept an invitation, get version. 
     1943        """ 
     1944        self.controller.add_user(self.brian) 
     1945        self.client.login(username="Brian", password="life") 
     1946        self._do_test_accept_invitation_get() 
     1947 
     1948    def test_accept_invitation_from_owner_post(self): 
     1949        """ 
     1950        Tests the page to accept an invitation, post version. 
     1951        """ 
     1952        self.controller.add_user(self.brian) 
     1953        self.client.login(username="Brian", password="life") 
     1954        self._do_test_accept_invitation_post() 
     1955 
     1956    def _do_test_refuse_invitation_get(self): 
     1957        mail.outbox = [] 
     1958        inv = m.Invitation.objects.get(group=self.group, 
     1959                guest=self.brian, owner=self.user) 
     1960        response = self.get(self.group_url + "invitation/refuse/%s/" % inv.token, 
     1961                page="users") 
     1962        self.assertEqual(inv, response.context["invitation"]) 
     1963        form = response.context["invitation_form"] 
     1964        self.assertEqual(form.initial["invitation"], inv) 
     1965        # check that brian does not belong to the group 
     1966        self.assertFalse(self.brian.groups.count()) 
     1967        self.assertFalse(mail.outbox) 
     1968     
     1969    def test_refuse_invitation_from_guest_get(self): 
     1970        """ 
     1971        Tests the page to refuse an invitation, get version. 
     1972        """ 
     1973        GroupController(self.group, self.brian).ask_to_join() 
     1974        self._do_test_refuse_invitation_get() 
     1975 
     1976    def _do_test_refuse_invitation_post(self): 
     1977        mail.outbox = [] 
     1978        inv = m.Invitation.objects.get(group=self.group, 
     1979                guest=self.brian, owner=self.user) 
     1980        data = {"invitation" : inv.pk } 
     1981        response = self.post(self.group_url + "invitation/refuse/%s/" % inv.token, 
     1982                page="users", data=data) 
     1983        # checks that brian does not belong to the group 
     1984        self.assertFalse(response.context["pending_invitations"]) 
     1985        self.assertFalse(response.context["user_formset"].forms) 
     1986        self.assertFalse(self.brian.groups.filter(id=self.group.id).exists()) 
     1987        inv = m.Invitation.objects.get(group=self.group, 
     1988                guest=self.brian, owner=self.user) 
     1989        self.assertEqual(m.Invitation.REFUSED, inv.state) 
     1990 
     1991    def test_refuse_invitation_from_guest_post(self): 
     1992        """ 
     1993        Tests the page to refuse an invitation, post version. 
     1994        """ 
     1995        GroupController(self.group, self.brian).ask_to_join() 
     1996        self._do_test_refuse_invitation_post() 
     1997     
     1998    def test_refuse_invitation_from_owner_get(self): 
     1999        """ 
     2000        Tests the page to refuse an invitation, get version. 
     2001        """ 
     2002        self.controller.add_user(self.brian) 
     2003        self.client.login(username="Brian", password="life") 
     2004        self._do_test_refuse_invitation_get() 
     2005 
     2006    def test_refuse_invitation_from_owner_post(self): 
     2007        """ 
     2008        Tests the page to refuse an invitation, post version. 
     2009        """ 
     2010        self.controller.add_user(self.brian) 
     2011        self.client.login(username="Brian", password="life") 
     2012        self._do_test_refuse_invitation_post() 
     2013 
     2014    def _do_test_refuse_invitation_post_error(self): 
     2015        mail.outbox = [] 
     2016        inv = m.Invitation.objects.get(group=self.group, 
     2017                guest=self.brian, owner=self.user) 
     2018        data = {"invitation" : inv.pk } 
     2019        response = self.client.post(self.group_url + "invitation/refuse/%s/" % inv.token, 
     2020                data=data) 
     2021        self.assertTemplateUsed(response, "error.html") 
     2022        # checks that brian does not belong to the group 
     2023        self.assertFalse(self.brian.groups.filter(id=self.group.id).exists()) 
     2024        self.assertEqual(0, len(mail.outbox)) 
     2025        inv = m.Invitation.objects.get(group=self.group, 
     2026                guest=self.brian, owner=self.user) 
     2027        self.assertEqual(m.Invitation.PENDING, inv.state) 
     2028 
     2029    def test_refuse_invitation_from_guest_post_error(self): 
     2030        """ 
     2031        Tests the page to refuse an invitation, post version, 
     2032        Error: not the guest asks and refuses. 
     2033        """ 
     2034        GroupController(self.group, self.brian).ask_to_join() 
     2035        self.client.login(username="Brian", password="life") 
     2036        self._do_test_refuse_invitation_post_error() 
     2037 
     2038    def test_refuse_invitation_from_owner_post_error(self): 
     2039        """ 
     2040        Tests the page to refuse an invitation, post version. 
     2041        Error: the owner adds and refuses. 
     2042        """ 
     2043        self.controller.add_user(self.brian) 
     2044        self._do_test_refuse_invitation_post_error() 
     2045 
     2046    def _do_test_send_invitation_post_error(self): 
     2047        mail.outbox = [] 
     2048        inv = m.Invitation.objects.get(group=self.group, 
     2049                guest=self.brian, owner=self.user) 
     2050        data = {"invitation" : inv.pk } 
     2051        response = self.client.post(self.group_url + "invitation/send/%s/" % inv.token, 
     2052                data=data) 
     2053        self.assertTemplateUsed(response, "error.html") 
     2054        # checks that brian does not belong to the group 
     2055        self.assertFalse(self.brian.groups.filter(id=self.group.id).exists()) 
     2056        self.assertEqual(0, len(mail.outbox)) 
     2057        inv = m.Invitation.objects.get(group=self.group, 
     2058                guest=self.brian, owner=self.user) 
     2059        self.assertEqual(m.Invitation.PENDING, inv.state) 
     2060 
     2061    def test_send_invitation_from_guest_post_error(self): 
     2062        """ 
     2063        Tests the page to send an invitation, post version, 
     2064        Error: not the guest asks and sends. 
     2065        """ 
     2066        GroupController(self.group, self.brian).ask_to_join() 
     2067        self._do_test_send_invitation_post_error() 
     2068 
     2069    def test_send_invitation_from_owner_post_error(self): 
     2070        """ 
     2071        Tests the page to send an invitation, post version. 
     2072        Error: the owner adds and sends. 
     2073        """ 
     2074        self.controller.add_user(self.brian) 
     2075        self.client.login(username="Brian", password="life") 
     2076        self._do_test_send_invitation_post_error() 
     2077 
     2078    def _do_test_send_invitation_get(self): 
     2079        mail.outbox = [] 
     2080        inv = m.Invitation.objects.get(group=self.group, 
     2081                guest=self.brian, owner=self.user) 
     2082        response = self.get(self.group_url + "invitation/send/%s/" % inv.token, 
     2083                page="users") 
     2084        # check that brian does not belong to the group 
     2085        self.assertFalse(self.brian.groups.count()) 
     2086        self.assertFalse(mail.outbox) 
     2087     
     2088    def test_send_invitation_from_guest_get(self): 
     2089        """ 
     2090        Tests the page to send an invitation, get version. 
     2091        """ 
     2092        GroupController(self.group, self.brian).ask_to_join() 
     2093        self.client.login(username="Brian", password="life") 
     2094        self._do_test_send_invitation_get() 
     2095 
     2096    def _do_test_send_invitation_post(self, from_owner): 
     2097        mail.outbox = [] 
     2098        inv = m.Invitation.objects.get(group=self.group, 
     2099                guest=self.brian, owner=self.user) 
     2100        data = {"invitation" : inv.pk } 
     2101        response = self.post(self.group_url + "invitation/send/%s/" % inv.token, 
     2102                page="users", data=data) 
     2103        # checks that brian does not belong to the group 
     2104        self.assertEqual([inv], list(response.context["pending_invitations"])) 
     2105        self.assertFalse(response.context["user_formset"].forms) 
     2106        self.assertFalse(self.brian.groups.filter(id=self.group.id).exists()) 
     2107        inv = m.Invitation.objects.get(group=self.group, 
     2108                guest=self.brian, owner=self.user) 
     2109        self.assertEqual(m.Invitation.PENDING, inv.state) 
     2110        # check a mail has been sent to the right user 
     2111        self.assertEqual(1, len(mail.outbox)) 
     2112        email = self.brian.email if from_owner else self.user.email 
     2113        self.assertEqual(mail.outbox[0].to, [email]) 
     2114 
     2115    def test_send_invitation_from_guest_post(self): 
     2116        """ 
     2117        Tests the page to send an invitation, post version. 
     2118        """ 
     2119        GroupController(self.group, self.brian).ask_to_join() 
     2120        self.client.login(username="Brian", password="life") 
     2121        self._do_test_send_invitation_post(False) 
     2122     
     2123    def test_send_invitation_from_owner_get(self): 
     2124        """ 
     2125        Tests the page to send an invitation, get version. 
     2126        """ 
     2127        self.controller.add_user(self.brian) 
     2128        self._do_test_send_invitation_get() 
     2129 
     2130    def test_send_invitation_from_owner_post(self): 
     2131        """ 
     2132        Tests the page to send an invitation, post version. 
     2133        """ 
     2134        self.controller.add_user(self.brian) 
     2135        self._do_test_send_invitation_post(True) 
     2136 
     2137  
    6132138def sorted_objects(l): 
    6142139    return sorted(l, key=lambda x: x.id) 
     
    7632288        results = self.search("NOT nothing", self.TYPE) 
    7642289        self.assertEqual([self.controller.object], results) 
     2290 
     2291    def test_search_invalid_type(self): 
     2292        results = self.search("NOT nothing", "InvalidType") 
     2293        self.assertEqual([], results) 
     2294 
     2295    def test_search_in_file(self): 
     2296        doc = DocumentController.create("doccc", "Document", "d", 
     2297                self.user, self.DATA) 
     2298        df = doc.add_file(self.get_file(name="pppp.txt", data="monocle monocle")) 
     2299        results = self.search("monocle", "Document") 
     2300        self.assertEqual([df], results) 
     2301        results = self.search("monocl*", "Document") 
     2302        self.assertEqual([df], results) 
     2303        results = self.search("file:monocl*", "Document") 
     2304        self.assertEqual([df], results) 
     2305        results = self.search("dfdf", "Document") 
     2306        self.assertEqual([], results) 
     2307        results = self.search("pppp.txt", "Document") 
     2308        self.assertEqual([df], results) 
     2309 
     2310    def test_search_in_odt(self): 
     2311        doc = DocumentController.create("doccc", "Document", "d", 
     2312                self.user, self.DATA) 
     2313        df = doc.add_file(File(open("datatests/office_a4_3p.odt", "rb"))) 
     2314        results = self.search("find me", "Document") 
     2315        self.assertEqual([df], results) 
     2316 
    7652317 
    7662318 
  • branches/3D/openPLM/plmapp/utils.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/views/ajax.py

    r817 r870  
    1818# 
    1919#    You should have received a copy of the GNU General Public License 
    20 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     20#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    2121# 
    2222# Contact : 
  • branches/3D/openPLM/plmapp/views/api.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
  • branches/3D/openPLM/plmapp/views/main.py

    r817 r870  
    1818# 
    1919#    You should have received a copy of the GNU General Public License 
    20 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     20#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    2121# 
    2222# Contact : 
     
    226226    return r2r('lifecycle.html', ctx, request) 
    227227 
    228 ########################################################################################## 
     228 
    229229@handle_errors 
    230230def display_object_revisions(request, obj_type, obj_ref, obj_revi): 
     
    236236    """ 
    237237    obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi) 
     238    ctx["add_revision_form"] = None 
    238239    if obj.is_document: 
    239         confirmation = obj.get_attached_parts().exists() 
    240     else: 
    241         confirmation = False 
     240        return revise_document(obj, ctx, request) 
     241    else: 
     242        return revise_part(obj, ctx, request) 
     243 
     244def revise_document(obj, ctx, request): 
     245    """ View to revise a document. """ 
     246    confirmation = False 
    242247    if obj.is_revisable(): 
     248        parts = obj.get_suggested_parts() 
     249        confirmation = bool(parts) 
     250        
    243251        if request.method == "POST" and request.POST: 
    244252            add_form = AddRevisionForm(request.POST) 
    245             kwargs = {} 
    246             if obj.is_document and confirmation: 
    247                 part_formset = forms.SelectPLMObjectFormset(request.POST) 
    248                 parts = [] 
     253            selected_parts = [] 
     254            valid_forms = True 
     255            if confirmation: 
     256                part_formset = forms.SelectPartFormset(request.POST) 
    249257                if part_formset.is_valid(): 
    250258                    for form in part_formset.forms: 
    251                         doc = form.cleaned_data["document"] 
    252                         if doc.pk != obj.pk: 
    253                             raise ValueError("Bad document") 
    254                         part = form.cleaned_data["part"] 
    255                         if form.cleaned_data["selected"] and part.is_editable: 
    256                             parts.append(part) 
    257                 kwargs["selected_parts"] = parts 
    258             if add_form.is_valid(): 
    259                 obj.revise(add_form.cleaned_data["revision"], **kwargs) 
     259                        part = form.instance 
     260                        if part not in parts:  
     261                            # invalid data 
     262                            # an user should not be able to go here if he  
     263                            # does not write by hand its post request 
     264                            # so we do not need to generate an error message 
     265                            valid_forms = False 
     266                            break 
     267                        if form.cleaned_data["selected"]: 
     268                            selected_parts.append(part) 
     269                else: 
     270                    valid_forms = False 
     271            if add_form.is_valid() and valid_forms: 
     272                obj.revise(add_form.cleaned_data["revision"], selected_parts) 
     273                return HttpResponseRedirect(".") 
    260274        else: 
    261             add_form = AddRevisionForm({"revision" : get_next_revision(obj_revi)}) 
    262             if obj.is_document: 
    263                 qs = obj.get_attached_parts() 
    264                 ctx["part_formset"] = forms.SelectPLMObjectFormset(queryset=qs) 
     275            add_form = AddRevisionForm({"revision" : get_next_revision(obj.revision)}) 
     276            if confirmation: 
     277                ctx["part_formset"] = forms.SelectPartFormset(queryset=parts) 
    265278        ctx["add_revision_form"] = add_form 
    266279 
     
    270283                'revisions' : revisions, 
    271284                }) 
    272     return r2r('revisions.html', ctx, request) 
     285    return r2r('documents/revisions.html', ctx, request) 
     286 
     287def revise_part(obj, ctx, request): 
     288    """ View to revise a part. """ 
     289    confirmation = False 
     290    if obj.is_revisable(): 
     291        children = [c.link for c in obj.get_children(1)] 
     292        parents = obj.get_suggested_parents() 
     293        documents = obj.get_suggested_documents() 
     294        confirmation = bool(children or parents or documents) 
     295 
     296        if request.method == "POST" and request.POST: 
     297            add_form = AddRevisionForm(request.POST) 
     298            valid_forms = True 
     299            selected_children = [] 
     300            selected_parents = [] 
     301            selected_documents = [] 
     302            if confirmation: 
     303                # children 
     304                children_formset = forms.SelectChildFormset(request.POST, 
     305                        prefix="children") 
     306                if children_formset.is_valid(): 
     307                    for form in children_formset.forms: 
     308                        link = form.cleaned_data["link"] 
     309                        if link not in children:  
     310                            valid_forms = False 
     311                            break 
     312                        if form.cleaned_data["selected"]: 
     313                            selected_children.append(link) 
     314                else: 
     315                    valid_forms = False 
     316                if valid_forms: 
     317                    # documents 
     318                    doc_formset = forms.SelectDocumentFormset(request.POST, 
     319                            prefix="documents") 
     320                    if doc_formset.is_valid(): 
     321                        for form in doc_formset.forms: 
     322                            doc = form.cleaned_data["document"] 
     323                            if doc not in documents:  
     324                                valid_forms = False 
     325                                break 
     326                            if form.cleaned_data["selected"]: 
     327                                selected_documents.append(doc) 
     328                    else: 
     329                        valid_forms = False 
     330                if valid_forms: 
     331                    # parents 
     332                    parents_formset = forms.SelectParentFormset(request.POST, 
     333                            prefix="parents") 
     334                    if parents_formset.is_valid(): 
     335                        for form in parents_formset.forms: 
     336                            parent = form.cleaned_data["new_parent"] 
     337                            link = form.cleaned_data["link"] 
     338                            if (link, parent) not in parents:  
     339                                valid_forms = False 
     340                                break 
     341                            if form.cleaned_data["selected"]: 
     342                                selected_parents.append((link, parent)) 
     343                    else: 
     344                        valid_forms = False 
     345            if add_form.is_valid() and valid_forms: 
     346                obj.revise(add_form.cleaned_data["revision"], selected_children, 
     347                        selected_documents, selected_parents) 
     348                return HttpResponseRedirect(".") 
     349        else: 
     350            add_form = AddRevisionForm({"revision" : get_next_revision(obj.revision)}) 
     351            if confirmation: 
     352                initial = [dict(link=link) for link in children] 
     353                ctx["children_formset"] = forms.SelectChildFormset(prefix="children", 
     354                        initial=initial) 
     355                initial = [dict(document=d) for d in documents] 
     356                ctx["doc_formset"] = forms.SelectDocumentFormset(prefix="documents", 
     357                        initial=initial) 
     358                initial = [dict(link=p[0], new_parent=p[1]) for p in parents] 
     359                ctx["parents_formset"] = forms.SelectParentFormset(prefix="parents", 
     360                        initial=initial) 
     361 
     362        ctx["add_revision_form"] = add_form 
     363 
     364    ctx["confirmation"] = confirmation 
     365    revisions = obj.get_all_revisions() 
     366    ctx.update({'current_page' : 'revisions', 
     367                'revisions' : revisions, 
     368                }) 
     369    return r2r('parts/revisions.html', ctx, request) 
    273370 
    274371########################################################################################## 
     
    11251222    ctx["add_user_form"] = form 
    11261223    ctx['current_page'] = 'users'  
     1224    ctx['link_creation'] = True 
    11271225    return r2r("groups/add_user.html", ctx, request) 
    11281226 
     
    11881286     
    11891287    obj, ctx = get_generic_data(request, "Group", obj_ref) 
    1190     ctx["objects"] = obj.plmobject_group.all().order_by("type", "reference", "revision") 
    1191     ctx['current_page'] = 'groups' 
     1288    ctx["objects"] = obj.plmobject_group.order_by("type", "reference", "revision") 
     1289    ctx['current_page'] = 'objects' 
    11921290    return r2r("groups/objects.html", ctx, request) 
    11931291 
  • branches/3D/openPLM/templates/revisions.html

    r817 r870  
    11{% extends "base.html" %} 
    22{% load i18n plmapp_tags %} 
    3  
    4 {% block css %} 
    5  
    6     <link rel="stylesheet" href="/media/css/lifecycle.css" type="text/css" charset="utf-8" /> 
    7 {% endblock css %} 
    83<!-- Manage html display in the Content div which correspond to the "revisions" menu --> 
    94 
     
    138            {% if confirmation %} class="confirmation" {% endif %} action=""> 
    149            {{ add_revision_form }} 
    15             {% if obj.is_document and confirmation %} 
    16                 <div id="form-revision-dialog" 
    17                     title="{% trans "Are you sure?" %}" 
    18                     {% if part_formset.is_bound and not part_formset.is_valid %}  
    19                         class="c-error action-{{ action }}" 
    20                     {% endif %} 
    21                     > 
    22                     <p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span>{% trans "Here is the list of parts connected to the original document. Do you want to connect them to new document?" %}</p> 
    23                     <div class="parts"> 
    24                         {{ part_formset.management_form }} 
    25  
    26                         <table class="Content"> 
    27                             {% for form in part_formset.forms %} 
    28                                 {{ form.id }} 
    29                                 {{ form.document }} 
    30  
    31                                 {{ form.part }} 
    32                                 <tr class="Content"> 
    33                                     {% with form.instance.part as part %} 
    34                                         {% if part.is_editable %} 
    35                                             <td class="Content" style="width:50px; text-align:center">{{ form.selected }}</td> 
    36                                         {% else %} 
    37                                             <td class="Content">-</td> 
    38                                         {% endif %} 
    39                                         <td class="Content">{{ part.type }}</td> 
    40                                         <td class="Content">{{ part.reference }} </td> 
    41                                         <td class="Content">{{ part.revision }}</td> 
    42                                         <td class="Content">{{ part.name }}</td> 
    43                                     {% endwith %} 
    44                                 </tr> 
    45                             {% endfor %} 
    46                         </table> 
    47  
    48                     </div> 
    49                 </div> 
     10             
     11            {% if confirmation %} 
     12                {% block confirmation %} {% endblock %} 
    5013            {% endif %} 
    5114 
  • branches/3D/openPLM/urls.py

    r817 r870  
    1616# 
    1717#    You should have received a copy of the GNU General Public License 
    18 #    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. 
     18#    along with openPLM.  If not, see <http://www.gnu.org/licenses/>. 
    1919# 
    2020# Contact : 
Note: See TracChangeset for help on using the changeset viewer.