diff --git a/.coveragerc b/.coveragerc index 93b8d443..f66a2b6c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,6 @@ [report] include = src/collective/* - src/collective.contact.widget/* omit = */test* + */upgrades/* diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e2a85884 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.min.js binary diff --git a/.gitignore b/.gitignore index 2232c825..200dbdb5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,42 @@ +.coverage* *.egg-info +*.log *.mo -*.pyc -.installed.cfg -.mr.developer.cfg -.project -.pydevproject -.settings/ +*.py? +*.swp +# dirs bin/ +buildout-cache/ develop-eggs/ +eggs/ +htmlcov/ include/ lib/ +local/ +node_modules/ parts/ src/* -!src/collective/ +dist/* +test.plone_addon/ var/ -buildout-cache/ -htmlcov -.coverage -*.html -*.log +# files +.installed.cfg +.mr.developer.cfg +lib64 +log.html output.xml -*.swp -/.idea \ No newline at end of file +pip-selfcheck.json +report.html +.vscode/ +.tox/ +reports/ + +# excludes +!.coveragerc +!.editorconfig +!.gitattributes +!.gitignore +!.gitkeep +!.travis.yml +!src/collective +.python-version diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000..ee2b3f89 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,7 @@ +{ + "browser": true, + "globals": { + "$": false, + "window": false + } +} diff --git a/.travis.yml b/.travis.yml index 3c3efd34..ce2f1209 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,33 +1,93 @@ +dist: bionic language: python +sudo: false +cache: + pip: true + directories: + - eggs python: -- 2.7 + - "2.7" +matrix: + include: + - python: "2.7" + env: PLONE_VERSION=51 + - python: "2.7" + env: PLONE_VERSION=52 + - python: "3.7" + env: PLONE_VERSION=52 + sudo: true + fast_finish: true +before_install: + - mkdir webdriver; + wget https://chromedriver.storage.googleapis.com/2.40/chromedriver_linux64.zip; + unzip chromedriver_linux64.zip -d webdriver; + export PATH=$PATH:$(pwd)/webdriver; + - virtualenv -p `which python` . + - bin/pip install -r requirements.txt -c constraints_plone$PLONE_VERSION.txt + - cp test_plone$PLONE_VERSION.cfg buildout.cfg + install: -- mkdir -p buildout-cache/{eggs,downloads} -- python bootstrap.py -c travis.cfg -- bin/buildout -c travis.cfg -N -q -t 3 -- pip install coverage -- curl -O https://saucelabs.com/downloads/sc-4.4.11-linux.tar.gz -- tar xzvf sc-4.4.11-linux.tar.gz -- ./sc-4.4.11-linux/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY -i $TRAVIS_JOB_ID -f CONNECTED & -- JAVA_PID=$! -- bash -c "while [ ! -f CONNECTED ]; do sleep 2; done" + - bin/buildout -N -t 3 code-analysis:return-status-codes=True annotate + - bin/buildout -N -t 3 code-analysis:return-status-codes=True + +before_script: +- 'export DISPLAY=:99.0' +- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & +- sleep 3 + script: -- bin/test -- coverage run ./bin/test -t \!robot +# Run code-analysis, except on Python 3.6, which mysteriously fails to find zc.buildout. + - python --version 2> /dev/stdout | grep 3.6 || bin/code-analysis + - ROBOT_BROWSER=chrome bin/test --all + after_success: -- pip install coveralls -- coveralls -after_script: -- kill $JAVA_PID -notifications: - email: - - prive-ged@listes.entrouvert.com - - travis-reports@ecreall.com - - devs@imio.be -env: - global: - - secure: vUcL3IrMd2Wc2fEnrIR6jD/e1tqnNKiJHKy83EPJM08xLPUghDbx5VoGtfrIur4GN22Ov+nCIH3U6n4BMrsaIj7KGSrOS31tY+UDQHpiu9t03oCfg5e7wHi1/4tn6aVN5UZI1A8a4xJO4S+ziXkM/VbOk2Ln5Petpsck/c2I9ps= - - secure: coD6Bmb6pK+dfbUj49Rmi7XNj1QCGYTECmZvpRWeJs2HFaYceb6Bc+yJI2hFc06P6XUwUoPOS52rn6C/JOcOFXdCuwSSXxKBVgXdt9jDTDYRumbhUqEJfLLqd012gfs10cZDE09uH0W+FPpLaD3Ft71JLhf9WzjXwgO6EVxFp/8= - - ROBOT_BUILD_NUMBER=travis-$TRAVIS_BUILD_NUMBER - - ROBOT_REMOTE_URL=http://$SAUCE_USERNAME:$SAUCE_ACCESS_KEY@ondemand.saucelabs.com:80/wd/hub - - ROBOT_DESIRED_CAPABILITIES=tunnel-identifier:$TRAVIS_JOB_ID + - bin/createcoverage --output-dir=parts/test/coverage + - bin/pip install coverage + - bin/python -m coverage.pickle2json + - bin/pip install -q coveralls + - bin/coveralls + + + + + + + + + +#language: python +# python: +# - 2.7 +# install: +# - mkdir -p buildout-cache/{eggs,downloads} +# - pip install --upgrade pip +# - pip install -r requirements.txt +# - python bootstrap.py --version=2.11.4 -c travis.cfg +# - bin/buildout -c travis.cfg -N -q -t 3 +# - pip install coverage +# - curl -O https://saucelabs.com/downloads/sc-4.5.4-linux.tar.gz +# - tar xzvf sc-4.5.4-linux.tar.gz +# - ./sc-4.5.4-linux/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY -i $TRAVIS_JOB_ID -f CONNECTED & +# - JAVA_PID=$! +# - bash -c "while [ ! -f CONNECTED ]; do sleep 2; done" +# script: +# - bin/test +# - coverage run ./bin/test -t \!robot +# after_success: +# - pip install coveralls +# - coveralls +# after_script: +# - kill $JAVA_PID +# notifications: +# email: +# - prive-ged@listes.entrouvert.com +# - travis-reports@ecreall.com +# - devs@imio.be +# env: +# global: +# - secure: vUcL3IrMd2Wc2fEnrIR6jD/e1tqnNKiJHKy83EPJM08xLPUghDbx5VoGtfrIur4GN22Ov+nCIH3U6n4BMrsaIj7KGSrOS31tY+UDQHpiu9t03oCfg5e7wHi1/4tn6aVN5UZI1A8a4xJO4S+ziXkM/VbOk2Ln5Petpsck/c2I9ps= +# - secure: coD6Bmb6pK+dfbUj49Rmi7XNj1QCGYTECmZvpRWeJs2HFaYceb6Bc+yJI2hFc06P6XUwUoPOS52rn6C/JOcOFXdCuwSSXxKBVgXdt9jDTDYRumbhUqEJfLLqd012gfs10cZDE09uH0W+FPpLaD3Ft71JLhf9WzjXwgO6EVxFp/8= +# - ROBOT_BUILD_NUMBER=travis-$TRAVIS_BUILD_NUMBER +# - ROBOT_REMOTE_URL=http://$SAUCE_USERNAME:$SAUCE_ACCESS_KEY@ondemand.saucelabs.com:80/wd/hub +# - ROBOT_DESIRED_CAPABILITIES=tunnel-identifier:$TRAVIS_JOB_ID +# - ZSERVER_PORT=55001 \ No newline at end of file diff --git a/CHANGES.rst b/CHANGES.rst index c68d1351..2bd2f5af 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,11 +1,78 @@ Changelog ========= -1.24 (unreleased) +2.0 (unreleased) +---------------- + +- Add new `enterprise number` field for organization content type + [laulaz] +- Update for Plone 5.2 + [agitator] +- Use mockup for tooltips, modals and date widget. + This removes Plone 4 compatibility. + [thomasdesvenain] +- Do not show add link if content type is not allowed as directory subcontent. + [thomasdesvenain] +- Prevent address field from being erased if they are changed programmaticaly before any manual edition. + [thomasdesvenain] + +1.30 (unreleased) +----------------- + +- Avoid an error when we try to remove a working copy from plone.app.iterate + [mpeeters] +- Display `description` on the organization view. Field `description` may be + filled but was not displayed. + [gbastien] + +1.29 (unreleased) ----------------- -- Nothing changed yet. +- Removed overlay on heldposition actions in person view. + [sgeulette] +- Added option to display belowcontenttitle viewlet on contact views. + [sgeulette] +1.28 (unreleased) +----------------- + +- Ensure than export is unicode encoding. + [boulch] + + +1.27 (unreleased) +----------------- + +- Added contact_source metadata to be used in contact widget. + [sgeulette] +- If person details privacy is True, contact details on person don't search related items. + [sgeulette] + +1.26 (unreleased) +----------------- + +- Keep div and CSS id `viewlet-below-content-body` when rendering + `plone.belowcontentbody` viewlets on various views. + [gbastien] +- Extended `utils.get_gender_and_number` to manage parameters `use_by` and + `use_to` that will add new values to returned result prepended by + `'B'` or `'T'`. + [gbastien] +- Added email index + [sgeulette, daggelpop] + +1.25 (unreleased) +----------------- + +- Call `@@gender_person_title_mapping.json` from JS on `portal_url`. + [gbastien] + +1.24 (unreleased) +----------------- + +- Added method `held_position.get_label` to get the `held_position` label so it + is easy to override. + [gbastien] 1.23 (2018-11-20) ----------------- diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index f52d0e50..ddcf96f9 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -1,7 +1,11 @@ +Contributors +============ + - Gauthier Bastien, IMIO - Vincent Fretin, Ecreall - Stéphan Geulette, IMIO - Cédric Messiant, Ecreall - Frédéric Peters, Entr'ouvert - Thomas Desvenain, Ecreall - +- Peter Holzer, peter.holzer@agitator.com +- Laurent Lasudry, Affinitic diff --git a/DEVELOP.rst b/DEVELOP.rst new file mode 100644 index 00000000..15c691f4 --- /dev/null +++ b/DEVELOP.rst @@ -0,0 +1,41 @@ +Using the development buildout +============================== + +Create a virtualenv in the package:: + + $ virtualenv --clear . + +Install requirements with pip:: + + $ ./bin/pip install -r requirements.txt + +Run buildout:: + + $ ./bin/buildout + +Start Plone in foreground: + + $ ./bin/instance fg + + +Running tests +------------- + + $ tox + +list all tox environments: + + $ tox -l + py27-Plone51 + py27-Plone52 + py37-Plone52 + build_instance + code-analysis + lint-py27 + lint-py37 + coverage-report + +run a specific tox env: + + $ tox -e py37-Plone52 + diff --git a/LICENSE.GPL b/LICENSE.GPL new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/LICENSE.GPL @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 00000000..768b2ba5 --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,15 @@ +collective.contact.core Copyright 2020, Peter Holzer + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, +MA 02111-1307 USA. diff --git a/MANIFEST.in b/MANIFEST.in index b7b33abc..5a15789b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ -recursive-include src/collective * -recursive-include docs * -include * +graft src/collective +graft docs +include *.rst +global-exclude *.pyc exclude .installed.cfg exclude .mr.developer.cfg -global-exclude *pyc diff --git a/README.rst b/README.rst index 9371bf49..9106cca9 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,11 @@ This add-on is part of the ``collective.contact.*`` suite. For an overview and a A Plone add-on that provides a directory where you create persons, organizations, sub-organizations and positions. +Plone Version Compatibility +=========================== + +collective.contact.core 1.11 works with Plone 5 + How-to ====== @@ -41,6 +46,10 @@ The following configuration can be adapted in the plone registry (prefix=IContac Use held positions to search persons. * use_description_to_search_person : boolean, default to True. Use description to search persons. +* display_contact_photo_on_organization_view : boolean, default to True. + Display contact photo on organization view. +* contact_source_metadata_content : choice, default to get_full_title. + Choose information displayed after a search in contact widget. Localization ============ @@ -64,9 +73,21 @@ Installation ============ * Add collective.contact.core to your eggs. +* Under Plone 4, set collective.z3cform.datagridfield to 1.2.x * Re-run buildout. * Install the product in your plone site. + +IMPORTANT : Compatibility with collective.js.jqueryui +----------------------------------------------------- + +For now, collective.js.jqueryui is not compatible with plone.formwidget.autocomplete, +which is a dependency of collective.contact.core. +If collective.js.jqueryui is installed, you **must** disable jqueryui autocomplete feature +unless contact widget will not be functionnal. +You can disable the plugin in the JQuery UI configurations settings of site control panel. + + Tests ===== diff --git a/base.cfg b/base.cfg index badef9d1..29b78571 100644 --- a/base.cfg +++ b/base.cfg @@ -1,23 +1,116 @@ [buildout] -package-name = collective.contact.core -package-extras = [test] extends = - https://raw.github.com/collective/buildout.plonetest/master/test-4.3.x.cfg - https://raw.github.com/collective/buildout.plonetest/master/qa.cfg sources.cfg checkouts.cfg versions.cfg + +show-picked-versions = true extensions = mr.developer -show-picked-versions = true -[test] -eggs += - ipdb +index = https://pypi.python.org/simple/ + +parts = + instance + test + code-analysis + coverage + test-coverage + createcoverage + releaser + i18ndude + omelette + robot + plone-helper-scripts +develop = . + + +[instance] +recipe = plone.recipe.zope2instance +user = admin:admin +http-address = 8080 +environment-vars = + zope_i18n_compile_mo_files true +eggs = + Plone + Pillow + collective.contact.core [test] + [code-analysis] recipe = plone.recipe.codeanalysis -directory = src/collective -# ignore lines too long and indentation warnings -# todo: remove C901,F401 -flake8-ignore = E123,E124,E402,E501,E126,E127,E128,C901,F401 +directory = ${buildout:directory}/src/collective +return-status-codes = False + + +[omelette] +recipe = collective.recipe.omelette +eggs = ${instance:eggs} + + +[test] +recipe = zc.recipe.testrunner +eggs = ${instance:eggs} +initialization = + os.environ['TZ'] = 'UTC' +defaults = ['-s', 'collective.contact.core', '--auto-color', '--auto-progress'] + + +[coverage] +recipe = zc.recipe.egg +eggs = coverage + + +[test-coverage] +recipe = collective.recipe.template +input = inline: + #!/bin/bash + export TZ=UTC + ${buildout:directory}/bin/coverage run bin/test $* + ${buildout:directory}/bin/coverage html + ${buildout:directory}/bin/coverage report -m --fail-under=90 + # Fail (exit status 1) if coverage returns exit status 2 (this happens + # when test coverage is below 100%. +output = ${buildout:directory}/bin/test-coverage +mode = 755 + + +[createcoverage] +recipe = zc.recipe.egg +eggs = createcoverage + + +[robot] +recipe = zc.recipe.egg +eggs = + ${test:eggs} + plone.app.robotframework[debug,reload] + + +[releaser] +recipe = zc.recipe.egg +eggs = zest.releaser + + +[i18ndude] +recipe = zc.recipe.egg +eggs = i18ndude + +[plone-helper-scripts] +recipe = zc.recipe.egg +eggs = + Products.CMFPlone + ${instance:eggs} +interpreter = zopepy +scripts = + zopepy + plone-compile-resources + +[versions] +# Don't use a released version of collective.contact.core +collective.contact.core = + + + + + diff --git a/bootstrap.py b/bootstrap.py index ed57894b..1f59b213 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -25,7 +25,10 @@ from optparse import OptionParser -tmpeggs = tempfile.mkdtemp() +__version__ = '2015-07-01' +# See zc.buildout's changelog if this version is up to date. + +tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] @@ -35,18 +38,19 @@ Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. -Note that by using --find-links to point to local resources, you can keep +Note that by using --find-links to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) -parser.add_option("-v", "--version", help="use a specific zc.buildout version") - +parser.add_option("--version", + action="store_true", default=False, + help=("Return bootstrap.py version.")) parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, - help=("Normally, if you do not specify a --version, the " - "bootstrap script and buildout gets the newest " + help=("Normally, if you do not specify a --buildout-version, " + "the bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " @@ -59,36 +63,57 @@ parser.add_option("--allow-site-packages", action="store_true", default=False, help=("Let bootstrap.py use existing site packages")) - +parser.add_option("--buildout-version", + help="Use a specific zc.buildout version") +parser.add_option("--setuptools-version", + help="Use a specific setuptools version") +parser.add_option("--setuptools-to-dir", + help=("Allow for re-use of existing directory of " + "setuptools versions")) options, args = parser.parse_args() +if options.version: + print("bootstrap.py version %s" % __version__) + sys.exit(0) + ###################################################################### # load/install setuptools try: - if options.allow_site_packages: - import setuptools - import pkg_resources from urllib.request import urlopen except ImportError: from urllib2 import urlopen ez = {} -exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) +if os.path.exists('ez_setup.py'): + exec(open('ez_setup.py').read(), ez) +else: + exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) if not options.allow_site_packages: # ez_setup imports site, which adds site packages - # this will remove them from the path to ensure that incompatible versions + # this will remove them from the path to ensure that incompatible versions # of setuptools are not in the path import site - # inside a virtualenv, there is no 'getsitepackages'. + # inside a virtualenv, there is no 'getsitepackages'. # We can't remove these reliably if hasattr(site, 'getsitepackages'): for sitepackage_path in site.getsitepackages(): - sys.path[:] = [x for x in sys.path if sitepackage_path not in x] + # Strip all site-packages directories from sys.path that + # are not sys.prefix; this is because on Windows + # sys.prefix is a site-package directory. + if sitepackage_path != sys.prefix: + sys.path[:] = [x for x in sys.path + if sitepackage_path not in x] setup_args = dict(to_dir=tmpeggs, download_delay=0) + +if options.setuptools_version is not None: + setup_args['version'] = options.setuptools_version +if options.setuptools_to_dir is not None: + setup_args['to_dir'] = options.setuptools_to_dir + ez['use_setuptools'](**setup_args) import setuptools import pkg_resources @@ -104,7 +129,12 @@ ws = pkg_resources.working_set +setuptools_path = ws.find( + pkg_resources.Requirement.parse('setuptools')).location + +# Fix sys.path here as easy_install.pth added before PYTHONPATH cmd = [sys.executable, '-c', + 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + 'from setuptools.command.easy_install import main; main()', '-mZqNxd', tmpeggs] @@ -117,21 +147,23 @@ if find_links: cmd.extend(['-f', find_links]) -setuptools_path = ws.find( - pkg_resources.Requirement.parse('setuptools')).location - requirement = 'zc.buildout' -version = options.version +version = options.buildout_version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): - for part in parsed_version: - if (part[:1] == '*') and (part not in _final_parts): - return False - return True + try: + return not parsed_version.is_prerelease + except AttributeError: + # Older setuptools + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + index = setuptools.package_index.PackageIndex( search_path=[setuptools_path]) if find_links: @@ -156,7 +188,7 @@ def _final_version(parsed_version): cmd.append(requirement) import subprocess -if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: +if subprocess.call(cmd) != 0: raise Exception( "Failed to execute command:\n%s" % repr(cmd)[1:-1]) diff --git a/buildout.cfg b/buildout.cfg index e1834a52..c5a74dc6 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,6 +1,14 @@ [buildout] + +# use this extend one of the buildout configuration: extends = - base.cfg -parts += - createcoverage -sources = sources +# -*- mrbob: extra extends -*- +# test_plone50.cfg + test_plone51.cfg +# test_plone52.cfg + +[instance] +eggs += + pdbpp + Products.PDBDebugMode + robotframework-debuglibrary \ No newline at end of file diff --git a/checkouts.cfg b/checkouts.cfg index 787d8288..c9e1666a 100644 --- a/checkouts.cfg +++ b/checkouts.cfg @@ -1,8 +1,16 @@ [buildout] extends = - https://raw.github.com/plone/buildout.coredev/4.3/sources.cfg + https://raw.github.com/plone/buildout.coredev/5.2/sources.cfg +sources-dir = ${buildout:directory}/devsrc always-checkout = force auto-checkout += plone.formwidget.masterselect collective.contact.widget collective.excelexport + mockup + +[sources] +mockup = git https://github.com/plone/mockup.git branch=master +#collective.contact.widget = git https://github.com/collective/collective.contact.widget.git branch=plone5-mockup +collective.contact.widget = git https://github.com/collective/collective.contact.widget.git branch=plone5-mockup-degrok +# collective.contact.widget = git https://github.com/collective/collective.contact.widget.git branch=plone5-mockup-autocomplete diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 00000000..24cbf877 --- /dev/null +++ b/constraints.txt @@ -0,0 +1 @@ +-c constraints_plone52.txt diff --git a/constraints_plone50.txt b/constraints_plone50.txt new file mode 100644 index 00000000..9852be62 --- /dev/null +++ b/constraints_plone50.txt @@ -0,0 +1,3 @@ +-c https://dist.plone.org/release/5.0-latest/requirements.txt +# setuptools==33.1.1 +# zc.buildout==2.9.5 diff --git a/constraints_plone51.txt b/constraints_plone51.txt new file mode 100644 index 00000000..8dcbff18 --- /dev/null +++ b/constraints_plone51.txt @@ -0,0 +1,3 @@ +-c https://dist.plone.org/release/5.1-latest/requirements.txt +# setuptools==39.1.0 +# zc.buildout==2.11.4 diff --git a/constraints_plone52.txt b/constraints_plone52.txt new file mode 100644 index 00000000..d96fee01 --- /dev/null +++ b/constraints_plone52.txt @@ -0,0 +1,3 @@ +-c https://dist.plone.org/release/5.2-latest/requirements.txt +# setuptools==40.2.0 +# zc.buildout==2.12.2 diff --git a/development.cfg b/development.cfg index e68a1a24..624cda49 100644 --- a/development.cfg +++ b/development.cfg @@ -5,7 +5,7 @@ parts += omelette robot eggs += - ipdb + pdbpp sources = sources [omelette] diff --git a/package.json b/package.json new file mode 100644 index 00000000..bd9351ac --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "collective.contact.core", + "version": "1.0.0", + "description": ".. contents::", + "main": "index.js", + "directories": { + "doc": "docs" + }, + "scripts": { + "minify": "uglifyjs src/collective/contact/core/browser/static/forms.js > src/collective/contact/core/browser/static/forms.min.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/collective/collective.contact.core.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/collective/collective.contact.core/issues" + }, + "homepage": "https://github.com/collective/collective.contact.core#readme", + "dependencies": { + "uglify-js": "^3.4.9" + } +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..fa2f6147 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +-c constraints_plone52.txt +setuptools +zc.buildout diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..e920041b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,19 @@ +[check-manifest] +ignore = + *.cfg + .coveragerc + .editorconfig + .gitattributes + +[isort] +# for details see +# http://docs.plone.org/develop/styleguide/python.html#grouping-and-sorting +force_alphabetical_sort = True +force_single_line = True +lines_after_imports = 2 +line_length = 200 +not_skip = __init__.py + +[flake8] +exclude = bootstrap.py,docs,*.egg.,omelette +max-complexity = 15 diff --git a/setup.py b/setup.py index 8cf9b922..1677e7e1 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,8 @@ # -*- coding: utf8 -*- -from setuptools import setup, find_packages +from setuptools import find_packages +from setuptools import setup + long_description = ( open('README.rst').read() @@ -14,7 +16,7 @@ + '\n') setup(name='collective.contact.core', - version='1.24.dev0', + version='2.0.dev0', description="Core package for collective.contact add-ons", long_description=long_description, # Get more strings from @@ -22,31 +24,33 @@ classifiers=[ "Environment :: Web Environment", "Framework :: Plone", - "Framework :: Plone :: 4.2", - "Framework :: Plone :: 4.3", + "Framework :: Plone :: 5.0", + "Framework :: Plone :: 5.1", + "Framework :: Plone :: 5.2", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules", ], - keywords='', + keywords='plone contact management organization person position', author='"Cedric Messiant"', author_email='cedricmessiant@ecreall.com', - url='http://svn.plone.org/svn/collective/', - license='gpl', + url='https://github.com/collective/collective.contact.core', + license='GPL version 2', packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['collective', 'collective.contact'], include_package_data=True, zip_safe=False, install_requires=[ + 'six', 'ExtensionClass', 'collective.z3cform.datagridfield', 'collective.contact.widget > 1.2.2', 'setuptools', - 'ecreall.helpers.upgrade >= 1.1.6.dev0', - 'five.grok', 'five.globalrequest', 'plone.api>=1.4.11', 'plone.app.dexterity', @@ -54,7 +58,6 @@ 'plone.app.relationfield', 'plone.app.textfield!=1.2.8', 'plone.autoform', - 'plone.formwidget.datetime', 'plone.formwidget.masterselect>=1.3', 'plone.supermodel', 'Products.CMFPlone', @@ -63,7 +66,9 @@ ], extras_require={ 'test': ['plone.app.testing', - 'plone.app.robotframework', + 'plone.testing>=5.0.0', + 'plone.app.contenttypes', + 'plone.app.robotframework[debug]', 'ecreall.helpers.testing', ], }, diff --git a/sources.cfg b/sources.cfg index 438bed72..9a1564f7 100644 --- a/sources.cfg +++ b/sources.cfg @@ -1,5 +1,9 @@ [buildout] -extends = https://raw.github.com/plone/buildout.coredev/4.3/sources.cfg +extends = https://raw.github.com/plone/buildout.coredev/5.2/sources.cfg + +auto-checkout += + plone.formwidget.masterselect + plone.formwidget.contenttree [remotes] tdesvenain = git://github.com/tdesvenain @@ -12,9 +16,10 @@ entrouvert = git://repos.entrouvert.org entrouvert_push = git+ssh://git@repos.entrouvert.org [sources] -collective.contact.core = git ${remotes:collective}/collective.contact.core.git pushurl=${remotes:collective_push}/collective.contact.core.git -collective.contact.facetednav = git ${remotes:collective}/collective.contact.facetednav.git pushurl=${remotes:collective_push}/collective.contact.facetednav.git -collective.contact.widget = git ${remotes:collective}/collective.contact.widget.git pushurl=${remotes:collective_push}/collective.contact.widget.git -plone.formwidget.masterselect = git ${remotes:collective}/plone.formwidget.masterselect.git pushurl=${remotes:collective_push}/plone.formwidget.masterselect.git +collective.contact.core = git ${remotes:collective}/collective.contact.core.git pushurl=${remotes:collective_push}/collective.contact.core.git branch=plone5 +collective.contact.facetednav = git ${remotes:collective}/collective.contact.facetednav.git pushurl=${remotes:collective_push}/collective.contact.facetednav.git branch=plone5 +collective.contact.widget = git ${remotes:collective}/collective.contact.widget.git pushurl=${remotes:collective_push}/collective.contact.widget.git branch=plone5 +plone.formwidget.masterselect = git ${remotes:collective}/plone.formwidget.masterselect.git pushurl=${remotes:collective_push}/plone.formwidget.masterselect.git branch=python3branch ecreall.helpers.upgrade = git ${remotes:tdesvenain}/ecreall.helpers.upgrade.git pushurl=${remotes:tdesvenain_push}/ecreall.helpers.upgrade.git collective.excelexport = git ${remotes:collective}/collective.excelexport.git pushurl=${remotes:collective_push}/collective.excelexport.git +plone.formwidget.contenttree = git ${remotes:plone}/plone.formwidget.contenttree.git pushurl=${remotes:plone_push}/plone.formwidget.contenttree.git branch=python3 diff --git a/src/collective/contact/core/__init__.py b/src/collective/contact/core/__init__.py index 6bd5e916..0b33d70d 100644 --- a/src/collective/contact/core/__init__.py +++ b/src/collective/contact/core/__init__.py @@ -1,6 +1,8 @@ from zope.i18nmessageid import MessageFactory import logging + + logger = logging.getLogger('collective.contact.core') _ = MessageFactory("collective.contact.core") diff --git a/src/collective/contact/core/adapters.py b/src/collective/contact/core/adapters.py index c94aa7ca..a1ae92c1 100644 --- a/src/collective/contact/core/adapters.py +++ b/src/collective/contact/core/adapters.py @@ -1,18 +1,14 @@ -import datetime -import vobject - -from zope.interface import Interface, implements -from five import grok - -from Products.CMFPlone.utils import safe_unicode +from collective.contact.core.behaviors import IBirthday +from collective.contact.core.content.organization import IOrganization +from collective.contact.core.interfaces import IContactable +from collective.contact.core.interfaces import IHeldPosition +from collective.contact.core.interfaces import IPersonHeldPositions from plone import api +from Products.CMFPlone.utils import safe_unicode +from zope.interface import implementer -from collective.contact.core.interfaces import IVCard, IContactable,\ - IPersonHeldPositions, IHeldPosition -from collective.contact.core.content.held_position import HeldPosition -from collective.contact.core.content.organization import IOrganization,\ - Organization -from collective.contact.core.behaviors import IBirthday +import datetime +import vobject class ContactableVCard: @@ -73,26 +69,23 @@ def get_vcard(self): return vcard -class ContactDetailsVCard(grok.Adapter, ContactableVCard): - grok.context(Interface) - grok.provides(IVCard) +class ContactDetailsVCard(ContactableVCard): def __init__(self, context): self.context = context def get_vcard(self): vcard = ContactableVCard.get_vcard(self) + title = safe_unicode(self.context.Title(), encoding='utf8') vcard.add('fn') - vcard.fn.value = self.context.Title() + vcard.fn.value = title vcard.add('n') - vcard.n.value = vobject.vcard.Name(self.context.Title()) + vcard.n.value = vobject.vcard.Name(title) return vcard -class HeldPositionVCard(grok.Adapter, ContactableVCard): - grok.implements(IHeldPosition) - grok.context(HeldPosition) - grok.provides(IVCard) +@implementer(IHeldPosition) +class HeldPositionVCard(ContactableVCard): def __init__(self, context): self.context = context @@ -146,10 +139,8 @@ def get_vcard(self): return vcard -class OrganizationVCard(grok.Adapter, ContactableVCard): - grok.implements(IOrganization) - grok.context(Organization) - grok.provides(IVCard) +@implementer(IOrganization) +class OrganizationVCard(ContactableVCard): def __init__(self, context): self.context = context @@ -169,20 +160,8 @@ def get_vcard(self): return vcard -def sort_closed_positions(position1, position2): - if position1.end_date == position2.end_date: - return 0 - elif not position1.end_date: - # position without end date is greater - return 1 - elif not position2.end_date: - return -1 - else: - return cmp(position1.end_date, position2.end_date) - - +@implementer(IPersonHeldPositions) class PersonHeldPositionsAdapter(object): - implements(IPersonHeldPositions) def __init__(self, person): self.person = person @@ -213,8 +192,13 @@ def get_closed_positions(self): """ all_positions = self.person.get_held_positions() active_positions = self.get_current_positions() - closed_positions = [p for p in all_positions if p not in active_positions] - closed_positions.sort(cmp=sort_closed_positions, reverse=True) + closed_positions = [ + p for p in all_positions if p not in active_positions] + closed_positions = sorted( + closed_positions, + key=lambda i: i.get('end_date', datetime.date(2100, 1, 1)), + reverse=True + ) return tuple(closed_positions) def get_sorted_positions(self): diff --git a/src/collective/contact/core/behaviors.py b/src/collective/contact/core/behaviors.py index 3a933578..decafa5d 100644 --- a/src/collective/contact/core/behaviors.py +++ b/src/collective/contact/core/behaviors.py @@ -1,30 +1,32 @@ -import re -import datetime - +from Acquisition import aq_base +from collective.contact.core import _ +from collective.contact.core.interfaces import IContactable +from collective.contact.widget.schema import ContactChoice +from collective.contact.widget.schema import ContactList +from collective.contact.widget.source import ContactSourceBinder +from plone.app.dexterity.browser.types import TypeSchemaContext +from plone.app.textfield import RichText +from plone.app.z3cform.widget import DateFieldWidget +from plone.autoform import directives as form +from plone.autoform.interfaces import IFormFieldProvider +from plone.formwidget.masterselect import MasterSelectBoolField +from plone.supermodel import model +from plone.supermodel.directives import fieldset +from z3c.form.widget import ComputedWidgetAttribute +from zope import schema from zope.interface import alsoProvides from zope.interface import Interface -from zope import schema -from Acquisition import aq_base -from z3c.form.widget import ComputedWidgetAttribute -from z3c.form.widget import FieldWidget -from plone.supermodel import model +import re -from plone.supermodel.directives import fieldset -from plone.autoform.interfaces import IFormFieldProvider -from plone.autoform import directives as form -from plone.formwidget.masterselect import MasterSelectBoolField -from plone.formwidget.datetime.z3cform import DateWidget -from plone.app.textfield import RichText -from plone.app.dexterity.browser.types import TypeSchemaContext -from Products.CMFDefault.utils import checkEmailAddress -from Products.CMFDefault.exceptions import EmailAddressInvalid +# from Products.CMFDefault.utils import checkEmailAddress +# from Products.CMFDefault.exceptions import EmailAddressInvalid -from collective.contact.core import _ -from collective.contact.core.interfaces import IContactable -from collective.contact.widget.schema import ContactChoice, ContactList -from collective.contact.widget.source import ContactSourceBinder + +# Taken from http://www.regular-expressions.info/email.html +_isemail = r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}" +_isemail = re.compile(_isemail).match class InvalidEmailAddress(schema.ValidationError): @@ -32,13 +34,21 @@ class InvalidEmailAddress(schema.ValidationError): __doc__ = _(u"Invalid email address") -def validateEmail(value): - """Simple email validator""" - try: - checkEmailAddress(value) - except EmailAddressInvalid: - raise InvalidEmailAddress(value) - return True +# def validateEmail(value): +# super(Email, self)._validate(value) +# if _isemail(value): +# return + +# raise InvalidEmail(value) + + +# def validateEmail(value): +# """Simple email validator""" +# try: +# checkEmailAddress(value) +# except EmailAddressInvalid: +# raise InvalidEmailAddress(value) +# return True class InvalidPhone(schema.ValidationError): @@ -76,37 +86,38 @@ class IGlobalPositioning(model.Schema): 'global_positioning', label=_(u'Global positioning'), fields=('latitude', 'longitude') - ) + ) latitude = schema.Float( - title=_('Latitude'), - description=_('Latitude'), - min=-90.0, - max=90.0, - required=False, - ) + title=_('Latitude'), + description=_('Latitude'), + min=-90.0, + max=90.0, + required=False, + ) longitude = schema.Float( - title=_('Longitude'), - description=_('Longitude'), - min=-90.0, - max=90.0, - required=False, - ) + title=_('Longitude'), + description=_('Longitude'), + min=-90.0, + max=90.0, + required=False, + ) + alsoProvides(IGlobalPositioning, IFormFieldProvider) # must stay a list so it can be patched ADDRESS_FIELDS = [ - 'street', - 'number', - 'additional_address_details', - 'zip_code', - 'city', - 'region', - 'country', - ] + 'street', + 'number', + 'additional_address_details', + 'zip_code', + 'city', + 'region', + 'country', +] # must stay a list so it can be patched @@ -116,61 +127,62 @@ class IGlobalPositioning(model.Schema): CONTACT_DETAILS_FIELDS = ( - 'phone', - 'cell_phone', - 'fax', - 'email', - 'im_handle', - 'website', - ) + 'phone', + 'cell_phone', + 'fax', + 'email', + 'im_handle', + 'website', +) class IContactDetails(model.Schema): """Contact details behavior""" - form.write_permission(use_parent_address='collective.contact.core.UseParentAddress') + form.write_permission( + use_parent_address='collective.contact.core.UseParentAddress') fieldset( 'contact_details', label=_(u'Contact details'), fields=CONTACT_DETAILS_FIELDS - ) + ) fieldset( 'address', label=_(u'Address'), fields=ADDRESS_FIELDS_PLUS_PARENT - ) + ) email = schema.TextLine( title=_(u"Email"), - constraint=validateEmail, + # constraint=validateEmail, required=False, - ) + ) phone = schema.TextLine( title=_(u"Phone"), required=False, constraint=validatePhone, - ) + ) cell_phone = schema.TextLine( title=_(u"Cell phone"), required=False, - ) + ) fax = schema.TextLine( title=_(u"Fax"), required=False, - ) + ) website = schema.TextLine( title=_(u"Website"), required=False, - ) + ) im_handle = schema.TextLine( title=_('Instant messenger handle'), required=False, - ) + ) use_parent_address = MasterSelectBoolField( title=_("Use the belonging entity address"), @@ -180,50 +192,50 @@ class IContactDetails(model.Schema): 'action': 'show', 'hide_values': 0, 'siblings': True, - }, + }, {'masterSelector': '#form-widgets-IContactDetails-use_parent_address-0, #oform-widgets-use_parent_address-0', 'name': 'region', 'action': 'show', 'hide_values': 0, 'siblings': True, - }, + }, {'masterSelector': '#form-widgets-IContactDetails-use_parent_address-0, #oform-widgets-use_parent_address-0', 'name': 'zip_code', 'action': 'show', 'hide_values': 0, 'siblings': True, - }, + }, {'masterSelector': '#form-widgets-IContactDetails-use_parent_address-0, #oform-widgets-use_parent_address-0', 'name': 'city', 'action': 'show', 'hide_values': 0, 'siblings': True, - }, + }, {'masterSelector': '#form-widgets-IContactDetails-use_parent_address-0, #oform-widgets-use_parent_address-0', 'name': 'number', 'action': 'show', 'hide_values': 0, 'siblings': True, - }, + }, {'masterSelector': '#form-widgets-IContactDetails-use_parent_address-0, #oform-widgets-use_parent_address-0', 'name': 'street', 'action': 'show', 'hide_values': 0, 'siblings': True, - }, + }, {'masterSelector': '#form-widgets-IContactDetails-use_parent_address-0, #oform-widgets-use_parent_address-0', 'name': 'additional_address_details', 'action': 'show', 'hide_values': 0, 'siblings': True, - }, + }, {'masterSelector': '#form-widgets-IContactDetails-use_parent_address-0, #oform-widgets-use_parent_address-0', 'name': 'parent_address', 'action': 'hide', 'hide_values': 0, 'siblings': True, - }, + }, ), default=True, required=False, @@ -233,43 +245,44 @@ class IContactDetails(model.Schema): default_mime_type='text/html', output_mime_type='text/html', required=False, - ) + ) form.mode(parent_address='display') country = schema.TextLine( title=_('Country'), required=False, - ) + ) zip_code = schema.TextLine( title=_('Zip Code'), required=False, - ) + ) city = schema.TextLine( title=_('City'), required=False, - ) + ) street = schema.TextLine( title=_('Street'), required=False, - ) + ) number = schema.TextLine( title=_('Number'), required=False, - ) + ) region = schema.TextLine( - title=_('Region'), - required=False, - ) + title=_('Region'), + required=False, + ) additional_address_details = schema.TextLine( - title=_('Additional address details'), - required=False, - ) + title=_('Additional address details'), + required=False, + ) + alsoProvides(IContactDetails, IFormFieldProvider) @@ -287,15 +300,15 @@ def default_use_parent_address(adapter): try: parent_type = parent.portal_type - except: + except Exception: # in schema editor return False if parent_type == 'person': return False elif parent_type == 'organization' \ - and not IOrganization.providedBy(adapter.context) \ - and not IPosition.providedBy(adapter.context): + and not IOrganization.providedBy(adapter.context) \ + and not IPosition.providedBy(adapter.context): return False else: return True @@ -311,22 +324,14 @@ def default_use_parent_address(adapter): field=IContactDetails['parent_address'], view=Interface) -def DateFieldWidget(field, request): - """IFieldWidget factory for DatetimeWidget.""" - widget = FieldWidget(field, DateWidget(request)) - currentYear = datetime.date.today().year - minimumYearRange = currentYear - 1900 # don't display dates before 1900 - widget.years_range = (-minimumYearRange, 1) - return widget - - class IBirthday(model.Schema): form.widget(birthday=DateFieldWidget) birthday = schema.Date( title=_("Birthday"), required=False, - ) + ) + alsoProvides(IBirthday, IFormFieldProvider) @@ -339,14 +344,15 @@ class IRelatedOrganizations(model.Schema): 'related_organizations', label=_(u'Related organizations'), fields=('related_organizations',), - ) + ) related_organizations = ContactList( - value_type=ContactChoice( - description=_("Search and attach organizations related to this one"), - source=ContactSourceBinder(portal_type=("organization",)),), - required=False, - addlink=False, + value_type=ContactChoice( + description=_( + "Search and attach organizations related to this one"), + source=ContactSourceBinder(portal_type=("organization",)),), + required=False, + addlink=False, ) diff --git a/src/collective/contact/core/behaviors.zcml b/src/collective/contact/core/behaviors.zcml index 7cb5f056..0976ba57 100644 --- a/src/collective/contact/core/behaviors.zcml +++ b/src/collective/contact/core/behaviors.zcml @@ -2,13 +2,10 @@ xmlns="http://namespaces.zope.org/zope" xmlns:plone="http://namespaces.plone.org/plone" xmlns:i18n="http://namespaces.zope.org/i18n" - xmlns:grok="http://namespaces.zope.org/grok" i18n_domain="collective.contact.core"> - - - + ' + row[1] }""" +return ' ' + row[1] }""" % ( + getSite().absolute_url()) } +@implementer(IContentProvider) class MasterSelectAddContactProvider(BrowserView): - implements(IContentProvider) def __init__(self, context, request, view): super(MasterSelectAddContactProvider, self).__init__(context, request) @@ -214,7 +239,7 @@ def render(self): add_text = addneworga.data('pbo').original_text; } else { // update add new orga link to add sub orga - add_organization_url = portal_url + orga.path + '/++add++organization'; + add_organization_url = "%s" + orga.path + '/++add++organization'; add_text = addneworga.data('pbo').original_text + ' dans ' + orga.title; } addneworga.data('pbo').src = add_organization_url; @@ -229,7 +254,7 @@ def render(self): .setOptions({extraParams: {path: orga.token}}).flushCache(); // update add new position url - var add_position_url = portal_url + orga.path + '/++add++position'; + var add_position_url = "%s" + orga.path + '/++add++position'; o.find('#oform-widgets-position-autocomplete .addnew').each(function(){ jQuery(this).data('pbo').src = add_position_url; }) @@ -261,59 +286,66 @@ def render(self): }); -""" % str(bool(getattr(self.__parent__.form, 'schema', None) == IAddHeldPosition)).lower() +""" % ( + str(bool(getattr(self.__parent__.form, 'schema', None) == IAddHeldPosition)).lower(), + getSite().absolute_url(), + getSite().absolute_url(), + ) class IAddHeldPosition(model.Schema): - """Schema to add held position Organization and person fields are required.""" organization = ContactChoice( - title=_(u"Organization"), - required=True, - description=_(u"Select the organization where the person holds the position"), - source=ContactSourceBinder(portal_type="organization")) + title=_(u"Organization"), + required=True, + description=_( + u"Select the organization where the person holds the position"), + source=ContactSourceBinder(portal_type="organization")) person = ContactChoice( - title=_(u"Person"), - description=_(u"Select the person who holds the position"), - required=True, - source=ContactSourceBinder(portal_type="person")) + title=_(u"Person"), + description=_(u"Select the person who holds the position"), + required=True, + source=ContactSourceBinder(portal_type="person")) position = ContactChoice( - title=_(u"Position"), - required=False, - description=_(u"Select the position held by this person in the selected organization"), - source=ContactSourceBinder(portal_type="position")) + title=_(u"Position"), + required=False, + description=_( + u"Select the position held by this person in the selected organization"), + source=ContactSourceBinder(portal_type="position")) class IAddContact(model.Schema): - """Schema to add held position, person or organization Fields are not required.""" organization = ContactChoice( - title=_(u"Organization"), - required=False, - description=_(u"Select the organization where the person holds the position"), - source=ContactSourceBinder(portal_type="organization")) + title=_(u"Organization"), + required=False, + description=_( + u"Select the organization where the person holds the position"), + source=ContactSourceBinder(portal_type="organization")) person = ContactChoice( - title=_(u"Person"), - description=_(u"Select the person who holds the position"), - required=False, - source=ContactSourceBinder(portal_type="person")) + title=_(u"Person"), + description=_(u"Select the person who holds the position"), + required=False, + source=ContactSourceBinder(portal_type="person")) position = ContactChoice( - title=_(u"Position"), - required=False, - description=_(u"Select the position held by this person in the selected organization"), - source=ContactSourceBinder(portal_type="position")) + title=_(u"Position"), + required=False, + description=_( + u"Select the position held by this person in the selected organization"), + source=ContactSourceBinder(portal_type="position")) +@implementer(IFieldsAndContentProvidersForm) class AddContact(DefaultAddForm, form.AddForm): """ The following is possible with this AddContact form: @@ -325,12 +357,12 @@ class AddContact(DefaultAddForm, form.AddForm): It's for this case we want no required errors in the form if the IHeldPosition required fields are not filled. """ - implements(IFieldsAndContentProvidersForm) contentProviders = ContentProviders(['organization-ms']) -# contentProviders['organization-ms'] = MasterSelectAddContactProvider + # contentProviders['organization-ms'] = MasterSelectAddContactProvider contentProviders['organization-ms'].position = -1 label = _(u"Create ${name}", mapping={'name': _(u"Contact")}) - description = _(u"A contact is a position held by a person in an organization") + description = _( + u"A contact is a position held by a person in an organization") schema = IAddContact portal_type = 'held_position' prefix = 'oform' @@ -365,6 +397,7 @@ def updateWidgets(self): self.widgets['parent_address'].mode = DISPLAY_MODE def update(self): + alsoProvides(self.request, IDeferSecurityCheck) super(AddContact, self).update() @button.buttonAndHandler(_('Add'), name='save') @@ -427,7 +460,6 @@ def add(self, obj): class AddHeldPosition(AddContact): - """Add an held position.""" schema = IAddHeldPosition @@ -437,7 +469,7 @@ class AddContactFromOrganization(AddContact): def updateWidgets(self): if 'oform.widgets.organization' not in self.request.form: self.request.form['oform.widgets.organization'] = '/'.join( - self.context.getPhysicalPath()) + self.context.getPhysicalPath()) super(AddContactFromOrganization, self).updateWidgets() @@ -446,17 +478,17 @@ def updateWidgets(self): organization = self.context.get_organization() if 'oform.widgets.organization' not in self.request.form: self.request.form['oform.widgets.organization'] = '/'.join( - organization.getPhysicalPath()) + organization.getPhysicalPath()) if 'oform.widgets.position' not in self.request.form: self.request.form['oform.widgets.position'] = '/'.join( - self.context.getPhysicalPath()) + self.context.getPhysicalPath()) super(AddContactFromPosition, self).updateWidgets() +@implementer(IFieldsAndContentProvidersForm) class AddOrganization(form.AddForm): - implements(IFieldsAndContentProvidersForm) contentProviders = ContentProviders(['organization-ms']) contentProviders['organization-ms'].position = 2 label = _(u"Create ${name}", mapping={'name': _(u"organization/position")}) @@ -467,9 +499,9 @@ class AddOrganization(form.AddForm): def updateWidgets(self): super(AddOrganization, self).updateWidgets() self.widgets['organization'].label = _( - 'help_add_organization_or_position_organization', - "Please fill the organization first " - "and then eventually select position") + 'help_add_organization_or_position_organization', + "Please fill the organization first " + "and then eventually select position") @button.buttonAndHandler(_('Add'), name='save') def handleAdd(self, action): diff --git a/src/collective/contact/core/browser/address.py b/src/collective/contact/core/browser/address.py index 1e373313..d28a6c07 100644 --- a/src/collective/contact/core/browser/address.py +++ b/src/collective/contact/core/browser/address.py @@ -1,21 +1,18 @@ from Acquisition import aq_base - -from five import grok -from plone import api - -from collective.contact.core.behaviors import IContactDetails -from collective.contact.core.browser import TEMPLATES_DIR from collective.contact.core.behaviors import ADDRESS_FIELDS -from collective.contact.core.interfaces import IHeldPosition, IContactCoreParameters - -grok.templatedir(TEMPLATES_DIR) +from collective.contact.core.behaviors import IContactDetails +from collective.contact.core.interfaces import IContactCoreParameters +from collective.contact.core.interfaces import IHeldPosition +from plone import api +from Products.Five import BrowserView def get_address(obj): """Returns a dictionary which contains address fields""" if aq_base(obj).use_parent_address is True: related = None - priv = api.portal.get_registry_record(name='person_contact_details_private', interface=IContactCoreParameters) + priv = api.portal.get_registry_record( + name='person_contact_details_private', interface=IContactCoreParameters) if IHeldPosition.providedBy(obj) and priv: # For a held position, we use the related element: a position or an organization related = (obj.get_position() or obj.get_organization()) @@ -41,11 +38,7 @@ def get_address(obj): return address -class Address(grok.View): - grok.name('address') - grok.context(IContactDetails) - grok.require("zope2.View") - grok.template('address') +class Address(BrowserView): def namespace(self): return get_address(self.context) diff --git a/src/collective/contact/core/browser/basefields/configure.zcml b/src/collective/contact/core/browser/basefields/configure.zcml new file mode 100644 index 00000000..5848605c --- /dev/null +++ b/src/collective/contact/core/browser/basefields/configure.zcml @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/src/collective/contact/core/browser/basefields/templates/held_position.pt b/src/collective/contact/core/browser/basefields/templates/held_position.pt index 24b9d5c6..0c292e87 100644 --- a/src/collective/contact/core/browser/basefields/templates/held_position.pt +++ b/src/collective/contact/core/browser/basefields/templates/held_position.pt @@ -2,20 +2,28 @@ lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal" - i18n:domain="collective.contact.core"> + i18n:domain="collective.contact.core" + tal:define="ajax_load request/ajax_load | nothing;">

+
+
-
+
- - - + + + + + +
@@ -30,4 +38,4 @@ i18n:translate="" />
- \ No newline at end of file + diff --git a/src/collective/contact/core/browser/basefields/templates/organization.pt b/src/collective/contact/core/browser/basefields/templates/organization.pt index 4e46d974..f4f38e46 100644 --- a/src/collective/contact/core/browser/basefields/templates/organization.pt +++ b/src/collective/contact/core/browser/basefields/templates/organization.pt @@ -3,16 +3,30 @@ xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en" - i18n:domain="collective.contact.core" tal:omit-tag=""> + i18n:domain="collective.contact.core" tal:omit-tag="" + tal:define="ajax_load request/ajax_load | nothing;">

+
+ Generic KSS Description. Is rendered with class="documentDescription". +
+ + + +
+
+
+ + +
+ diff --git a/src/collective/contact/core/browser/basefields/templates/person.pt b/src/collective/contact/core/browser/basefields/templates/person.pt index c62a6eca..ddf2b3a0 100644 --- a/src/collective/contact/core/browser/basefields/templates/person.pt +++ b/src/collective/contact/core/browser/basefields/templates/person.pt @@ -3,20 +3,26 @@ xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en" - tal:define="portal_url context/@@plone_portal_state/portal_url;" + tal:define="portal_url context/@@plone_portal_state/portal_url; + ajax_load request/ajax_load | nothing;" i18n:domain="collective.contact.core" tal:omit-tag="">

+
+
- \ No newline at end of file + diff --git a/src/collective/contact/core/browser/basefields/templates/position.pt b/src/collective/contact/core/browser/basefields/templates/position.pt index cf9293af..3d73a2d5 100644 --- a/src/collective/contact/core/browser/basefields/templates/position.pt +++ b/src/collective/contact/core/browser/basefields/templates/position.pt @@ -3,13 +3,17 @@ xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en" - i18n:domain="collective.contact.core" tal:omit-tag=""> + i18n:domain="collective.contact.core" tal:omit-tag="" + tal:define="ajax_load request/ajax_load | nothing;">

+
+
- \ No newline at end of file + diff --git a/src/collective/contact/core/browser/basefields/views.py b/src/collective/contact/core/browser/basefields/views.py index b77a7541..928c8a7c 100644 --- a/src/collective/contact/core/browser/basefields/views.py +++ b/src/collective/contact/core/browser/basefields/views.py @@ -1,25 +1,25 @@ +# -*- coding: utf-8 -*- from AccessControl import getSecurityManager - -from five import grok - +from collective.contact.core.behaviors import IBirthday +from collective.contact.core.browser.utils import date_to_DateTime +from collective.contact.core.interfaces import IContactCoreParameters +from plone import api +from Products.Five import BrowserView from zope.component import getUtility from zope.schema.interfaces import IVocabularyFactory -from collective.contact.core.behaviors import IBirthday -from collective.contact.core.browser.utils import date_to_DateTime -from collective.contact.core.content.person import IPerson -from collective.contact.core.content.organization import IOrganization -from collective.contact.core.content.position import IPosition -from collective.contact.core.interfaces import IHeldPosition +class BaseFields(object): -grok.templatedir('templates') + def display_below_content_title(self): + return api.portal.get_registry_record( + 'display_below_content_title_on_views', + interface=IContactCoreParameters, + default=False + ) -class PersonBaseFields(grok.View): - grok.name('basefields') - grok.template('person') - grok.context(IPerson) +class PersonBaseFields(BrowserView, BaseFields): name = '' birthday = '' @@ -44,12 +44,12 @@ def update(self): self.gender = person.gender or '' self.can_edit = sm.checkPermission('Modify portal content', person) + def __call__(self): + self.update() + return super(PersonBaseFields, self).__call__() -class OrganizationBaseFields(grok.View): - grok.name('basefields') - grok.template('organization') - grok.context(IOrganization) +class OrganizationBaseFields(BrowserView, BaseFields): name = '' type = '' @@ -64,17 +64,19 @@ def update(self): factory = getUtility(IVocabularyFactory, "OrganizationTypesOrLevels") vocabulary = factory(self.context) try: - self.type = vocabulary.getTerm(organization.organization_type).title + self.type = vocabulary.getTerm( + organization.organization_type + ).title except LookupError: pass self.activity = self.context.activity + def __call__(self): + self.update() + return super(OrganizationBaseFields, self).__call__() -class PositionBaseFields(grok.View): - grok.name('basefields') - grok.template('position') - grok.context(IPosition) +class PositionBaseFields(BrowserView, BaseFields): name = '' type = '' @@ -87,11 +89,12 @@ def update(self): vocabulary = factory(self.context) self.type = vocabulary.getTerm(position.position_type).title + def __call__(self): + self.update() + return super(PositionBaseFields, self).__call__() -class HeldPositionBaseFields(grok.View): - grok.name('basefields') - grok.template('held_position') - grok.context(IHeldPosition) + +class HeldPositionBaseFields(BrowserView, BaseFields): start_date = '' end_date = '' @@ -119,3 +122,7 @@ def update(self): self.title = held_position.get_full_title() self.position = held_position.get_position() + + def __call__(self): + self.update() + return super(HeldPositionBaseFields, self).__call__() diff --git a/src/collective/contact/core/browser/configure.zcml b/src/collective/contact/core/browser/configure.zcml index 409b9408..cd4608bb 100644 --- a/src/collective/contact/core/browser/configure.zcml +++ b/src/collective/contact/core/browser/configure.zcml @@ -1,17 +1,25 @@ + + + @@ -44,6 +53,85 @@ permission="zope2.View" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -96,12 +190,19 @@ + + diff --git a/src/collective/contact/core/browser/contact.py b/src/collective/contact/core/browser/contact.py index 96133b69..28931591 100644 --- a/src/collective/contact/core/browser/contact.py +++ b/src/collective/contact/core/browser/contact.py @@ -1,8 +1,7 @@ -from plone import api - +from collective.contact.core.behaviors import IBirthday from collective.contact.core.browser.contactable import BaseView from collective.contact.core.browser.utils import date_to_DateTime -from collective.contact.core.behaviors import IBirthday +from plone import api class Contact(BaseView): diff --git a/src/collective/contact/core/browser/contactable.py b/src/collective/contact/core/browser/contactable.py index 1bd93efc..7fbf0a83 100644 --- a/src/collective/contact/core/browser/contactable.py +++ b/src/collective/contact/core/browser/contactable.py @@ -1,31 +1,29 @@ -import os.path - -from zope.globalrequest import getRequest -from zope.interface import Interface -from five import grok +# -*- coding: utf-8 -*- from Acquisition import aq_base - -from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile - -from plone import api -from plone.dexterity.browser.view import DefaultView -from plone.dexterity.utils import getAdditionalSchemata - +from collective.contact.core.behaviors import CONTACT_DETAILS_FIELDS +from collective.contact.core.behaviors import IContactDetails from collective.contact.core.browser import TEMPLATES_DIR from collective.contact.core.browser.address import get_address -from collective.contact.core.behaviors import IContactDetails -from collective.contact.core.interfaces import IContactable, IContactCoreParameters -from collective.contact.widget.interfaces import IContactContent -from collective.contact.core.behaviors import CONTACT_DETAILS_FIELDS from collective.contact.core.browser.utils import get_valid_url +from collective.contact.core.interfaces import IContactable +from collective.contact.core.interfaces import IContactCoreParameters +from plone import api +from plone.dexterity.browser.view import DefaultView +from plone.dexterity.utils import getAdditionalSchemata +from Products.Five import BrowserView +from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile +from zope.globalrequest import getRequest +from zope.interface import implementer -grok.templatedir(TEMPLATES_DIR) +import os.path -class ContactDetailsContactable(grok.Adapter): +@implementer(IContactable) +class ContactDetailsContactable(object): """Common adapter class for objects that just implement the IContactDetails behavior""" - grok.provides(IContactable) - grok.context(Interface) + + def __init__(self, context): + self.context = context def get_contact_details(self, keys=(), fallback=True): if not IContactDetails.providedBy(self.context): @@ -57,35 +55,35 @@ def get_parent_address(self): return u"" -class ContactDetails(grok.View): - grok.name('contactdetails') - grok.template('contactdetails') - grok.context(IContactContent) +class ContactDetails(BrowserView): + + address_template = ViewPageTemplateFile('templates/address.pt') - template_path = os.path.join(TEMPLATES_DIR, 'address.pt') + def __call__(self): + self.update() + return super(ContactDetails, self).__call__() def update(self): contactable = IContactable(self.context) self.contact_details = contactable.get_contact_details() def render_address(self): - template = ViewPageTemplateFile(self.template_path) - return template(self, self.contact_details['address']) + return self.address_template(self.contact_details['address']) class NoFallbackContactDetails(ContactDetails): - grok.name('nofallbackcontactdetails') def update(self): contactable = IContactable(self.context) self.contact_details = contactable.get_contact_details(fallback=False) -class Contactable(grok.Adapter): +@implementer(IContactable) +class Contactable(object): """Base adapter class for contact content types with fallback system""" - grok.provides(IContactable) - grok.context(IContactContent) - grok.baseclass() + + def __init__(self, context): + self.context = context @property def person(self): @@ -110,9 +108,13 @@ def _get_contactables(self): we use the one of the first object in this list which have this information """ contactables = [] - related_items = [self.context, self.held_position, self.position] + list(reversed(self.organizations)) + related_items = [self.context, self.held_position, + self.position] + list(reversed(self.organizations)) if not api.portal.get_registry_record(name='person_contact_details_private', interface=IContactCoreParameters): related_items.insert(2, self.person) + elif self.context == self.person: + # for a person, we get private info only + related_items = [self.context] for related_item in related_items: if related_item is not None \ and IContactDetails.providedBy(related_item) \ @@ -156,7 +158,8 @@ def get_contact_details(self, keys=(), fallback=True): contact_details['address'] = self._get_address(contactables) if 'website' in contact_details: - contact_details['website'] = get_valid_url(contact_details['website']) + contact_details['website'] = get_valid_url( + contact_details['website']) return contact_details diff --git a/src/collective/contact/core/browser/directory.py b/src/collective/contact/core/browser/directory.py index 434bca98..af4835e7 100644 --- a/src/collective/contact/core/browser/directory.py +++ b/src/collective/contact/core/browser/directory.py @@ -1,5 +1,5 @@ -from plone.dexterity.browser.view import DefaultView from plone import api +from plone.dexterity.browser.view import DefaultView class Directory(DefaultView): @@ -10,6 +10,7 @@ def update(self): search_path = {'query': directory_path, 'depth': 1} catalog = api.portal.get_tool('portal_catalog') self.persons = catalog.searchResults(portal_type="person", - path=search_path) + path=search_path, + sort_on='sortable_title') self.organizations = catalog.searchResults(portal_type="organization", path=search_path) diff --git a/src/collective/contact/core/browser/excelexport.py b/src/collective/contact/core/browser/excelexport.py index 56312c4c..42be2eb4 100644 --- a/src/collective/contact/core/browser/excelexport.py +++ b/src/collective/contact/core/browser/excelexport.py @@ -1,11 +1,17 @@ -from zope.component import adapts +from collective.contact.core.behaviors import ADDRESS_FIELDS +from collective.contact.core.interfaces import IContactable +from collective.contact.core.interfaces import IHeldPosition +from collective.contact.widget.interfaces import IContactChoice +from collective.contact.widget.interfaces import IContactContent +from plone import api +from plone.dexterity.interfaces import IDexterityFTI +from Products.CMFPlone.utils import safe_unicode +from zope.component import adapter from zope.component import getMultiAdapter from zope.component.interfaces import ComponentLookupError +from zope.interface import implementer from zope.interface import Interface -from zope.interface import implements -from plone.dexterity.interfaces import IDexterityFTI -from plone import api try: from collective.excelexport.exportables.dexterityfields import BaseFieldRenderer @@ -17,15 +23,11 @@ except ImportError: HAS_EXCELEXPORT = False -from collective.contact.widget.interfaces import IContactChoice, IContactContent -from collective.contact.core.interfaces import IContactable, IHeldPosition -from collective.contact.core.behaviors import ADDRESS_FIELDS - -if HAS_EXCELEXPORT: +if HAS_EXCELEXPORT: # noqa for now 'is too complex' + @adapter(IContactChoice, Interface, Interface) class ContactFieldRenderer(BaseFieldRenderer): - adapts(IContactChoice, Interface, Interface) def render_value(self, obj): value = self.get_value(obj) @@ -36,12 +38,12 @@ def render_collection_entry(self, obj, value): if not rel_obj: return u"" if IHeldPosition.providedBy(rel_obj): - return rel_obj.get_full_title() or u"" + return safe_unicode(rel_obj.get_full_title()) or u"" else: - return rel_obj.Title() + return safe_unicode(rel_obj.Title()) + @adapter(IDexterityFTI, Interface, Interface) class HeldPositionPersonInfoExportableFactory(BaseExportableFactory): - adapts(IDexterityFTI, Interface, Interface) portal_types = ('held_position',) weight = 10 @@ -56,29 +58,30 @@ def get_exportables(self): try: # check if there is a specific adapter for the field name exportable = getMultiAdapter( - (field, self.context, self.request), - interface=IExportable, - name=field_name) + (field, self.context, self.request), + interface=IExportable, + name=field_name) except ComponentLookupError: # get the generic adapter for the field exportable = getMultiAdapter( - (field, self.context, self.request), - interface=IExportable) + (field, self.context, self.request), + interface=IExportable) exportables.append(exportable) return exportables + @implementer(IFieldValueGetter) + @adapter(IContactContent) class ContactValueGetter(object): - adapts(IContactContent) - implements(IFieldValueGetter) def __init__(self, context): self.context = context def get(self, field): if field.__name__ in ADDRESS_FIELDS: - address = IContactable(self.context).get_contact_details(('address',))['address'] + address = IContactable(self.context).get_contact_details( + ('address',))['address'] return address.get(field.__name__, None) return getattr(self.context, field.__name__, None) diff --git a/src/collective/contact/core/browser/excelexport.zcml b/src/collective/contact/core/browser/excelexport.zcml index c2408d9a..42eb0da4 100644 --- a/src/collective/contact/core/browser/excelexport.zcml +++ b/src/collective/contact/core/browser/excelexport.zcml @@ -2,7 +2,6 @@ xmlns="http://namespaces.zope.org/zope" xmlns:zcml="http://namespaces.zope.org/zcml" xmlns:five="http://namespaces.zope.org/five" - xmlns:grok="http://namespaces.zope.org/grok" xmlns:i18n="http://namespaces.zope.org/i18n" xmlns:browser="http://namespaces.zope.org/browser" xmlns:genericsetup="http://namespaces.zope.org/genericsetup" diff --git a/src/collective/contact/core/browser/organization.py b/src/collective/contact/core/browser/organization.py index fe34cd9a..1452ef7e 100644 --- a/src/collective/contact/core/browser/organization.py +++ b/src/collective/contact/core/browser/organization.py @@ -1,41 +1,16 @@ # -*- coding: utf-8 -*- - from AccessControl import getSecurityManager from collective.contact.core.behaviors import IContactDetails from collective.contact.core.browser.contactable import BaseView from collective.contact.core.browser.utils import date_to_DateTime from collective.contact.core.browser.utils import get_valid_url -from collective.contact.core.content.organization import IOrganization +from collective.contact.core.indexers import held_position_sortable_title from collective.contact.core.interfaces import IContactable from collective.contact.core.interfaces import IContactCoreParameters -from five import grok from plone import api from Products.Five import BrowserView -ADDNEW_OVERLAY = """ - -""" - - -grok.templatedir('templates') - - class Organization(BaseView): def update(self): @@ -57,7 +32,6 @@ def update(self): self.positions = self.context.get_positions() sm = getSecurityManager() self.can_add = sm.checkPermission('Add portal content', self.context) - self.addnew_script = ADDNEW_OVERLAY def display_date(self, date): """Display date nicely in template.""" @@ -76,25 +50,26 @@ def __call__(self): return self.index() -class OtherContacts(grok.View): +class OtherContacts(BrowserView): """Displays other contacts list""" - grok.name('othercontacts') - grok.context(IOrganization) held_positions = '' + def held_position_order_key(self, held_position): + return held_position_sortable_title(held_position) + def update(self): organization = self.context othercontacts = [] held_positions = organization.get_held_positions() - held_positions.sort(key=lambda x: x.get_sortable_title()) + held_positions.sort(key=lambda x: self.held_position_order_key(x)) for hp in held_positions: contact = {} person = hp.get_person() contact['person'] = person contact['title'] = person.Title() contact['held_position'] = hp.Title() - contact['label'] = hp.label + contact['label'] = hp.get_label() contact['obj'] = hp contact['display_photo'] = api.portal.get_registry_record( name='display_contact_photo_on_organization_view', @@ -116,3 +91,7 @@ def update(self): othercontacts.append(contact) self.othercontacts = othercontacts + + def __call__(self): + self.update() + return super(OtherContacts, self).__call__() diff --git a/src/collective/contact/core/browser/person.py b/src/collective/contact/core/browser/person.py index 648c5c78..c9d2d9fd 100644 --- a/src/collective/contact/core/browser/person.py +++ b/src/collective/contact/core/browser/person.py @@ -1,17 +1,12 @@ -from five import grok - +# -*- coding: utf-8 -*- from AccessControl import getSecurityManager - -from collective.contact.core.browser import TEMPLATES_DIR +from collective.contact.core.behaviors import IContactDetails from collective.contact.core.browser.contactable import BaseView -from collective.contact.core.content.person import IPerson from collective.contact.core.browser.utils import date_to_DateTime -from collective.contact.core.interfaces import IContactable,\ - IPersonHeldPositions -from collective.contact.core.behaviors import IContactDetails - - -grok.templatedir(TEMPLATES_DIR) +from collective.contact.core.content.person import IPerson # noqa +from collective.contact.core.interfaces import IContactable +from collective.contact.core.interfaces import IPersonHeldPositions +from Products.Five import BrowserView class Person(BaseView): @@ -27,11 +22,8 @@ def update(self): self.show_contact_details = True -class HeldPositions(grok.View): +class HeldPositions(BrowserView): """Displays held positions list""" - grok.name('heldpositions') - grok.template('heldpositions') - grok.context(IPerson) held_positions = '' @@ -44,7 +36,8 @@ def update(self): held_position['title'] = obj.Title() if obj.start_date is not None: start_date = date_to_DateTime(obj.start_date) - held_position['start_date'] = person.toLocalizedTime(start_date) + held_position['start_date'] = person.toLocalizedTime( + start_date) else: held_position['start_date'] = None @@ -58,10 +51,20 @@ def update(self): # held_position['email'] = obj.email held_position['object'] = obj organization = obj.get_organization() + if organization: + held_position['organization'] = organization.get_root_organization() + else: + held_position['organization'] = None held_position['icon'] = obj.getIconURL() - held_position['organization'] = organization if organization else None - held_position['can_edit'] = sm.checkPermission('Modify portal content', obj) - held_position['can_delete'] = sm.checkPermission('Delete objects', obj) + held_position['can_edit'] = sm.checkPermission( + 'Modify portal content', obj) + held_position['can_delete'] = sm.checkPermission( + 'Delete objects', obj) held_positions.append(held_position) self.held_positions = held_positions + return super(HeldPositions, self).__call__() + + def __call__(self): + self.update() + return super(HeldPositions, self).__call__() diff --git a/src/collective/contact/core/browser/person_title_mapping.py b/src/collective/contact/core/browser/person_title_mapping.py index 83a6a94f..39c76060 100644 --- a/src/collective/contact/core/browser/person_title_mapping.py +++ b/src/collective/contact/core/browser/person_title_mapping.py @@ -1,22 +1,16 @@ # -*- coding: utf-8 -*- -import json - -from five import grok +from collective.contact.core import _ +from Products.Five import BrowserView from zope.i18n import translate -from zope.interface import Interface -from collective.contact.core import _ +import json -class GenderPersonTitleMapping(grok.View): +class GenderPersonTitleMapping(BrowserView): """Return gender/person_title mapping in json.""" - grok.name("gender_person_title_mapping.json") - grok.context(Interface) - grok.require('zope2.View') - - def render(self): + def __call__(self): request = self.request request.response.setHeader( 'Content-Type', 'application/json') diff --git a/src/collective/contact/core/browser/position.py b/src/collective/contact/core/browser/position.py index 2d817138..d7d63497 100644 --- a/src/collective/contact/core/browser/position.py +++ b/src/collective/contact/core/browser/position.py @@ -1,30 +1,8 @@ from AccessControl import getSecurityManager - -from zope.component import getUtility -from zope.schema.interfaces import IVocabularyFactory - from collective.contact.core.browser.contactable import BaseView from collective.contact.core.interfaces import IContactable - - -ADDNEW_OVERLAY = """ - -""" +from zope.component import getUtility +from zope.schema.interfaces import IVocabularyFactory class Position(BaseView): @@ -42,4 +20,3 @@ def update(self): sm = getSecurityManager() self.can_add = sm.checkPermission('Add portal content', self.context) - self.addnew_script = ADDNEW_OVERLAY diff --git a/src/collective/contact/core/skins/collective_contact_core/create_contact.png b/src/collective/contact/core/browser/static/create_contact.png similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/create_contact.png rename to src/collective/contact/core/browser/static/create_contact.png diff --git a/src/collective/contact/core/browser/static/delete_icon.png b/src/collective/contact/core/browser/static/delete_icon.png new file mode 100644 index 00000000..36594937 Binary files /dev/null and b/src/collective/contact/core/browser/static/delete_icon.png differ diff --git a/src/collective/contact/core/browser/static/delete_icon.png.metadata b/src/collective/contact/core/browser/static/delete_icon.png.metadata new file mode 100644 index 00000000..855feccc --- /dev/null +++ b/src/collective/contact/core/browser/static/delete_icon.png.metadata @@ -0,0 +1,2 @@ +[default] +cache=HTTPCache diff --git a/src/collective/contact/core/skins/collective_contact_core/directory_icon.png b/src/collective/contact/core/browser/static/directory_icon.png similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/directory_icon.png rename to src/collective/contact/core/browser/static/directory_icon.png diff --git a/src/collective/contact/core/skins/collective_contact_core/directory_icon.png.metadata b/src/collective/contact/core/browser/static/directory_icon.png.metadata similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/directory_icon.png.metadata rename to src/collective/contact/core/browser/static/directory_icon.png.metadata diff --git a/src/collective/contact/core/browser/static/edit.png b/src/collective/contact/core/browser/static/edit.png new file mode 100644 index 00000000..e432e029 Binary files /dev/null and b/src/collective/contact/core/browser/static/edit.png differ diff --git a/src/collective/contact/core/browser/static/edit.png.metadata b/src/collective/contact/core/browser/static/edit.png.metadata new file mode 100644 index 00000000..855feccc --- /dev/null +++ b/src/collective/contact/core/browser/static/edit.png.metadata @@ -0,0 +1,2 @@ +[default] +cache=HTTPCache diff --git a/src/collective/contact/core/browser/static/forms.js b/src/collective/contact/core/browser/static/forms.js index ce344656..286e6919 100644 --- a/src/collective/contact/core/browser/static/forms.js +++ b/src/collective/contact/core/browser/static/forms.js @@ -1,11 +1,12 @@ -contactswidget = {}; +'use strict'; +var contactswidget = {}; /* Update person_title when gender changes (if person_title hasn't been set manually) */ contactswidget.update_person_title = function(genderInput, mapping) { var gender = $(genderInput).val(); var person_title = $('#form-widgets-person_title').val(); var values = Object.values(mapping); - if (person_title === '' || values.indexOf(person_title) !== -1) { + if (person_title === "" || values.indexOf(person_title) !== -1) { $('#form-widgets-person_title').val(mapping[gender]); } }; @@ -44,9 +45,9 @@ contactswidget.serialize_form = function(form) { /* Update the token which matches the input*/ contactswidget.update_token = function(){ var name = $(this).val(); - var token = $(this).closest('tr').find('input[id$="-widgets-token"]'); - if (token.val() === '') { - token_value = contactswidget.normalize_string(name); + var token = $(this).closest("tr").find('input[id$="-widgets-token"]'); + if (token.val() === "") { + var token_value = contactswidget.normalize_string(name); token.val(token_value); } }; @@ -67,8 +68,8 @@ contactswidget.manage_directory = function(){ use_parent_address is not checked */ contactswidget.manage_hide_use_parent_address = function(){ if ($("#form-widgets-IContactDetails-parent_address").text().trim().length === 0) { - if($('#form-widgets-IContactDetails-use_parent_address-0').length > 0 - && $('#form-widgets-IContactDetails-use_parent_address-0:checked').length == 0) { + if($('#form-widgets-IContactDetails-use_parent_address-0').length > 0 && + $('#form-widgets-IContactDetails-use_parent_address-0:checked').length === 0) { if($('#formfield-form-widgets-position').length === 0){ /* except on held position form because, there, * actual parent address can change during edition @@ -110,7 +111,7 @@ contactswidget.setup_relation_dependency = function(master_field, slave_field, r /* change create link so that master field selection is selected by default */ var add_link = $('#formfield-' + slave_field.replace(/\./g, '-')).find('.addnew'); - if(add_link.length == 1){ + if(add_link.length === 1){ var orig_href = add_link.attr('href'); var key; if(orig_href.indexOf('@add-contact') > -1) { @@ -134,7 +135,6 @@ contactswidget.setup_relation_dependency = function(master_field, slave_field, r } else { add_link.text(text_wo_company + ' (' + selected.title + ')'); } - } } } @@ -153,37 +153,20 @@ contactswidget.setup_relation_dependency = function(master_field, slave_field, r }; $(document).ready(function(){ - var url = 'gender_person_title_mapping.json'; + // call view on portal + var url = portal_url + '/@@gender_person_title_mapping.json'; $.get(url, function (mapping) { $(document).on( 'change', '#formfield-form-widgets-gender input', - function () { contactswidget.update_person_title(this, mapping) } + function () { contactswidget.update_person_title(this, mapping); } ); }); /* contactswidget.manage_directory(); Do not hide token column in edit mode */ contactswidget.manage_hide_use_parent_address(); - jQuery(document).bind('loadInsideOverlay', - function(e, pbajax, responseText, errorText, api){ + $(document).bind('after-render', function() { contactswidget.manage_hide_use_parent_address(); }); - - $('.contactoverlay').prepOverlay({ - subtype: 'ajax', - filter: common_content_filter, - formselector: '#form', - closeselector: '[name="form.buttons.cancel"]', - noform: function(el, pbo) {return 'reload';} - }); - - $('.deleteoverlay').prepOverlay({ - subtype: 'ajax', - filter: common_content_filter, - formselector: '#delete_confirmation', - closeselector: '[name="form.button.Cancel"]', - noform: function(el, pbo) {return 'reload';} - }); - }); diff --git a/src/collective/contact/core/browser/static/forms.min.js b/src/collective/contact/core/browser/static/forms.min.js new file mode 100644 index 00000000..88d122b5 --- /dev/null +++ b/src/collective/contact/core/browser/static/forms.min.js @@ -0,0 +1 @@ +"use strict";var contactswidget={};contactswidget.update_person_title=function(genderInput,mapping){var gender=$(genderInput).val();var person_title=$("#form-widgets-person_title").val();var values=Object.values(mapping);if(person_title===""||values.indexOf(person_title)!==-1){$("#form-widgets-person_title").val(mapping[gender])}};contactswidget.normalize_string=function(s){var rules={a:/[àáâãäå]/g,ae:/[æ]/g,c:/[ç]/g,e:/[èéêë]/g,i:/[ìíîï]/g,n:/[ñ]/g,o:/[òóôõö]/g,oe:/[œ]/g,u:/[ùúûü]/g,y:/[ýÿ]/g,th:/[ðþ]/g,ss:/[ß]/g,_:/[\s\\]+/g};s=s.toLowerCase();for(var r in rules)s=s.replace(rules[r],r);return s};contactswidget.serialize_form=function(form){var viewArr=$(form).serializeArray();var view={};for(var i in viewArr){view[viewArr[i].name]=viewArr[i].value}return view};contactswidget.update_token=function(){var name=$(this).val();var token=$(this).closest("tr").find('input[id$="-widgets-token"]');if(token.val()===""){var token_value=contactswidget.normalize_string(name);token.val(token_value)}};contactswidget.manage_directory=function(){$('input[id$="-widgets-token"]').hide();$("#formfield-form-widgets-position_types thead").hide();$("#formfield-form-widgets-organization_types thead").hide();$("#formfield-form-widgets-organization_levels thead").hide();$(".portaltype-directory .datagridwidget-table-view thead").hide();$('input[id$="-widgets-name"]').blur(contactswidget.update_token)};contactswidget.manage_hide_use_parent_address=function(){if($("#form-widgets-IContactDetails-parent_address").text().trim().length===0){if($("#form-widgets-IContactDetails-use_parent_address-0").length>0&&$("#form-widgets-IContactDetails-use_parent_address-0:checked").length===0){if($("#formfield-form-widgets-position").length===0){$("#formfield-form-widgets-IContactDetails-use_parent_address").hide()}}}};contactswidget.get_selected_contact=function(form,field_id){var view=contactswidget.serialize_form(form);var token=view[field_id];if(token===undefined){return undefined}var input=form.find("#"+field_id.replace(/\./g,"-")+'-input-fields input[value="'+token+'"]');var title=input.siblings(".label").find("a").first().text();var path="/"+token.split("/").slice(2).join("/");return{token:token,title:title,path:path}};contactswidget.setup_relation_dependency=function(master_field,slave_field,relation){function apply_relation_dependency(input,master_field,slave_field,relation){var form=input.parents("form").first();var selected=contactswidget.get_selected_contact(form,master_field);var relations={};if(selected!==undefined){relations["relations."+relation+":record"]=selected.token}var slave_field_query=$("#"+slave_field.replace(/\./g,"-")+"-widgets-query");slave_field_query.setOptions({extraParams:relations}).flushCache();var add_link=$("#formfield-"+slave_field.replace(/\./g,"-")).find(".addnew");if(add_link.length===1){var orig_href=add_link.attr("href");var key;if(orig_href.indexOf("@add-contact")>-1){key="@add-contact"}if(orig_href.indexOf("@add-held-position")>-1){key="@add-held-position"}if(key){var base_add_url=orig_href.substr(0,orig_href.indexOf(key)+key.length);var new_url=base_add_url+"?oform.widgets.organization="+selected.token;new_url+="&oform.widgets.position="+selected.token;if(add_link.orig_text===undefined){add_link.orig_text=add_link.text()}add_link.attr("href",new_url);add_link.data("pbo").src=new_url;var text_wo_company=add_link.orig_text.replace(/ *\([^)]*\) */g,"");if(selected.token==="--NOVALUE--"){add_link.text(text_wo_company)}else{add_link.text(text_wo_company+" ("+selected.title+")")}}}}var selector="#"+master_field.replace(/\./g,"-")+"-input-fields input";$("body").on("change",selector,function(){apply_relation_dependency($(this),master_field,slave_field,relation)});$(document).ready(function(){$("body").find(selector).each(function(){apply_relation_dependency($(this),master_field,slave_field,relation)})})};$(document).ready(function(){var url="gender_person_title_mapping.json";$.get(url,function(mapping){$(document).on("change","#formfield-form-widgets-gender input",function(){contactswidget.update_person_title(this,mapping)})});contactswidget.manage_hide_use_parent_address();$(document).bind("after-render",function(){contactswidget.manage_hide_use_parent_address()})}); diff --git a/src/collective/contact/core/skins/collective_contact_core/held_position_icon.png b/src/collective/contact/core/browser/static/held_position_icon.png similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/held_position_icon.png rename to src/collective/contact/core/browser/static/held_position_icon.png diff --git a/src/collective/contact/core/skins/collective_contact_core/held_position_icon.png.metadata b/src/collective/contact/core/browser/static/held_position_icon.png.metadata similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/held_position_icon.png.metadata rename to src/collective/contact/core/browser/static/held_position_icon.png.metadata diff --git a/src/collective/contact/core/skins/collective_contact_core/organization_icon.png b/src/collective/contact/core/browser/static/organization_icon.png similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/organization_icon.png rename to src/collective/contact/core/browser/static/organization_icon.png diff --git a/src/collective/contact/core/skins/collective_contact_core/organization_icon.png.metadata b/src/collective/contact/core/browser/static/organization_icon.png.metadata similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/organization_icon.png.metadata rename to src/collective/contact/core/browser/static/organization_icon.png.metadata diff --git a/src/collective/contact/core/skins/collective_contact_core/person_icon.png b/src/collective/contact/core/browser/static/person_icon.png similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/person_icon.png rename to src/collective/contact/core/browser/static/person_icon.png diff --git a/src/collective/contact/core/skins/collective_contact_core/person_icon.png.metadata b/src/collective/contact/core/browser/static/person_icon.png.metadata similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/person_icon.png.metadata rename to src/collective/contact/core/browser/static/person_icon.png.metadata diff --git a/src/collective/contact/core/skins/collective_contact_core/position_icon.png b/src/collective/contact/core/browser/static/position_icon.png similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/position_icon.png rename to src/collective/contact/core/browser/static/position_icon.png diff --git a/src/collective/contact/core/skins/collective_contact_core/position_icon.png.metadata b/src/collective/contact/core/browser/static/position_icon.png.metadata similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/position_icon.png.metadata rename to src/collective/contact/core/browser/static/position_icon.png.metadata diff --git a/src/collective/contact/core/browser/static/search_icon.png b/src/collective/contact/core/browser/static/search_icon.png new file mode 100644 index 00000000..eb31b322 Binary files /dev/null and b/src/collective/contact/core/browser/static/search_icon.png differ diff --git a/src/collective/contact/core/browser/static/search_icon.png.metadata b/src/collective/contact/core/browser/static/search_icon.png.metadata new file mode 100644 index 00000000..855feccc --- /dev/null +++ b/src/collective/contact/core/browser/static/search_icon.png.metadata @@ -0,0 +1,2 @@ +[default] +cache=HTTPCache diff --git a/src/collective/contact/core/skins/collective_contact_core/style.css b/src/collective/contact/core/browser/static/style.css similarity index 63% rename from src/collective/contact/core/skins/collective_contact_core/style.css rename to src/collective/contact/core/browser/static/style.css index a0cd0c45..87a9e6cb 100644 --- a/src/collective/contact/core/skins/collective_contact_core/style.css +++ b/src/collective/contact/core/browser/static/style.css @@ -45,10 +45,39 @@ padding-right: 1em; } +/* Reset widget styling for Plone 5 */ +#formfield-form-widgets-IBirthday-birthday select { + width: unset; + display: unset; +} + +#oform input[type="text"], +#oform input[type="email"], +#oform input[type="password"], +#oform textarea, +#oform select { + width: auto!important; + display: inline-block; +} + h3 img, h4 img { vertical-align: baseline; } -#content a:link.link-tooltip { +#content .pat-tooltip a { border-bottom: 1px dotted; -} \ No newline at end of file +} + +/* + tooltip + */ + +.contact-tooltip.mockup-tooltip.tooltip.in { + opacity: 99%; +} + +.contact-tooltip.mockup-tooltip .tooltip-inner { + max-width: 100%; + text-align: inherit; + padding: 12px; +} diff --git a/src/collective/contact/core/skins/collective_contact_core/vcard_icon.png b/src/collective/contact/core/browser/static/vcard_icon.png similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/vcard_icon.png rename to src/collective/contact/core/browser/static/vcard_icon.png diff --git a/src/collective/contact/core/skins/collective_contact_core/vcard_icon.png.metadata b/src/collective/contact/core/browser/static/vcard_icon.png.metadata similarity index 100% rename from src/collective/contact/core/skins/collective_contact_core/vcard_icon.png.metadata rename to src/collective/contact/core/browser/static/vcard_icon.png.metadata diff --git a/src/collective/contact/core/browser/templates/address.pt b/src/collective/contact/core/browser/templates/address.pt index d12a23c6..dc172468 100644 --- a/src/collective/contact/core/browser/templates/address.pt +++ b/src/collective/contact/core/browser/templates/address.pt @@ -3,24 +3,26 @@ i18n:domain="collective.contact.core" tal:repeat="address args">
- -
- - + +
+ + +
+
+ +
+
+ + +
+
+ +
+
+ +
-
- -
-
- - -
-
- -
-
- -
- -
-
\ No newline at end of file +
diff --git a/src/collective/contact/core/browser/templates/contact.pt b/src/collective/contact/core/browser/templates/contact.pt index 86defb43..42b2a622 100644 --- a/src/collective/contact/core/browser/templates/contact.pt +++ b/src/collective/contact/core/browser/templates/contact.pt @@ -17,10 +17,16 @@
@@ -31,7 +37,7 @@ -
+
diff --git a/src/collective/contact/core/browser/templates/contactdetails.pt b/src/collective/contact/core/browser/templates/contactdetails.pt index 9c33ca75..3d7978ed 100644 --- a/src/collective/contact/core/browser/templates/contactdetails.pt +++ b/src/collective/contact/core/browser/templates/contactdetails.pt @@ -7,7 +7,7 @@ class="contact-details" tal:define="portal_url context/@@plone_portal_state/portal_url;"> -

+

-

+

-

+

-

+

-

@@ -53,11 +53,11 @@ tal:attributes="href website" />

-

+

- + Download VCard

diff --git a/src/collective/contact/core/browser/templates/directory.pt b/src/collective/contact/core/browser/templates/directory.pt index 8b242eca..b1c34038 100644 --- a/src/collective/contact/core/browser/templates/directory.pt +++ b/src/collective/contact/core/browser/templates/directory.pt @@ -1,6 +1,5 @@ -
- -

- -
-

Organizations:

- -
- -
-

Persons:

- -
- -
+
+ +

+ +
+

Organizations:

+ +
+ +
+

+ Persons + : +

+ +
+
- \ No newline at end of file + diff --git a/src/collective/contact/core/browser/templates/heldpositions.pt b/src/collective/contact/core/browser/templates/heldpositions.pt index fd00479f..147de2a9 100644 --- a/src/collective/contact/core/browser/templates/heldpositions.pt +++ b/src/collective/contact/core/browser/templates/heldpositions.pt @@ -3,7 +3,8 @@ xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en" - tal:define="portal_url context/@@plone_portal_state/portal_url;" + tal:define="portal_url context/@@plone_portal_state/portal_url; + checkPermission nocall: context/portal_membership/checkPermission;" i18n:domain="collective.contact.core" tal:omit-tag="">
@@ -11,27 +12,31 @@
-
- + diff --git a/src/collective/contact/core/browser/templates/organization.pt b/src/collective/contact/core/browser/templates/organization.pt index 01e91b78..f3a826b9 100644 --- a/src/collective/contact/core/browser/templates/organization.pt +++ b/src/collective/contact/core/browser/templates/organization.pt @@ -3,81 +3,106 @@ xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" - metal:use-macro="context/main_template/macros/master" - i18n:domain="collective.contact.core"> + metal:use-macro="context/main_template/macros/master" + i18n:domain="collective.contact.core">
-
-
+
+
-
- - -
+
+ + +
-
+
-
+
- + -
+
-
-

Positions in this organization:

-
    - -
  • - - : - -
      -
    • - - - - - (from to ) - -
    • -
    -
    Not assigned -
  • -
    -
-
+
+

+ Positions in this organization + : +

+
    + +
  • -
    + + + + : + + +
      +
    • + + + + + + + + ( + from + to ) + + , +
    • +
    +
    + Not assigned +
  • +
    +
+
- - - - - Create Contact - - - - +
-
+ + + + + Create + Contact + + + + +
diff --git a/src/collective/contact/core/browser/templates/othercontacts.pt b/src/collective/contact/core/browser/templates/othercontacts.pt index dcac7c26..978af420 100644 --- a/src/collective/contact/core/browser/templates/othercontacts.pt +++ b/src/collective/contact/core/browser/templates/othercontacts.pt @@ -24,17 +24,17 @@ - - - + + + - - - - + + + + - +
diff --git a/src/collective/contact/core/browser/templates/person.pt b/src/collective/contact/core/browser/templates/person.pt index c0224482..2f7ea225 100644 --- a/src/collective/contact/core/browser/templates/person.pt +++ b/src/collective/contact/core/browser/templates/person.pt @@ -20,7 +20,7 @@
- @@ -34,17 +40,18 @@ - - + + Create Contact - -
+
+
diff --git a/src/collective/contact/core/browser/templates/suborganizations.pt b/src/collective/contact/core/browser/templates/suborganizations.pt index 23f3d0fa..70eea16a 100644 --- a/src/collective/contact/core/browser/templates/suborganizations.pt +++ b/src/collective/contact/core/browser/templates/suborganizations.pt @@ -10,10 +10,16 @@

Organizations in this organization:

diff --git a/src/collective/contact/core/browser/ttwfields.py b/src/collective/contact/core/browser/ttwfields.py index f960ec4f..9953534d 100644 --- a/src/collective/contact/core/browser/ttwfields.py +++ b/src/collective/contact/core/browser/ttwfields.py @@ -1,24 +1,20 @@ -from five import grok - -from zope.interface import Interface - +# -*- coding: utf-8 -*- from collective.contact.core.browser.utils import get_ttw_fields -from collective.contact.core.browser import TEMPLATES_DIR - +from Products.Five import BrowserView -grok.templatedir(TEMPLATES_DIR) - -class TTWFields(grok.View): +class TTWFields(BrowserView): """Show fields that were added TTW """ - grok.name('ttwfields') - grok.template('ttwfields') - grok.context(Interface) def update(self): contact_view = self.context.unrestrictedTraverse('view') contact_view.update() self.widgets = contact_view.widgets ttw_fields = get_ttw_fields(self.context) - self.ttw_fields = [field for field in ttw_fields if field in self.widgets.keys()] + self.ttw_fields = [ + field for field in ttw_fields if field in self.widgets.keys()] + + def __call__(self): + self.update() + return super(TTWFields, self).__call__() diff --git a/src/collective/contact/core/browser/utils.py b/src/collective/contact/core/browser/utils.py index 9870a7b7..c62bc900 100644 --- a/src/collective/contact/core/browser/utils.py +++ b/src/collective/contact/core/browser/utils.py @@ -1,16 +1,14 @@ +from collective.contact.core.behaviors import IBirthday +from collective.contact.core.behaviors import IContactDetails from DateTime import DateTime -from zope.component import getUtility -from zope import schema - -from plone.supermodel.interfaces import ISchemaPolicy +from plone.app.dexterity.behaviors.metadata import IBasic from plone.autoform.interfaces import IFormFieldProvider from plone.behavior.interfaces import IBehavior -from plone.schemaeditor.utils import non_fieldset_fields from plone.dexterity.interfaces import IDexterityFTI -from plone.app.dexterity.behaviors.metadata import IBasic - -from collective.contact.core.behaviors import IBirthday -from collective.contact.core.behaviors import IContactDetails +from plone.schemaeditor.utils import non_fieldset_fields +from plone.supermodel.interfaces import ISchemaPolicy +from zope import schema +from zope.component import getUtility IGNORED_BEHAVIORS = [IContactDetails, IBasic, IBirthday] @@ -44,7 +42,7 @@ def get_ttw_fields(obj): # @TODO: get generic method to get widget id new_fields.extend(['%s.%s' % (behavior_name, field_name) for field_name in default_fieldset_fields]) - except: + except Exception: pass return new_fields diff --git a/src/collective/contact/core/browser/vcard_export.py b/src/collective/contact/core/browser/vcard_export.py index 570c435f..1077df06 100644 --- a/src/collective/contact/core/browser/vcard_export.py +++ b/src/collective/contact/core/browser/vcard_export.py @@ -1,15 +1,10 @@ -from five import grok - from collective.contact.core.interfaces import IVCard -from collective.contact.widget.interfaces import IContactContent +from Products.Five import BrowserView -class ContactVCF(grok.View): - grok.name('contact.vcf') - grok.context(IContactContent) - grok.require("zope2.View") +class ContactVCF(BrowserView): - def render(self): + def __call__(self): self.request.response.setHeader( 'Content-type', "text/x-vCard; charset=utf-8") content_disposition = 'attachment; filename=%s.vcf' % (self.context.id) diff --git a/src/collective/contact/core/configure.zcml b/src/collective/contact/core/configure.zcml index 5123e97a..60643641 100644 --- a/src/collective/contact/core/configure.zcml +++ b/src/collective/contact/core/configure.zcml @@ -1,66 +1,39 @@ - - - - + + + + - - - - - - - - - - - - + + + - - - - + + + + + + + diff --git a/src/collective/contact/core/content/configure.zcml b/src/collective/contact/core/content/configure.zcml new file mode 100644 index 00000000..bf3b8b42 --- /dev/null +++ b/src/collective/contact/core/content/configure.zcml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/src/collective/contact/core/content/directory.py b/src/collective/contact/core/content/directory.py index de854e83..c4d9a39f 100644 --- a/src/collective/contact/core/content/directory.py +++ b/src/collective/contact/core/content/directory.py @@ -1,16 +1,14 @@ -from zope.interface import Interface, implements -from zope import schema - -from five import grok - +# -*- coding: utf-8 -*- +from collective.contact.core import _ +from collective.z3cform.datagridfield import DataGridFieldFactory +from collective.z3cform.datagridfield import DictRow from plone.autoform.directives import widget from plone.dexterity.content import Container from plone.dexterity.schema import DexteritySchemaPolicy from plone.supermodel import model - -from collective.z3cform.datagridfield import DataGridFieldFactory, DictRow - -from collective.contact.core import _ +from zope import schema +from zope.interface import implementer +from zope.interface import Interface class INameTokenTableRowSchema(Interface): @@ -29,33 +27,31 @@ class IDirectory(model.Schema): title=_("Position types"), value_type=DictRow(title=_(u'Position'), schema=INameTokenTableRowSchema) - ) + ) widget('position_types', DataGridFieldFactory, allow_reorder=True) organization_types = schema.List( title=_("Organization types"), value_type=DictRow(title=_(u'Organization'), schema=INameTokenTableRowSchema) - ) + ) widget('organization_types', DataGridFieldFactory, allow_reorder=True) organization_levels = schema.List( title=_("Organization levels"), value_type=DictRow(title=_(u'Organization level'), schema=INameTokenTableRowSchema) - ) + ) widget('organization_levels', DataGridFieldFactory, allow_reorder=True) +@implementer(IDirectory) class Directory(Container): """Directory content type""" - implements(IDirectory) -class DirectorySchemaPolicy(grok.GlobalUtility, - DexteritySchemaPolicy): +class DirectorySchemaPolicy(DexteritySchemaPolicy): """Schema policy for Directory content type""" - grok.name("schema_policy_directory") def bases(self, schemaName, tree): return (IDirectory, ) diff --git a/src/collective/contact/core/content/held_position.py b/src/collective/contact/core/content/held_position.py index 2dd11ae9..1f34aea2 100644 --- a/src/collective/contact/core/content/held_position.py +++ b/src/collective/contact/core/content/held_position.py @@ -1,16 +1,15 @@ +# -*- coding: utf-8 -*- +from collective.contact.core.browser.contactable import Contactable +from collective.contact.core.interfaces import IHeldPosition from ComputedAttribute import ComputedAttribute -from Products.CMFPlone.utils import normalizeString, safe_unicode - -from z3c.form.interfaces import NO_VALUE -from zope.interface import implements - -from five import grok - -from plone.dexterity.schema import DexteritySchemaPolicy from plone.dexterity.content import Container +from plone.dexterity.schema import DexteritySchemaPolicy +from Products.CMFPlone.utils import normalizeString +from Products.CMFPlone.utils import safe_unicode +from z3c.form.interfaces import NO_VALUE +from zope.interface import implementer -from collective.contact.core.browser.contactable import Contactable -from collective.contact.core.interfaces import IHeldPosition +import six def acqproperty(func): @@ -21,8 +20,6 @@ def acqproperty(func): class HeldPositionContactableAdapter(Contactable): """Contactable adapter for HeldPosition content type""" - grok.context(IHeldPosition) - @property def person(self): return self.context.get_person() @@ -37,13 +34,12 @@ def organizations(self): return organization and organization.get_organizations_chain() or [] +@implementer(IHeldPosition) class HeldPosition(Container): """HeldPosition content type Links a Position or an Organization to a person in an organization """ - implements(IHeldPosition) - use_parent_address = NO_VALUE parent_address = NO_VALUE @@ -55,6 +51,11 @@ def get_title(self): title = property(get_title, set_title) + def get_label(self): + """Returns the held_position label. + Made to be overrided.""" + return self.label + def get_person(self): """Returns the person who holds the position """ @@ -92,17 +93,30 @@ def Title(self, separator=u' / ', first_index=0): position = self.get_position() organization = self.get_organization() - if position is None and not self.label: - return "(%s)" % organization.get_full_title(separator=separator, first_index=first_index).encode('utf8') + label = self.get_label() + if position is None and not label: + if six.PY2: + return "(%s)" % organization.get_full_title(separator=separator, first_index=first_index).encode('utf8') + else: + return "(%s)" % organization.get_full_title(separator=separator, first_index=first_index) + # we display the position title or the label - position_title = self.label or position.title - return "%s (%s)" % (position_title.encode('utf8'), - organization.get_full_title(separator=separator, first_index=first_index).encode('utf8')) + position_title = label or position.title + if six.PY2: + return "%s (%s)" % (position_title.encode('utf8'), + organization.get_full_title(separator=separator, first_index=first_index).encode('utf8')) + else: + return "%s (%s)" % (position_title, + organization.get_full_title(separator=separator, first_index=first_index)) def get_full_title(self, separator=u' / ', first_index=0): """Returns the 'title' and include person name.""" person_name = self.get_person_title() - title = self.Title(separator=separator, first_index=first_index).decode('utf8') + if six.PY2: + title = self.Title(separator=separator, + first_index=first_index).decode('utf8') + else: + title = self.Title(separator=separator, first_index=first_index) if title[0:1] == '(': return u"%s %s" % (person_name, title) else: @@ -141,11 +155,8 @@ def photo(self): return person.photo -class HeldPositionSchemaPolicy(grok.GlobalUtility, - DexteritySchemaPolicy): +class HeldPositionSchemaPolicy(DexteritySchemaPolicy): """Schema policy for HeldPosition content type""" - grok.name("schema_policy_held_position") - def bases(self, schemaName, tree): return (IHeldPosition,) diff --git a/src/collective/contact/core/content/organization.py b/src/collective/contact/core/content/organization.py index 128d2e7e..3713261c 100644 --- a/src/collective/contact/core/content/organization.py +++ b/src/collective/contact/core/content/organization.py @@ -1,11 +1,10 @@ +# -*- coding: utf-8 -*- from Acquisition import aq_chain from Acquisition import aq_inner from collective.contact.core import _ from collective.contact.core import logger from collective.contact.core.browser.contactable import Contactable -from collective.contact.core.interfaces import IHeldPosition from collective.contact.widget.interfaces import IContactContent -from five import grok from plone import api from plone.app.textfield import RichText from plone.dexterity.content import Container @@ -16,17 +15,29 @@ from zc.relation.interfaces import ICatalog from zope import schema from zope.component import getUtility -from zope.interface import implements +from zope.interface import implementer from zope.intid.interfaces import IIntIds +class InvalidEnterpriseNumber(schema.ValidationError): + """Exception for invalid enterprise number""" + __doc__ = _(u"Enterprise number must contain only letters and numbers") + + +def validateEnterpriseNumber(value): + """Enterprise number validator""" + if not value.isalnum(): + raise InvalidEnterpriseNumber(value) + return True + + class IOrganization(model.Schema, IContactContent): """Interface for Organization content type""" activity = RichText( title=_("Activity"), required=False, - ) + ) organization_type = schema.Choice( title=_("Type or level"), @@ -36,7 +47,13 @@ class IOrganization(model.Schema, IContactContent): logo = NamedImage( title=_("Logo"), required=False, - ) + ) + + enterprise_number = schema.TextLine( + title=_(u"Enterprise (or VAT) number"), + required=False, + constraint=validateEnterpriseNumber, + ) def get_organizations_chain(self): """Returns the list of organizations and sub-organizations in this organization @@ -77,16 +94,14 @@ def get_held_positions(self): class OrganizationContactableAdapter(Contactable): """Contactable adapter for Organization content type""" - grok.context(IOrganization) - @property def organizations(self): return self.context.get_organizations_chain() +@implementer(IOrganization) class Organization(Container): """Organization content type""" - implements(IOrganization) def get_organizations_chain(self, first_index=0): """Returns the list of organizations and sub-organizations in this organization @@ -144,9 +159,9 @@ def get_held_positions(self): catalog = getUtility(ICatalog) orga_intid = intids.getId(self) contact_relations = catalog.findRelations( - {'to_id': orga_intid, - 'from_interfaces_flattened': IHeldPosition, - 'from_attribute': 'position'}) + {'to_id': orga_intid, + 'from_attribute': 'position'} + ) held_positions = [] for relation in contact_relations: held_position = relation.from_object @@ -159,11 +174,8 @@ def get_held_positions(self): return held_positions -class OrganizationSchemaPolicy(grok.GlobalUtility, - DexteritySchemaPolicy): +class OrganizationSchemaPolicy(DexteritySchemaPolicy): """Schema policy for Organization content type""" - grok.name("schema_policy_organization") - def bases(self, schemaName, tree): return (IOrganization,) diff --git a/src/collective/contact/core/content/person.py b/src/collective/contact/core/content/person.py index ebdb782d..831d1bb4 100644 --- a/src/collective/contact/core/content/person.py +++ b/src/collective/contact/core/content/person.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - from collective.contact.core import _ from collective.contact.core.browser.contactable import Contactable from collective.contact.core.interfaces import IContactable @@ -7,7 +6,6 @@ from collective.contact.core.interfaces import IHeldPosition from collective.contact.core.interfaces import IPersonHeldPositions from collective.contact.widget.interfaces import IContactContent -from five import grok from plone.autoform import directives as form from plone.dexterity.content import Container from plone.dexterity.schema import DexteritySchemaPolicy @@ -20,7 +18,9 @@ from zope import schema from zope.cachedescriptors.property import CachedProperty from zope.component import queryUtility -from zope.interface import implements +from zope.interface import implementer + +import six class IPerson(model.Schema, IContactContent): @@ -29,32 +29,32 @@ class IPerson(model.Schema, IContactContent): lastname = schema.TextLine( title=_("Lastname"), required=True - ) + ) firstname = schema.TextLine( title=_("Firstname"), required=False, - ) + ) gender = schema.Choice( title=_("Gender"), vocabulary="Genders", required=False, - ) + ) form.widget(gender=RadioFieldWidget) person_title = schema.TextLine( title=_("Person title"), description=_('help_person_title', u"Doctor, Mrs..."), required=False, - ) + ) photo = NamedImage( title=_("Photo"), required=False, - ) + ) signature = NamedImage( title=_("Signature"), description=_("Scanned signature"), required=False, - ) + ) def get_held_positions(self): """Returns held positions of this person @@ -64,8 +64,6 @@ def get_held_positions(self): class PersonContactableAdapter(Contactable): """Contactable adapter for Person content type""" - grok.context(IPerson) - @property def person(self): return self.context @@ -89,11 +87,10 @@ def organizations(self): return () +@implementer(IPerson) class Person(Container): """Person content type""" - implements(IPerson) - # plone.dexterity.content.Content.__getattr__ retrieve the field.default # so step 1.2.1 in z3c.form.widget.py returns something instead of NO_VALUE # then IValue adapter is not looked up... @@ -125,7 +122,10 @@ def get_title(self, include_person_title=True): def Title(self): # must return utf8 and not unicode (Title() from basic behavior return utf8) # attributes are stored as unicode - return self.get_title().encode('utf-8') + if six.PY2: + return self.get_title().encode('utf-8') + else: + return self.get_title() def get_sortable_title(self): if self.firstname is None: @@ -144,11 +144,8 @@ def get_full_name(self): return u' '.join([x for x in (self.firstname, self.lastname) if x]) -class PersonSchemaPolicy(grok.GlobalUtility, - DexteritySchemaPolicy): +class PersonSchemaPolicy(DexteritySchemaPolicy): """Schema policy for Person content type""" - grok.name("schema_policy_person") - def bases(self, schemaName, tree): return (IPerson, ) diff --git a/src/collective/contact/core/content/position.py b/src/collective/contact/core/content/position.py index 7a8d89cb..0010cc84 100644 --- a/src/collective/contact/core/content/position.py +++ b/src/collective/contact/core/content/position.py @@ -1,21 +1,17 @@ -from zope.interface import implements -from zope import schema -from z3c.form.interfaces import NO_VALUE - -from five import grok - -from plone.dexterity.content import Container -from plone.supermodel import model -from plone.dexterity.schema import DexteritySchemaPolicy - +# -*- coding: utf-8 -*- from collective.contact.core import _ +from collective.contact.core import logger from collective.contact.core.browser.contactable import Contactable from collective.contact.widget.interfaces import IContactContent +from plone.dexterity.content import Container +from plone.dexterity.schema import DexteritySchemaPolicy +from plone.supermodel import model +from z3c.form.interfaces import NO_VALUE +from zc.relation.interfaces import ICatalog +from zope import schema from zope.component import getUtility +from zope.interface import implementer from zope.intid.interfaces import IIntIds -from zc.relation.interfaces import ICatalog -from collective.contact.core.interfaces import IHeldPosition -from collective.contact.core import logger class IPosition(model.Schema, IContactContent): @@ -24,7 +20,7 @@ class IPosition(model.Schema, IContactContent): position_type = schema.Choice( title=_("Type"), vocabulary="PositionTypes", - ) + ) def get_organization(self): """Returns the organization to which the position is linked""" @@ -39,8 +35,6 @@ def get_full_title(self): class PositionContactableAdapter(Contactable): """Contactable adapter for Position content type""" - grok.context(IPosition) - @property def position(self): return self.context @@ -51,11 +45,10 @@ def organizations(self): return organization.get_organizations_chain() +@implementer(IPosition) class Position(Container): """Position content type""" - implements(IPosition) - use_parent_address = NO_VALUE parent_address = NO_VALUE @@ -87,24 +80,22 @@ def get_held_positions(self): catalog = getUtility(ICatalog) position_intid = intids.getId(self) contact_relations = catalog.findRelations( - {'to_id': position_intid, - 'from_interfaces_flattened': IHeldPosition, - 'from_attribute': 'position'}) + {'to_id': position_intid, + 'from_attribute': 'position'} + ) held_positions = [] for relation in contact_relations: held_position = relation.from_object if not held_position: - logger.error("from_object missing for relation from held_position to position %s: %s", self, relation.__dict__) + logger.error("from_object missing for relation from held_position to position %s: %s", self, + relation.__dict__) continue held_positions.append(held_position) return held_positions -class PositionSchemaPolicy(grok.GlobalUtility, - DexteritySchemaPolicy): +class PositionSchemaPolicy(DexteritySchemaPolicy): """Schema policy for Position content type""" - grok.name("schema_policy_position") - def bases(self, schemaName, tree): return (IPosition,) diff --git a/src/collective/contact/core/fields.py b/src/collective/contact/core/fields.py deleted file mode 100644 index 4355a0b8..00000000 --- a/src/collective/contact/core/fields.py +++ /dev/null @@ -1,6 +0,0 @@ -import plone.supermodel.exportimport -import z3c.relationfield.schema - - -# Field import/export handlers -RelationChoiceHandler = plone.supermodel.exportimport.ChoiceHandler(z3c.relationfield.schema.RelationChoice) diff --git a/src/collective/contact/core/fti.py b/src/collective/contact/core/fti.py index 84827110..f2bc0f5b 100644 --- a/src/collective/contact/core/fti.py +++ b/src/collective/contact/core/fti.py @@ -1,17 +1,16 @@ -from zope.interface import implements - -from plone.dexterity.interfaces import IDexterityFTI from plone.dexterity.fti import DexterityFTI -from plone.supermodel import loadString, loadFile +from plone.dexterity.interfaces import IDexterityFTI +from plone.supermodel import loadFile +from plone.supermodel import loadString from plone.supermodel.model import Model +from zope.interface import implementer +@implementer(IDexterityFTI) class DexterityConfigurablePolicyFTI(DexterityFTI): """A Configurable policy FTI """ - implements(IDexterityFTI) - meta_type = "Dexterity configurable policy FTI" _properties = DexterityFTI._properties + ( @@ -20,7 +19,7 @@ class DexterityConfigurablePolicyFTI(DexterityFTI): 'mode': 'w', 'label': 'Schema policy', 'description': 'Schema policy' - }, + }, ) schema_policy = u'dexterity' @@ -37,7 +36,8 @@ def lookupModel(self): schema = self.lookupSchema() return Model({u"": schema}) - raise ValueError("Neither model source, nor model file, nor schema is specified in FTI %s" % self.getId()) + raise ValueError( + "Neither model source, nor model file, nor schema is specified in FTI %s" % self.getId()) # # Base class overrides diff --git a/src/collective/contact/core/indexers.py b/src/collective/contact/core/indexers.py index c13bcd1e..4a0ac0b4 100644 --- a/src/collective/contact/core/indexers.py +++ b/src/collective/contact/core/indexers.py @@ -1,14 +1,42 @@ +# -*- coding: utf-8 -*- +from collective.contact.core.behaviors import ADDRESS_FIELDS +from collective.contact.core.behaviors import IContactDetails +from collective.contact.core.behaviors import IRelatedOrganizations +from collective.contact.core.content.organization import IOrganization +from collective.contact.core.content.person import IPerson +from collective.contact.core.content.position import IPosition +from collective.contact.core.interfaces import IContactable +from collective.contact.core.interfaces import IHeldPosition +from collective.contact.widget.interfaces import IContactContent from datetime import date from plone import api from plone.indexer import indexer from Products.CMFPlone.utils import safe_unicode -from collective.contact.core.content.organization import IOrganization -from collective.contact.core.content.position import IPosition -from collective.contact.core.content.person import IPerson -from collective.contact.core.behaviors import IContactDetails -from collective.contact.core.behaviors import IRelatedOrganizations -from collective.contact.core.interfaces import IHeldPosition + +@indexer(IContactContent) +def contact_email(contact): + email = IContactDetails(contact).email + return email or u'' + + +@indexer(IContactContent) +def contact_source(contact): + csmc = api.portal.get_registry_record('collective.contact.core.interfaces.IContactCoreParameters.' + 'contact_source_metadata_content', default=u'{gft}') + variables = {'gft': contact.get_full_title()} + contactable = IContactable(contact) + details = contactable.get_contact_details() + address = details.pop('address') + for fld in ADDRESS_FIELDS: + address.setdefault(fld, '') + variables.update(address) + variables.update(details) + try: + return csmc.format(**variables) + except Exception: + pass + return u'' @indexer(IOrganization) @@ -21,6 +49,9 @@ def organization_searchable_text(organization): words += organization.get_organizations_titles() + if organization.enterprise_number is not None: + words.append(organization.enterprise_number) + email = IContactDetails(organization).email if email: words.append(email) @@ -40,8 +71,9 @@ def held_position_searchable_text(obj): if organization: indexed_fields.extend(organization.get_organizations_titles()) - if obj.label: - indexed_fields.append(obj.label) + label = obj.get_label() + if label: + indexed_fields.append(label) email = IContactDetails(obj).email if email: @@ -109,4 +141,4 @@ def end_date(obj): if obj.end_date: return obj.end_date # if empty we return future date - return date(2100, 01, 01) + return date(2100, 1, 1) diff --git a/src/collective/contact/core/interfaces.py b/src/collective/contact/core/interfaces.py index 41f43124..c76f26e6 100755 --- a/src/collective/contact/core/interfaces.py +++ b/src/collective/contact/core/interfaces.py @@ -1,13 +1,11 @@ -from zope.interface import Interface -from zope import schema - -from plone.namedfile.field import NamedImage -from plone.supermodel import model - from collective.contact.core import _ from collective.contact.core.schema import ContactChoice from collective.contact.widget.interfaces import IContactContent from collective.contact.widget.source import ContactSourceBinder +from plone.namedfile.field import NamedImage +from plone.supermodel import model +from zope import schema +from zope.interface import Interface class IContactable(Interface): @@ -56,10 +54,23 @@ class IContactCoreParameters(Interface): required=False, default=True) display_contact_photo_on_organization_view = schema.Bool( - title=_(u"Display contact photo on organization view (instead person content type icon)."), + title=_( + u"Display contact photo on organization view (instead person content type icon)."), description=u"", required=False, default=True) + display_below_content_title_on_views = schema.Bool( + title=_(u"Display belowcontenttitle viewlet on contact views."), + description=u"", + required=False, default=False) + + contact_source_metadata_content = schema.TextLine( + title=_(u"Choose information displayed after a search in contact widget."), + description=u"Format string containing variables like : {gft} (full title) , {number} , {street} , " + u"{additional_address_details} , {zip_code} , {city} , {region} , {country}, {email}, {phone}, " + u"{cell_phone}, {fax}, {website}, {im_handle}", + required=True, default=u'{gft}') + class IPersonHeldPositions(Interface): """Adapter interface to get ordered positions of a person diff --git a/src/collective/contact/core/locales/collective.contact.core.pot b/src/collective/contact/core/locales/collective.contact.core.pot index b37db48b..f25cae18 100644 --- a/src/collective/contact/core/locales/collective.contact.core.pot +++ b/src/collective/contact/core/locales/collective.contact.core.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -46,11 +46,11 @@ msgstr "" msgid "Additional address details" msgstr "" -#: ../interfaces.py:93 +#: ../interfaces.py:100 msgid "Additional label" msgstr "" -#: ../interfaces.py:94 +#: ../interfaces.py:101 msgid "Additional label with information that does not appear on position label" msgstr "" @@ -73,6 +73,10 @@ msgstr "" msgid "Cell phone number" msgstr "" +#: ../interfaces.py:64 +msgid "Choose information displayed after a search in contact widget." +msgstr "" + #: ../behaviors.py:250 msgid "City" msgstr "" @@ -99,7 +103,7 @@ msgid "Country" msgstr "" #: ../browser/addcontact.py:65 -#: ../browser/templates/organization.pt:74 +#: ../browser/templates/organization.pt:72 #: ../browser/templates/position.pt:41 msgid "Create ${name}" msgstr "" @@ -136,10 +140,19 @@ msgstr "" #: ../browser/basefields/templates/held_position.pt:28 #: ../browser/templates/heldpositions.pt:47 -#: ../interfaces.py:102 +#: ../interfaces.py:109 msgid "End date" msgstr "" +#: ../browser/basefields/templates/organization.pt:42 +#: ../content/organization.py:53 +msgid "Enterprise (or VAT) number" +msgstr "" + +#: ../content/organization.py:24 +msgid "Enterprise number must contain only letters and numbers" +msgstr "" + #: ../behaviors.py:161 msgid "Fax" msgstr "" @@ -250,7 +263,7 @@ msgstr "" msgid "Name" msgstr "" -#: ../browser/templates/organization.pt:59 +#: ../browser/templates/organization.pt:57 msgid "Not assigned" msgstr "" @@ -280,11 +293,11 @@ msgstr "" msgid "Organization types" msgstr "" -#: ../interfaces.py:88 +#: ../interfaces.py:95 msgid "Organization/Position" msgstr "" -#: ../browser/templates/contact.pt:17 +#: ../browser/templates/contact.pt:16 #: ../browser/templates/directory.pt:22 msgid "Organizations" msgstr "" @@ -297,7 +310,7 @@ msgstr "" msgid "Other contacts in this organization:" msgstr "" -#: ../browser/templates/organization.pt:19 +#: ../browser/templates/organization.pt:17 #: ../browser/templates/position.pt:17 msgid "Parent organizations" msgstr "" @@ -325,7 +338,7 @@ msgid "Phone number" msgstr "" #: ../content/person.py:50 -#: ../interfaces.py:106 +#: ../interfaces.py:113 msgid "Photo" msgstr "" @@ -347,7 +360,7 @@ msgstr "" msgid "Positions" msgstr "" -#: ../browser/templates/organization.pt:39 +#: ../browser/templates/organization.pt:37 msgid "Positions in this organization" msgstr "" @@ -387,7 +400,7 @@ msgstr "" #: ../browser/basefields/templates/held_position.pt:22 #: ../browser/templates/heldpositions.pt:42 -#: ../interfaces.py:98 +#: ../interfaces.py:105 msgid "Start date" msgstr "" @@ -458,11 +471,11 @@ msgid "collective.contact.core tests" msgstr "" #. Default: ":" -#: ../browser/templates/organization.pt:45 +#: ../browser/templates/organization.pt:43 msgid "colon" msgstr "" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "from" msgstr "" @@ -485,6 +498,6 @@ msgstr "" msgid "organization/position" msgstr "" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "to" msgstr "" diff --git a/src/collective/contact/core/locales/de/LC_MESSAGES/collective.contact.core.po b/src/collective/contact/core/locales/de/LC_MESSAGES/collective.contact.core.po index 09702fe8..f0c28a1c 100644 --- a/src/collective/contact/core/locales/de/LC_MESSAGES/collective.contact.core.po +++ b/src/collective/contact/core/locales/de/LC_MESSAGES/collective.contact.core.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: collective.contact.core\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: 2016-11-16 11:24+0100\n" "Last-Translator: Cédric Messiant \n" "Language-Team: French\n" @@ -45,11 +45,11 @@ msgstr "Hinzufügen" msgid "Additional address details" msgstr "Zusätzliche Details Adresse" -#: ../interfaces.py:93 +#: ../interfaces.py:100 msgid "Additional label" msgstr "Zusätzliches Feld" -#: ../interfaces.py:94 +#: ../interfaces.py:101 msgid "Additional label with information that does not appear on position label" msgstr "Zusätzliches Feld mit Information die nicht im Positionsfeld aufscheint" @@ -72,6 +72,10 @@ msgstr "Mobil" msgid "Cell phone number" msgstr "Mobil" +#: ../interfaces.py:64 +msgid "Choose information displayed after a search in contact widget." +msgstr "" + #: ../behaviors.py:250 msgid "City" msgstr "Stadt" @@ -98,7 +102,7 @@ msgid "Country" msgstr "Land" #: ../browser/addcontact.py:65 -#: ../browser/templates/organization.pt:74 +#: ../browser/templates/organization.pt:72 #: ../browser/templates/position.pt:41 msgid "Create ${name}" msgstr "Eingabe ${name}" @@ -135,10 +139,19 @@ msgstr "Email" #: ../browser/basefields/templates/held_position.pt:28 #: ../browser/templates/heldpositions.pt:47 -#: ../interfaces.py:102 +#: ../interfaces.py:109 msgid "End date" msgstr "Enddatum" +#: ../browser/basefields/templates/organization.pt:42 +#: ../content/organization.py:53 +msgid "Enterprise (or VAT) number" +msgstr "" + +#: ../content/organization.py:24 +msgid "Enterprise number must contain only letters and numbers" +msgstr "" + #: ../behaviors.py:161 msgid "Fax" msgstr "Fax" @@ -249,7 +262,7 @@ msgstr "" msgid "Name" msgstr "Name" -#: ../browser/templates/organization.pt:59 +#: ../browser/templates/organization.pt:57 msgid "Not assigned" msgstr "Nicht zugewiesen" @@ -279,11 +292,11 @@ msgstr "Organisationstyp" msgid "Organization types" msgstr "Organisationstypen" -#: ../interfaces.py:88 +#: ../interfaces.py:95 msgid "Organization/Position" msgstr "Organisation/Position" -#: ../browser/templates/contact.pt:17 +#: ../browser/templates/contact.pt:16 #: ../browser/templates/directory.pt:22 msgid "Organizations" msgstr "Organisationen" @@ -296,7 +309,7 @@ msgstr "Organisationen in dieser Organisation" msgid "Other contacts in this organization:" msgstr "Weitere Kontakte in dieser Organisation" -#: ../browser/templates/organization.pt:19 +#: ../browser/templates/organization.pt:17 #: ../browser/templates/position.pt:17 msgid "Parent organizations" msgstr "Übergeordnete Organisationen" @@ -324,7 +337,7 @@ msgid "Phone number" msgstr "Telefon" #: ../content/person.py:50 -#: ../interfaces.py:106 +#: ../interfaces.py:113 msgid "Photo" msgstr "Foto" @@ -346,7 +359,7 @@ msgstr "Position-Typen" msgid "Positions" msgstr "Positionen" -#: ../browser/templates/organization.pt:39 +#: ../browser/templates/organization.pt:37 msgid "Positions in this organization" msgstr "Positionen in dieser Organisation" @@ -386,7 +399,7 @@ msgstr "" #: ../browser/basefields/templates/held_position.pt:22 #: ../browser/templates/heldpositions.pt:42 -#: ../interfaces.py:98 +#: ../interfaces.py:105 msgid "Start date" msgstr "Beginndatum" @@ -457,11 +470,11 @@ msgid "collective.contact.core tests" msgstr "" #. Default: ":" -#: ../browser/templates/organization.pt:45 +#: ../browser/templates/organization.pt:43 msgid "colon" -msgstr "\":\"" +msgstr ":" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "from" msgstr "von" @@ -484,6 +497,6 @@ msgstr "Wenn das Element nicht vorhanden ist, kann es zur Datenbank hinzugefügt msgid "organization/position" msgstr "Organisation/Position" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "to" msgstr "bis" diff --git a/src/collective/contact/core/locales/de/LC_MESSAGES/plone.po b/src/collective/contact/core/locales/de/LC_MESSAGES/plone.po index 751656b2..ee09a6f3 100644 --- a/src/collective/contact/core/locales/de/LC_MESSAGES/plone.po +++ b/src/collective/contact/core/locales/de/LC_MESSAGES/plone.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: collective.contact.core\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: 2016-11-16 11:25+0100\n" "Last-Translator: Cédric Messiant \n" "Language-Team: Ecréall\n" diff --git a/src/collective/contact/core/locales/fr/LC_MESSAGES/collective.contact.core.po b/src/collective/contact/core/locales/fr/LC_MESSAGES/collective.contact.core.po index 20d6a4a1..3530788d 100644 --- a/src/collective/contact/core/locales/fr/LC_MESSAGES/collective.contact.core.po +++ b/src/collective/contact/core/locales/fr/LC_MESSAGES/collective.contact.core.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: collective.contact.core\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: 2017-03-30 15:56+0200\n" "Last-Translator: Harald Friessnegger \n" "Language-Team: German \n" @@ -19,7 +19,7 @@ msgstr "" #: ../browser/addcontact.py:112 msgid "${name} (${position}" -msgstr "" +msgstr "${name} (${position}" #: ../browser/addcontact.py:333 msgid "A contact is a position held by a person in an organization" @@ -46,11 +46,11 @@ msgstr "Ajouter" msgid "Additional address details" msgstr "Complément d'adresse" -#: ../interfaces.py:93 +#: ../interfaces.py:100 msgid "Additional label" msgstr "Intitulé" -#: ../interfaces.py:94 +#: ../interfaces.py:101 msgid "Additional label with information that does not appear on position label" msgstr "Intitulé complétant l'intitulé de poste" @@ -73,6 +73,10 @@ msgstr "Téléphone portable" msgid "Cell phone number" msgstr "Numéro de téléphone portable " +#: ../interfaces.py:64 +msgid "Choose information displayed after a search in contact widget." +msgstr "Choisir les informations affichées lors d'une recherche dans un champ contact." + #: ../behaviors.py:250 msgid "City" msgstr "Ville" @@ -92,14 +96,14 @@ msgstr "Informations de contact (numéro de téléphone, adresse, email, ...)" #: ../behaviors.zcml:28 msgid "Contact have a birthday." -msgstr "" +msgstr "Ajout d'un champ anniversaire" #: ../behaviors.py:240 msgid "Country" msgstr "Pays" #: ../browser/addcontact.py:65 -#: ../browser/templates/organization.pt:74 +#: ../browser/templates/organization.pt:72 #: ../browser/templates/position.pt:41 msgid "Create ${name}" msgstr "Créer ${name}" @@ -119,7 +123,7 @@ msgstr "Annuaire" #: ../interfaces.py:59 msgid "Display contact photo on organization view (instead person content type icon)." -msgstr "" +msgstr "Afficher la photo du contact dans la vue de l'organisation (à la place de l'icône 'personne')" #: ../interfaces.py:44 msgid "Display person title in displayed person's title." @@ -136,10 +140,19 @@ msgstr "Courriel " #: ../browser/basefields/templates/held_position.pt:28 #: ../browser/templates/heldpositions.pt:47 -#: ../interfaces.py:102 +#: ../interfaces.py:109 msgid "End date" msgstr "Date de fin " +#: ../browser/basefields/templates/organization.pt:42 +#: ../content/organization.py:53 +msgid "Enterprise (or VAT) number" +msgstr "Numéro d'entreprise (ou de TVA)" + +#: ../content/organization.py:24 +msgid "Enterprise number must contain only letters and numbers" +msgstr "Le numéro d'entreprise ne peut contenir que des lettres et des chiffres" + #: ../behaviors.py:161 msgid "Fax" msgstr "Fax" @@ -167,7 +180,7 @@ msgstr "Localisation" #: ../behaviors.zcml:22 msgid "Global positioning (latitude and longitude)." -msgstr "" +msgstr "Ajout des champs latitude et longitude" #: ../profiles/default/types/held_position.xml #: ../upgrades/profiles/v2/types/held_position.xml @@ -180,11 +193,11 @@ msgstr "Identifiant de messagerie instantanée " #: ../configure.zcml:58 msgid "Installs test data for the collective.contact.core package" -msgstr "" +msgstr "Installe les données de tests de collective.contact.core" #: ../configure.zcml:50 msgid "Installs the collective.contact.core package" -msgstr "" +msgstr "Installe le package collective.contact.core" #: ../behaviors.py:171 msgid "Instant messenger handle" @@ -220,23 +233,23 @@ msgstr "Masculin" #: ../upgrades/configure.zcml:14 msgid "Migration profile for collective.contact.core to 2" -msgstr "" +msgstr "Migration de collective.contact.core vers version 2" #: ../upgrades/configure.zcml:54 msgid "Migration profile for collective.contact.core to 6" -msgstr "" +msgstr "Migration de collective.contact.core vers version 6" #: ../upgrades/configure.zcml:70 msgid "Migration profile for collective.contact.core to 7" -msgstr "" +msgstr "Migration de collective.contact.core vers version 7" #: ../upgrades/configure.zcml:86 msgid "Migration profile for collective.contact.core to 8" -msgstr "" +msgstr "Migration de collective.contact.core vers version 8" #: ../upgrades/configure.zcml:102 msgid "Migration profile for collective.contact.core to 9" -msgstr "" +msgstr "Migration de collective.contact.core vers version 9" #: ../browser/person_title_mapping.py:24 msgid "Mr" @@ -250,7 +263,7 @@ msgstr "Madame" msgid "Name" msgstr "Nom" -#: ../browser/templates/organization.pt:59 +#: ../browser/templates/organization.pt:57 msgid "Not assigned" msgstr "Non assigné" @@ -280,11 +293,11 @@ msgstr "Type d'organisation " msgid "Organization types" msgstr "Types d'organisations" -#: ../interfaces.py:88 +#: ../interfaces.py:95 msgid "Organization/Position" msgstr "Organisation/Fonction" -#: ../browser/templates/contact.pt:17 +#: ../browser/templates/contact.pt:16 #: ../browser/templates/directory.pt:22 msgid "Organizations" msgstr "Organisations " @@ -297,7 +310,7 @@ msgstr "Organisations dans cette organisation " msgid "Other contacts in this organization:" msgstr "Autres contacts dans cette organisation :" -#: ../browser/templates/organization.pt:19 +#: ../browser/templates/organization.pt:17 #: ../browser/templates/position.pt:17 msgid "Parent organizations" msgstr "Organisations parentes " @@ -325,7 +338,7 @@ msgid "Phone number" msgstr "Numéro de téléphone " #: ../content/person.py:50 -#: ../interfaces.py:106 +#: ../interfaces.py:113 msgid "Photo" msgstr "Photo" @@ -347,7 +360,7 @@ msgstr "Types de fonctions" msgid "Positions" msgstr "Fonctions occupées" -#: ../browser/templates/organization.pt:39 +#: ../browser/templates/organization.pt:37 msgid "Positions in this organization" msgstr "Fonctions dans cette organisation " @@ -387,13 +400,13 @@ msgstr "Signature" #: ../browser/basefields/templates/held_position.pt:22 #: ../browser/templates/heldpositions.pt:42 -#: ../interfaces.py:98 +#: ../interfaces.py:105 msgid "Start date" msgstr "Date de début " #: ../testing.zcml:18 msgid "Steps to ease tests of collective.contact.core" -msgstr "" +msgstr "Steps pour faciliter les tests de collective.contact.core" #: ../behaviors.py:255 msgid "Street" @@ -429,7 +442,7 @@ msgstr "Utiliser l'adresse de l'entité d'appartenance" #: ../behaviors.zcml:34 msgid "We can attach organizations on content." -msgstr "" +msgstr "Lier des organisations au contenu" #: ../behaviors.py:166 #: ../browser/templates/contactdetails.pt:50 @@ -447,22 +460,22 @@ msgstr "${street} ${number}" #: ../configure.zcml:50 msgid "collective.contact.core" -msgstr "" +msgstr "collective.contact.core" #: ../configure.zcml:58 msgid "collective.contact.core test data" -msgstr "" +msgstr "Données de tests de collective.contact.core" #: ../testing.zcml:18 msgid "collective.contact.core tests" -msgstr "" +msgstr "Tests de collective.contact.core" #. Default: ":" -#: ../browser/templates/organization.pt:45 +#: ../browser/templates/organization.pt:43 msgid "colon" msgstr " :" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "from" msgstr "de" @@ -485,6 +498,6 @@ msgstr "Si l'élément n'existe pas, vous pouvez l'ajouter à la base :" msgid "organization/position" msgstr "organisation/fonction" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "to" msgstr "à" diff --git a/src/collective/contact/core/locales/fr/LC_MESSAGES/plone.po b/src/collective/contact/core/locales/fr/LC_MESSAGES/plone.po index 3359c982..613e0134 100644 --- a/src/collective/contact/core/locales/fr/LC_MESSAGES/plone.po +++ b/src/collective/contact/core/locales/fr/LC_MESSAGES/plone.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: collective.contact.core\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: 2013-03-11 16:38+0100\n" "Last-Translator: Cédric Messiant \n" "Language-Team: Ecréall\n" diff --git a/src/collective/contact/core/locales/it/LC_MESSAGES/collective.contact.core.po b/src/collective/contact/core/locales/it/LC_MESSAGES/collective.contact.core.po index ba80dd78..0c433a71 100644 --- a/src/collective/contact/core/locales/it/LC_MESSAGES/collective.contact.core.po +++ b/src/collective/contact/core/locales/it/LC_MESSAGES/collective.contact.core.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: 2016-10-04 18:45+0200\n" "Last-Translator: Luca Fabbri \n" "Language-Team: \n" @@ -48,11 +48,11 @@ msgstr "Aggiungi" msgid "Additional address details" msgstr "Dettagli aggiuntivi dell'indirizzo" -#: ../interfaces.py:93 +#: ../interfaces.py:100 msgid "Additional label" msgstr "Etichetta aggiuntiva" -#: ../interfaces.py:94 +#: ../interfaces.py:101 msgid "Additional label with information that does not appear on position label" msgstr "Etichetta aggiuntiva con informazioni che non compaiono nell'etichetta della posizione" @@ -75,6 +75,10 @@ msgstr "Telefono cellulare" msgid "Cell phone number" msgstr "Telefono cellulare" +#: ../interfaces.py:64 +msgid "Choose information displayed after a search in contact widget." +msgstr "" + #: ../behaviors.py:250 msgid "City" msgstr "Città" @@ -101,7 +105,7 @@ msgid "Country" msgstr "Paese" #: ../browser/addcontact.py:65 -#: ../browser/templates/organization.pt:74 +#: ../browser/templates/organization.pt:72 #: ../browser/templates/position.pt:41 msgid "Create ${name}" msgstr "Crea ${name}" @@ -138,10 +142,19 @@ msgstr "E-mail" #: ../browser/basefields/templates/held_position.pt:28 #: ../browser/templates/heldpositions.pt:47 -#: ../interfaces.py:102 +#: ../interfaces.py:109 msgid "End date" msgstr "Data fine" +#: ../browser/basefields/templates/organization.pt:42 +#: ../content/organization.py:53 +msgid "Enterprise (or VAT) number" +msgstr "" + +#: ../content/organization.py:24 +msgid "Enterprise number must contain only letters and numbers" +msgstr "" + #: ../behaviors.py:161 msgid "Fax" msgstr "Fax" @@ -252,7 +265,7 @@ msgstr "" msgid "Name" msgstr "Nome" -#: ../browser/templates/organization.pt:59 +#: ../browser/templates/organization.pt:57 msgid "Not assigned" msgstr "Non assegnata" @@ -282,11 +295,11 @@ msgstr "Tipo organizzazione" msgid "Organization types" msgstr "Tipi di organizzazione" -#: ../interfaces.py:88 +#: ../interfaces.py:95 msgid "Organization/Position" msgstr "Organizzazione/Posizione" -#: ../browser/templates/contact.pt:17 +#: ../browser/templates/contact.pt:16 #: ../browser/templates/directory.pt:22 msgid "Organizations" msgstr "Organizzazioni" @@ -299,7 +312,7 @@ msgstr "Organizzazioni in questa organizzazione" msgid "Other contacts in this organization:" msgstr "Altri contatti in questa organizzazione:" -#: ../browser/templates/organization.pt:19 +#: ../browser/templates/organization.pt:17 #: ../browser/templates/position.pt:17 msgid "Parent organizations" msgstr "Organizzazione padre" @@ -327,7 +340,7 @@ msgid "Phone number" msgstr "Numero di telefono" #: ../content/person.py:50 -#: ../interfaces.py:106 +#: ../interfaces.py:113 msgid "Photo" msgstr "Foto" @@ -349,7 +362,7 @@ msgstr "Tipi di posizione" msgid "Positions" msgstr "Posizioni" -#: ../browser/templates/organization.pt:39 +#: ../browser/templates/organization.pt:37 msgid "Positions in this organization" msgstr "Posizione in questa organizzazione" @@ -389,7 +402,7 @@ msgstr "" #: ../browser/basefields/templates/held_position.pt:22 #: ../browser/templates/heldpositions.pt:42 -#: ../interfaces.py:98 +#: ../interfaces.py:105 msgid "Start date" msgstr "Datas inizio" @@ -460,11 +473,11 @@ msgid "collective.contact.core tests" msgstr "" #. Default: ":" -#: ../browser/templates/organization.pt:45 +#: ../browser/templates/organization.pt:43 msgid "colon" msgstr ":" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "from" msgstr "Da" @@ -487,6 +500,6 @@ msgstr "Se la voce non esiste, puoi aggiungerla al database:" msgid "organization/position" msgstr "organizzazione/posizione" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "to" msgstr "A" diff --git a/src/collective/contact/core/locales/it/LC_MESSAGES/plone.po b/src/collective/contact/core/locales/it/LC_MESSAGES/plone.po index 1154837d..c72fb294 100644 --- a/src/collective/contact/core/locales/it/LC_MESSAGES/plone.po +++ b/src/collective/contact/core/locales/it/LC_MESSAGES/plone.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: Luca Fabbri \n" "Language-Team: LANGUAGE \n" diff --git a/src/collective/contact/core/locales/plone.pot b/src/collective/contact/core/locales/plone.pot index 36dbd36e..a9060a46 100644 --- a/src/collective/contact/core/locales/plone.pot +++ b/src/collective/contact/core/locales/plone.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/src/collective/contact/core/locales/sl/LC_MESSAGES/collective.contact.core.po b/src/collective/contact/core/locales/sl/LC_MESSAGES/collective.contact.core.po index 1f4a32e1..8fae8806 100644 --- a/src/collective/contact/core/locales/sl/LC_MESSAGES/collective.contact.core.po +++ b/src/collective/contact/core/locales/sl/LC_MESSAGES/collective.contact.core.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: collective.contact.core\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: 2016-10-01 22:38+0200\n" "Last-Translator: Cédric Messiant \n" "Language-Team: French\n" @@ -45,11 +45,11 @@ msgstr "Dodaj" msgid "Additional address details" msgstr "Podatki o dodatnem naslovu" -#: ../interfaces.py:93 +#: ../interfaces.py:100 msgid "Additional label" msgstr "Dodatna oznaka" -#: ../interfaces.py:94 +#: ../interfaces.py:101 msgid "Additional label with information that does not appear on position label" msgstr "Dodatna oznaka z informacijami, ki niso vidne na oznaki pozicije" @@ -72,6 +72,10 @@ msgstr "GSM" msgid "Cell phone number" msgstr "GSM Številka" +#: ../interfaces.py:64 +msgid "Choose information displayed after a search in contact widget." +msgstr "" + #: ../behaviors.py:250 msgid "City" msgstr "Mesto" @@ -98,7 +102,7 @@ msgid "Country" msgstr "Država" #: ../browser/addcontact.py:65 -#: ../browser/templates/organization.pt:74 +#: ../browser/templates/organization.pt:72 #: ../browser/templates/position.pt:41 msgid "Create ${name}" msgstr "Ustvari ${name}" @@ -135,10 +139,19 @@ msgstr "Email" #: ../browser/basefields/templates/held_position.pt:28 #: ../browser/templates/heldpositions.pt:47 -#: ../interfaces.py:102 +#: ../interfaces.py:109 msgid "End date" msgstr "Končni datum" +#: ../browser/basefields/templates/organization.pt:42 +#: ../content/organization.py:53 +msgid "Enterprise (or VAT) number" +msgstr "" + +#: ../content/organization.py:24 +msgid "Enterprise number must contain only letters and numbers" +msgstr "" + #: ../behaviors.py:161 msgid "Fax" msgstr "Telefaks" @@ -249,7 +262,7 @@ msgstr "" msgid "Name" msgstr "Ime" -#: ../browser/templates/organization.pt:59 +#: ../browser/templates/organization.pt:57 msgid "Not assigned" msgstr "Ni dodeljen" @@ -279,11 +292,11 @@ msgstr "Tip organizacije" msgid "Organization types" msgstr "Tipi organizacij" -#: ../interfaces.py:88 +#: ../interfaces.py:95 msgid "Organization/Position" msgstr "Organizacija/pozicija" -#: ../browser/templates/contact.pt:17 +#: ../browser/templates/contact.pt:16 #: ../browser/templates/directory.pt:22 msgid "Organizations" msgstr "Organizacije" @@ -296,7 +309,7 @@ msgstr "Organizacije v tej organizaciji" msgid "Other contacts in this organization:" msgstr "Drugi kontakti v tej organizaciji" -#: ../browser/templates/organization.pt:19 +#: ../browser/templates/organization.pt:17 #: ../browser/templates/position.pt:17 msgid "Parent organizations" msgstr "Krovne organizacije" @@ -324,7 +337,7 @@ msgid "Phone number" msgstr "Telefonske številke" #: ../content/person.py:50 -#: ../interfaces.py:106 +#: ../interfaces.py:113 msgid "Photo" msgstr "Foto" @@ -346,7 +359,7 @@ msgstr "Tipi pozicij" msgid "Positions" msgstr "Pozicije" -#: ../browser/templates/organization.pt:39 +#: ../browser/templates/organization.pt:37 msgid "Positions in this organization" msgstr "Pozicije v tej organizaciji" @@ -386,7 +399,7 @@ msgstr "" #: ../browser/basefields/templates/held_position.pt:22 #: ../browser/templates/heldpositions.pt:42 -#: ../interfaces.py:98 +#: ../interfaces.py:105 msgid "Start date" msgstr "Začetni datum" @@ -457,11 +470,11 @@ msgid "collective.contact.core tests" msgstr "" #. Default: ":" -#: ../browser/templates/organization.pt:45 +#: ../browser/templates/organization.pt:43 msgid "colon" msgstr "dvopičje" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "from" msgstr "od" @@ -484,6 +497,6 @@ msgstr "Če predmet ne obstaja, ga lahko dodaš v podatkovno bazo" msgid "organization/position" msgstr "Organizacija/pozicija" -#: ../browser/templates/organization.pt:55 +#: ../browser/templates/organization.pt:53 msgid "to" msgstr "za" diff --git a/src/collective/contact/core/locales/sl/LC_MESSAGES/plone.po b/src/collective/contact/core/locales/sl/LC_MESSAGES/plone.po index 82c68caa..21f1aaca 100644 --- a/src/collective/contact/core/locales/sl/LC_MESSAGES/plone.po +++ b/src/collective/contact/core/locales/sl/LC_MESSAGES/plone.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: collective.contact.core\n" -"POT-Creation-Date: 2018-10-23 08:18+0000\n" +"POT-Creation-Date: 2019-09-05 13:13+0000\n" "PO-Revision-Date: 2016-10-01 23:31+0200\n" "Last-Translator: Cédric Messiant \n" "Language-Team: Ecréall\n" diff --git a/src/collective/contact/core/profiles.zcml b/src/collective/contact/core/profiles.zcml new file mode 100644 index 00000000..c42973e3 --- /dev/null +++ b/src/collective/contact/core/profiles.zcml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/src/collective/contact/core/profiles/default/catalog.xml b/src/collective/contact/core/profiles/default/catalog.xml index 21627011..b45c91c9 100644 --- a/src/collective/contact/core/profiles/default/catalog.xml +++ b/src/collective/contact/core/profiles/default/catalog.xml @@ -4,5 +4,11 @@ + - \ No newline at end of file + + + + + + diff --git a/src/collective/contact/core/profiles/default/cssregistry.xml b/src/collective/contact/core/profiles/default/cssregistry.xml index 81a5b922..d904ac0a 100644 --- a/src/collective/contact/core/profiles/default/cssregistry.xml +++ b/src/collective/contact/core/profiles/default/cssregistry.xml @@ -1,10 +1,4 @@ - - - - \ No newline at end of file + + diff --git a/src/collective/contact/core/profiles/default/jsregistry.xml b/src/collective/contact/core/profiles/default/jsregistry.xml index 56a89766..1537b714 100644 --- a/src/collective/contact/core/profiles/default/jsregistry.xml +++ b/src/collective/contact/core/profiles/default/jsregistry.xml @@ -1,9 +1,4 @@ - - - - \ No newline at end of file + + diff --git a/src/collective/contact/core/profiles/default/metadata.xml b/src/collective/contact/core/profiles/default/metadata.xml index 7d2e497f..e7bff334 100644 --- a/src/collective/contact/core/profiles/default/metadata.xml +++ b/src/collective/contact/core/profiles/default/metadata.xml @@ -1,10 +1,9 @@ - 13 + 17 profile-collective.contact.widget:default profile-plone.app.dexterity:default - profile-plone.formwidget.datetime:default profile-plone.formwidget.masterselect:default profile-plone.app.relationfield:default profile-collective.z3cform.datagridfield:default diff --git a/src/collective/contact/core/profiles/default/propertiestool.xml b/src/collective/contact/core/profiles/default/propertiestool.xml index e6378bf5..7ac769b9 100644 --- a/src/collective/contact/core/profiles/default/propertiestool.xml +++ b/src/collective/contact/core/profiles/default/propertiestool.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/src/collective/contact/core/profiles/default/registry.xml b/src/collective/contact/core/profiles/default/registry.xml index e2693b16..acac234e 100644 --- a/src/collective/contact/core/profiles/default/registry.xml +++ b/src/collective/contact/core/profiles/default/registry.xml @@ -1,3 +1,31 @@ + + + + + directory + + + + + ++resource++collective.contact.core/forms.js + + ++resource++collective.contact.core/style.css + + + + + + collective.contact.core + + True + plone + False + ++resource++collective.contact.core/forms.min.js + 2019-02-15 10:32:00 + + diff --git a/src/collective/contact/core/profiles/default/types/directory.xml b/src/collective/contact/core/profiles/default/types/directory.xml index 600c7fff..19a3f0f4 100644 --- a/src/collective/contact/core/profiles/default/types/directory.xml +++ b/src/collective/contact/core/profiles/default/types/directory.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Directory - directory_icon.png + ++resource++collective.contact.core/directory_icon.png directory string:${folder_url}/++add++directory @@ -24,8 +24,8 @@ cmf.AddPortalContent collective.contact.core.content.directory.Directory - - + + diff --git a/src/collective/contact/core/profiles/default/types/held_position.xml b/src/collective/contact/core/profiles/default/types/held_position.xml index 41623837..80c4c0ef 100644 --- a/src/collective/contact/core/profiles/default/types/held_position.xml +++ b/src/collective/contact/core/profiles/default/types/held_position.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Held position - string:${portal_url}/held_position_icon.png + string:${portal_url}/++resource++collective.contact.core/held_position_icon.png held_position string:${folder_url}/++add++held_position @@ -20,7 +20,7 @@ cmf.AddPortalContent collective.contact.core.content.held_position.HeldPosition - + diff --git a/src/collective/contact/core/profiles/default/types/organization.xml b/src/collective/contact/core/profiles/default/types/organization.xml index b64e64ed..77112bf5 100644 --- a/src/collective/contact/core/profiles/default/types/organization.xml +++ b/src/collective/contact/core/profiles/default/types/organization.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Organization - string:${portal_url}/organization_icon.png + string:${portal_url}/++resource++collective.contact.core/organization_icon.png organization string:${folder_url}/++add++organization @@ -24,8 +24,8 @@ cmf.AddPortalContent collective.contact.core.content.organization.Organization - - + + diff --git a/src/collective/contact/core/profiles/default/types/person.xml b/src/collective/contact/core/profiles/default/types/person.xml index 8677c93e..0b3fb201 100644 --- a/src/collective/contact/core/profiles/default/types/person.xml +++ b/src/collective/contact/core/profiles/default/types/person.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Person - string:${portal_url}/person_icon.png + string:${portal_url}/++resource++collective.contact.core/person_icon.png person string:${folder_url}/++add++person @@ -22,7 +22,7 @@ cmf.AddPortalContent collective.contact.core.content.person.Person - + diff --git a/src/collective/contact/core/profiles/default/types/position.xml b/src/collective/contact/core/profiles/default/types/position.xml index d708e280..be70ced1 100644 --- a/src/collective/contact/core/profiles/default/types/position.xml +++ b/src/collective/contact/core/profiles/default/types/position.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Position - string:${portal_url}/position_icon.png + string:${portal_url}/++resource++collective.contact.core/position_icon.png position string:${folder_url}/++add++position @@ -21,8 +21,8 @@ cmf.AddPortalContent collective.contact.core.content.position.Position - - + + diff --git a/src/collective/contact/core/profiles/test_data/collective_contact_core_test_data_marker.txt b/src/collective/contact/core/profiles/test_data/collective_contact_core_test_data_marker.txt deleted file mode 100644 index 8089e33f..00000000 --- a/src/collective/contact/core/profiles/test_data/collective_contact_core_test_data_marker.txt +++ /dev/null @@ -1 +0,0 @@ -Profile marker \ No newline at end of file diff --git a/src/collective/contact/core/profiles/test_data/img/sgt_pepper.jpg b/src/collective/contact/core/profiles/test_data/img/sgt_pepper.jpg deleted file mode 100644 index cb79876e..00000000 Binary files a/src/collective/contact/core/profiles/test_data/img/sgt_pepper.jpg and /dev/null differ diff --git a/src/collective/contact/core/profiles/test_data/metadata.xml b/src/collective/contact/core/profiles/test_data/metadata.xml deleted file mode 100644 index f8aaf2d7..00000000 --- a/src/collective/contact/core/profiles/test_data/metadata.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - 0001 - - profile-collective.contact.core:default - - diff --git a/src/collective/contact/core/profiles/testing/types/testtype.xml b/src/collective/contact/core/profiles/testing/types/testtype.xml index 78269d30..c59b72ce 100644 --- a/src/collective/contact/core/profiles/testing/types/testtype.xml +++ b/src/collective/contact/core/profiles/testing/types/testtype.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Test type None - string:${portal_url}/folder_icon.png + string:${portal_url}/++resource++collective.contact.core/folder_icon.png testtype string:${folder_url}/++add++testtype diff --git a/src/collective/contact/core/schema.py b/src/collective/contact/core/schema.py index 1be2b3d6..6aaf9e37 100755 --- a/src/collective/contact/core/schema.py +++ b/src/collective/contact/core/schema.py @@ -1 +1,2 @@ -from collective.contact.widget.schema import ContactList, ContactChoice +from collective.contact.widget.schema import ContactChoice # noqa for now +from collective.contact.widget.schema import ContactList # noqa for now diff --git a/src/collective/contact/core/setuphandlers.py b/src/collective/contact/core/setuphandlers.py index e44f185c..e8bb8d20 100644 --- a/src/collective/contact/core/setuphandlers.py +++ b/src/collective/contact/core/setuphandlers.py @@ -1,28 +1,21 @@ # -*- coding: utf-8 -*- -# -# File: setuphandlers.py -# -# # GNU General Public License (GPL) -# - -__docformat__ = 'plaintext' - -import datetime - +from collective.contact.core.interfaces import IContactCoreParameters +from plone import api +from z3c.relationfield.relation import RelationValue from zope import component from zope.intid.interfaces import IIntIds +from zope.lifecycleevent import modified + +import datetime +import logging +import transaction -from z3c.relationfield.relation import RelationValue -from plone import api # from plone.registry.interfaces import IRegistry -import logging logger = logging.getLogger('collective.contact.core: setuphandlers') -from collective.contact.core.interfaces import IContactCoreParameters - def isNotCollectiveContactContentProfile(context): return context.readDataFile("collective_contact_core_marker.txt") is None @@ -42,13 +35,16 @@ def postInstall(context): import traceback for line in traceback.format_stack(): if 'QuickInstallerTool.py' in line and 'reinstallProducts' in line: - raise Exception('You can not reinstall this product, use portal_setup to re-apply the relevant profile !') + raise Exception( + 'You can not reinstall this product, use portal_setup to re-apply the relevant profile !') # Set default values in registry for name in ('person_contact_details_private', 'person_title_in_title', 'use_held_positions_to_search_person', 'use_description_to_search_person'): - val = api.portal.get_registry_record(name=name, interface=IContactCoreParameters) + val = api.portal.get_registry_record( + name=name, interface=IContactCoreParameters) if val is None: - api.portal.set_registry_record(name=name, value=True, interface=IContactCoreParameters) + api.portal.set_registry_record( + name=name, value=True, interface=IContactCoreParameters) # we need to remove the default model_source added to our portal_types # XXX to be done @@ -73,31 +69,31 @@ def create_test_contact_data(portal): {'name': u'Regiment', 'token': u'regiment'}, {'name': u'Squad', 'token': u'squad'}, ] -# Examples structure -# ------------------ -# organizations (* = organization, £ = position) -# * Armée de terre -# * Corps A -# * Division Alpha -# * Régiment H -# * Brigade LH -# £ Sergent -# £ Capitaine -# * Division Beta -# * Corps B -# £ Général -# -# persons (> = person, @ = held_position) -# > De Gaulle -# @ Armée de terre -# @ Général -# > Pepper -# @ Sergent -# > Rambo -# @ Brigade LH -# > Draper -# @ Capitaine -# @ Division Beta + # Examples structure + # ------------------ + # organizations (* = organization, £ = position) + # * Armée de terre + # * Corps A + # * Division Alpha + # * Régiment H + # * Brigade LH + # £ Sergent + # £ Capitaine + # * Division Beta + # * Corps B + # £ Général + # + # persons (> = person, @ = held_position) + # > De Gaulle + # @ Armée de terre + # @ Général + # > Pepper + # @ Sergent + # > Rambo + # @ Brigade LH + # > Draper + # @ Capitaine + # @ Division Beta params = {'title': u"Military directory", 'position_types': position_types, @@ -123,7 +119,6 @@ def create_test_contact_data(portal): 'website': 'www.charles-de-gaulle.org' } mydirectory.invokeFactory('person', 'degaulle', **params) - degaulle = mydirectory['degaulle'] params = {'lastname': u'Pepper', 'gender': u'M', @@ -137,7 +132,6 @@ def create_test_contact_data(portal): 'website': 'http://www.stephen-pepper.org' } mydirectory.invokeFactory('person', 'pepper', **params) - pepper = mydirectory['pepper'] params = {'lastname': u'Rambo', 'firstname': u'John', @@ -145,7 +139,6 @@ def create_test_contact_data(portal): 'use_parent_address': True, } mydirectory.invokeFactory('person', 'rambo', **params) - rambo = mydirectory['rambo'] params = {'lastname': u'Draper', 'firstname': u'John', @@ -154,7 +147,6 @@ def create_test_contact_data(portal): } mydirectory.invokeFactory('person', 'draper', **params) - draper = mydirectory['draper'] params = {'title': u"Armée de terre", 'organization_type': u'army', @@ -166,6 +158,7 @@ def create_test_contact_data(portal): 'number': u'1', 'zip_code': u'75008', 'country': u'France', + 'enterprise_number': 'BE123456789', } mydirectory.invokeFactory('organization', 'armeedeterre', **params) armeedeterre = mydirectory['armeedeterre'] @@ -199,7 +192,6 @@ def create_test_contact_data(portal): corpsa.invokeFactory('organization', 'divisionbeta', **params) divisionalpha = corpsa['divisionalpha'] - divisionbeta = corpsa['divisionbeta'] params = {'title': u"Régiment H", 'organization_type': u'regiment', @@ -237,7 +229,6 @@ def create_test_contact_data(portal): 'use_parent_address': True, } divisionalpha.invokeFactory('position', 'capitaine_alpha', **params) - capitaine_alpha = divisionalpha['capitaine_alpha'] params = {'title': u"Sergent de la brigade LH", 'position_type': u'sergeant', @@ -247,15 +238,31 @@ def create_test_contact_data(portal): 'use_parent_address': True, } brigadelh.invokeFactory('position', 'sergent_lh', **params) - sergent_lh = brigadelh['sergent_lh'] + transaction.commit() + +def create_test_held_positions(portal): + mydirectory = portal['mydirectory'] + armeedeterre = mydirectory['armeedeterre'] intids = component.getUtility(IIntIds) + degaulle = mydirectory['degaulle'] + rambo = mydirectory['rambo'] + draper = mydirectory['draper'] + corpsa = armeedeterre['corpsa'] + divisionalpha = corpsa['divisionalpha'] + divisionbeta = corpsa['divisionbeta'] + capitaine_alpha = divisionalpha['capitaine_alpha'] + regimenth = divisionalpha['regimenth'] + brigadelh = regimenth['brigadelh'] + sergent_lh = brigadelh['sergent_lh'] + pepper = mydirectory['pepper'] params = {'start_date': datetime.date(1940, 5, 25), 'end_date': datetime.date(1970, 11, 9), 'position': RelationValue(intids.getId(armeedeterre)), } degaulle.invokeFactory('held_position', 'adt', **params) + modified(degaulle['adt']) general_adt = armeedeterre['general_adt'] params = {'start_date': datetime.date(1940, 5, 25), @@ -296,11 +303,4 @@ def create_test_contact_data(portal): 'use_parent_address': True, } rambo.invokeFactory('held_position', 'brigadelh', **params) - - -def createTestData(context): - """Create test data for collective.contact.core""" - if isNotTestDataProfile(context): - return - portal = context.getSite() - create_test_contact_data(portal) + transaction.commit() diff --git a/src/collective/contact/core/subscribers.py b/src/collective/contact/core/subscribers.py index b7d0bbd7..0b95e9fa 100644 --- a/src/collective/contact/core/subscribers.py +++ b/src/collective/contact/core/subscribers.py @@ -1,18 +1,21 @@ -from Acquisition import aq_get -from five import grok +# from Acquisition import aq_get +from collective.contact.core.behaviors import IContactDetails +from collective.contact.core.content.organization import IOrganization +from collective.contact.core.interfaces import IContactCoreParameters +from collective.contact.widget.interfaces import IContactContent +from plone import api +# from plone.app.iterate.interfaces import IWorkingCopy +# from plone.app.linkintegrity.handlers import referencedObjectRemoved as baseReferencedObjectRemoved +# from plone.app.linkintegrity.interfaces import ILinkIntegrityInfo +from plone.registry.interfaces import IRecordModifiedEvent from z3c.form.interfaces import NO_VALUE - -from zc.relation.interfaces import ICatalog -from zope import component -from zope.lifecycleevent.interfaces import IObjectAddedEvent,\ - IObjectModifiedEvent +# from zc.relation.interfaces import ICatalog +# from zope import component from zope.container.contained import ContainerModifiedEvent -from zope.intid.interfaces import IIntIds +# from zope.intid.interfaces import IIntIds from zope.schema import getFields -from plone.app.linkintegrity.interfaces import ILinkIntegrityInfo -from plone.app.linkintegrity.handlers import referencedObjectRemoved as \ - baseReferencedObjectRemoved + try: from plone.app.referenceablebehavior.referenceable import IReferenceable except ImportError: @@ -21,20 +24,12 @@ class IReferenceable(Interface): pass -from collective.contact.widget.interfaces import IContactContent -from collective.contact.core.behaviors import IContactDetails -from collective.contact.core.content.position import IPosition -from collective.contact.core.content.person import IPerson -from collective.contact.core.content.organization import IOrganization -from collective.contact.core.interfaces import IHeldPosition # update indexes of related content when a content is modified # you can monkey patch this value if you have an index that needs this indexes_to_update = ['SearchableText'] -@grok.subscribe(IHeldPosition, IObjectAddedEvent) -@grok.subscribe(IHeldPosition, IObjectModifiedEvent) def update_related_with_held_position(obj, event=None): if isinstance(event, ContainerModifiedEvent): return @@ -42,7 +37,6 @@ def update_related_with_held_position(obj, event=None): obj.get_person().reindexObject(idxs=indexes_to_update) -@grok.subscribe(IPosition, IObjectModifiedEvent) def update_related_with_position(obj, event=None): if isinstance(event, ContainerModifiedEvent): return @@ -52,7 +46,6 @@ def update_related_with_position(obj, event=None): update_related_with_held_position(held_position) -@grok.subscribe(IPerson, IObjectModifiedEvent) def update_related_with_person(obj, event=None): if isinstance(event, ContainerModifiedEvent): return @@ -61,7 +54,6 @@ def update_related_with_person(obj, event=None): held_position.reindexObject(idxs=indexes_to_update) -@grok.subscribe(IOrganization, IObjectModifiedEvent) def update_related_with_organization(obj, event=None): if isinstance(event, ContainerModifiedEvent): return @@ -82,42 +74,43 @@ def update_related_with_organization(obj, event=None): update_related_with_organization(child) -def referenceRemoved(obj, event, toInterface=IContactContent): - """Store information about the removed link integrity reference. - """ - # inspired from z3c/relationfield/event.py:breakRelations - # and plone/app/linkintegrity/handlers.py:referenceRemoved - # if the object the event was fired on doesn't have a `REQUEST` attribute - # we can safely assume no direct user action was involved and therefore - # never raise a link integrity exception... - request = aq_get(obj, 'REQUEST', None) - if not request: - return - storage = ILinkIntegrityInfo(request) +# def referenceRemoved(obj, event, toInterface=IContactContent): +# """Store information about the removed link integrity reference. +# """ +# # inspired from z3c/relationfield/event.py:breakRelations +# # and plone/app/linkintegrity/handlers.py:referenceRemoved +# # if the object the event was fired on doesn't have a `REQUEST` attribute +# # we can safely assume no direct user action was involved and therefore +# # never raise a link integrity exception... +# request = aq_get(obj, 'REQUEST', None) +# if not request: +# return +# storage = ILinkIntegrityInfo(request) - catalog = component.queryUtility(ICatalog) - intids = component.queryUtility(IIntIds) - if catalog is None or intids is None: - return +# catalog = component.queryUtility(ICatalog) +# intids = component.queryUtility(IIntIds) +# if catalog is None or intids is None: +# return - # find all relations that point to us - obj_id = intids.queryId(obj) - if obj_id is None: - return +# # find all relations that point to us +# obj_id = intids.queryId(obj) +# if obj_id is None: +# return - rels = list(catalog.findRelations({'to_id': obj_id})) - for rel in rels: - if toInterface.providedBy(rel.to_object): - storage.addBreach(rel.from_object, rel.to_object) +# rels = list(catalog.findRelations({'to_id': obj_id})) +# for rel in rels: +# if toInterface.providedBy(rel.to_object): +# storage.addBreach(rel.from_object, rel.to_object) -def referencedObjectRemoved(obj, event): - if not IReferenceable.providedBy(obj): - baseReferencedObjectRemoved(obj, event) +# def referencedObjectRemoved(obj, event): +# # Avoid an error when we try to remove a working copy (plone.app.iterate) +# if IWorkingCopy.providedBy(obj): +# return +# if not IReferenceable.providedBy(obj): +# baseReferencedObjectRemoved(obj, event) -@grok.subscribe(IContactDetails, IObjectModifiedEvent) -@grok.subscribe(IContactDetails, IObjectAddedEvent) def clear_fields_use_parent_address(obj, event): """If 'use parent address' has been selected, ensure content address fields are cleared @@ -130,3 +123,16 @@ def clear_fields_use_parent_address(obj, event): delattr(obj, field_name) except AttributeError: pass + + +def recordModified(event): + """ + Manage configuration change in registry + """ + if (IRecordModifiedEvent.providedBy(event) and # noqa W504 + event.record.interfaceName and # noqa W504 + event.record.interface == IContactCoreParameters): + if event.record.fieldName == 'contact_source_metadata_content': + pc = api.portal.get_tool('portal_catalog') + for brain in pc(object_provides=IContactContent.__identifier__): + brain.getObject().reindexObject(idxs=['contact_source']) diff --git a/src/collective/contact/core/subscribers.zcml b/src/collective/contact/core/subscribers.zcml new file mode 100644 index 00000000..358bdc8c --- /dev/null +++ b/src/collective/contact/core/subscribers.zcml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/collective/contact/core/testing.py b/src/collective/contact/core/testing.py index 6955a8d2..d08fd71f 100644 --- a/src/collective/contact/core/testing.py +++ b/src/collective/contact/core/testing.py @@ -1,43 +1,58 @@ # -*- coding: utf8 -*- -from plone.app.testing import PloneWithPackageLayer -from plone.app.testing import IntegrationTesting +from collective.contact.core.setuphandlers import create_test_contact_data +from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE +from plone.app.robotframework.testing import REMOTE_LIBRARY_BUNDLE_FIXTURE from plone.app.testing import FunctionalTesting +from plone.app.testing import IntegrationTesting +from plone.app.testing import PloneSandboxLayer from plone.app.testing import setRoles from plone.app.testing import TEST_USER_ID - -from plone.app.robotframework.testing import AUTOLOGIN_LIBRARY_FIXTURE - from plone.testing import z2 import collective.contact.core +import collective.contact.widget + +class CollectiveContactCoreLayer(PloneSandboxLayer): -class ContactContentLayer(PloneWithPackageLayer): + defaultBases = ( + PLONE_APP_CONTENTTYPES_FIXTURE, + ) + + def setUpZope(self, app, configurationContext): + self.loadZCML(package=collective.contact.widget) + self.loadZCML(package=collective.contact.core) + self.loadZCML(name='testing.zcml', package=collective.contact.core) def setUpPloneSite(self, portal): + self.applyProfile(portal, 'collective.contact.core:testing') - # insert some test data - self.applyProfile(portal, 'collective.contact.core:test_data') + # # insert some test data setRoles(portal, TEST_USER_ID, ['Manager']) + create_test_contact_data(portal) + + +COLLECTIVE_CONTACT_CORE_FIXTURE = CollectiveContactCoreLayer() + +COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING = IntegrationTesting( + bases=(COLLECTIVE_CONTACT_CORE_FIXTURE,), + name='CollectiveContactCoreLayer:IntegrationTesting', +) -COLLECTIVE_CONTACT_CORE = ContactContentLayer( - zcml_package=collective.contact.core, - zcml_filename='testing.zcml', - gs_profile_id='collective.contact.core:testing', - name="COLLECTIVE_CONTACT_CORE") -INTEGRATION = IntegrationTesting( - bases=(COLLECTIVE_CONTACT_CORE, ), - name="INTEGRATION") +COLLECTIVE_CONTACT_CORE_FUNCTIONAL_TESTING = FunctionalTesting( + bases=(COLLECTIVE_CONTACT_CORE_FIXTURE,), + name='CollectiveContactCoreLayer:FunctionalTesting', +) -FUNCTIONAL = FunctionalTesting( - bases=(COLLECTIVE_CONTACT_CORE, ), - name="FUNCTIONAL") -ACCEPTANCE = FunctionalTesting( - bases=(COLLECTIVE_CONTACT_CORE, - AUTOLOGIN_LIBRARY_FIXTURE, - z2.ZSERVER_FIXTURE), - name="ACCEPTANCE") +COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING = FunctionalTesting( + bases=( + COLLECTIVE_CONTACT_CORE_FIXTURE, + REMOTE_LIBRARY_BUNDLE_FIXTURE, + z2.ZSERVER_FIXTURE, + ), + name='CollectiveContactCoreLayer:AcceptanceTesting', +) diff --git a/src/collective/contact/core/tests/robot/test_contacts.robot b/src/collective/contact/core/tests/robot/test_contacts.robot index 38420b26..6d2370a8 100644 --- a/src/collective/contact/core/tests/robot/test_contacts.robot +++ b/src/collective/contact/core/tests/robot/test_contacts.robot @@ -1,15 +1,14 @@ *** Settings *** -Test Setup Open SauceLabs test browser -Test Teardown Run keywords Report test status Close all browsers +Test Setup Open test browser +Test Teardown Run keywords Close all browsers Resource plone/app/robotframework/keywords.robot #Test Setup Open test browser #Test Teardown Close all browsers -Resource plone/app/robotframework/saucelabs.robot Resource keywords.robot *** Test cases *** Directory is available [Tags] Go Log in as site owner and wait - Click link css=#portaltab-mydirectory a + Click link css=#portal-globalnav .mydirectory a Wait until element is visible xpath=//h1/span[.='Military directory'] 5 Create a new organization @@ -23,128 +22,128 @@ Create a new organization Go to directory Element should contain organizations Squadron five -Can create new contact from organization - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha - Page should contain link css=.addnewcontactfromorganization - Click link css=.addnewcontactfromorganization - Overlay is opened - Wait For Condition return $('.overlay h1').text() === "Create Contact" - Element should contain oform-widgets-organization-input-fields Armée de terre / Corps A / Division Alpha - Sleep 1 - Input text oform-widgets-person-widgets-query Ramb - Click element oform-widgets-person-widgets-query - Wait Until Page Contains Element css=.ac_results - Click element css=.ac_results li:nth-child(1) - Sleep 1 - Click button Add +# Can create new contact from organization +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha +# Page should contain link css=.add-contact +# Click link css=.add-contact +# Overlay is opened +# Wait For Condition return $('.overlay h1').text() === "Create Contact" +# Element should contain oform-widgets-organization-input-fields Armée de terre / Corps A / Division Alpha +# Sleep 1 +# Input text oform-widgets-person-widgets-query Ramb +# Click element oform-widgets-person-widgets-query +# Wait Until Page Contains Element css=.ac_results +# Click element css=.ac_results li:nth-child(1) +# Sleep 1 +# Click button Add -Can create new person from organization - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha - Click link css=.addnewcontactfromorganization - Wait For Condition return $('.overlay h1').text() === "Create Contact" - Element should not be visible css=#formfield-oform-widgets-person .addnew-block - Sleep 1 - Input text oform-widgets-person-widgets-query Chuck Norris - Element should become visible css=#formfield-oform-widgets-person .addnew-block - Click link Create Person - Wait Until Page Contains Add Person - ${original_speed} = Get Selenium speed - Set Selenium speed 1 - Textfield Value Should Be form-widgets-lastname Norris - Set Selenium speed ${original_speed} - Textfield Value Should Be form-widgets-firstname Chuck - Click element form-widgets-gender-0 - Click button Save - Wait Until Page Contains Chuck Norris - Sleep 1 - Click button Add - Wait Until Page Contains Element other-contacts - Element Should Contain other-contacts Chuck Norris +# Can create new person from organization +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha +# Click link css=.add-contact +# Wait For Condition return $('.overlay h1').text() === "Create Contact" +# Element should not be visible css=#formfield-oform-widgets-person .addnew-block +# Sleep 1 +# Input text oform-widgets-person-widgets-query Chuck Norris +# Element should become visible css=#formfield-oform-widgets-person .addnew-block +# Click link Create Person +# Wait Until Page Contains Add Person +# ${original_speed} = Get Selenium speed +# Set Selenium speed 1 +# Textfield Value Should Be form-widgets-lastname Norris +# Set Selenium speed ${original_speed} +# Textfield Value Should Be form-widgets-firstname Chuck +# Click element form-widgets-gender-0 +# Click button Save +# Wait Until Page Contains Chuck Norris +# Sleep 1 +# Click button Add +# Wait Until Page Contains Element other-contacts +# Element Should Contain other-contacts Chuck Norris -Can create new contact from position - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha/regimenth/brigadelh/sergent_lh - Page should contain link css=.addnewcontactfromposition - Click link css=.addnewcontactfromposition - Overlay is opened - Wait For Condition return $('.overlay h1').text() === "Create Contact" - Element should not be visible css=#oform-widgets-position-input-fields - Element should contain oform-widgets-organization-input-fields Armée de terre / Corps A / Division Alpha / Régiment H / Brigade LH - Sleep 1 - Input text oform-widgets-person-widgets-query Ramb - Click element oform-widgets-person-widgets-query - Wait Until Page Contains Element css=.ac_results - Click element css=.ac_results li:nth-child(1) - Sleep 1 - Element should become visible css=#oform-widgets-position-input-fields - Element should contain oform-widgets-position-input-fields Sergent de la brigade LH (Armée de terre / Corps A / Division Alpha / Régiment H / Brigade LH) - Click button Add +# Can create new contact from position +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha/regimenth/brigadelh/sergent_lh +# Page should contain link css=.add-contact +# Click link css=.add-contact +# Overlay is opened +# Wait For Condition return $('.overlay h1').text() === "Create Contact" +# Element should not be visible css=#oform-widgets-position-input-fields +# Element should contain oform-widgets-organization-input-fields Armée de terre / Corps A / Division Alpha / Régiment H / Brigade LH +# Sleep 1 +# Input text oform-widgets-person-widgets-query Ramb +# Click element oform-widgets-person-widgets-query +# Wait Until Page Contains Element css=.ac_results +# Click element css=.ac_results li:nth-child(1) +# Sleep 1 +# Element should become visible css=#oform-widgets-position-input-fields +# Element should contain oform-widgets-position-input-fields Sergent de la brigade LH (Armée de terre / Corps A / Division Alpha / Régiment H / Brigade LH) +# Click button Add -Show parent address if it exists in creation - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa - Add new organization - Click link Address - Checkbox Should Be Selected form-widgets-IContactDetails-use_parent_address-0 - Element should contain css=.address rue Philibert Lucot - Element should contain css=.address Orléans - Element should contain css=.address France - Element should not be visible formfield-form-widgets-IContactDetails-number - Element should not be visible formfield-form-widgets-IContactDetails-street - Element should not be visible formfield-form-widgets-IContactDetails-city - Element should not be visible formfield-form-widgets-IContactDetails-country +# Show parent address if it exists in creation +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa +# Add new organization +# Click link Address +# Checkbox Should Be Selected form-widgets-IContactDetails-use_parent_address-0 +# Element should contain css=.address rue Philibert Lucot +# Element should contain css=.address Orléans +# Element should contain css=.address France +# Element should not be visible formfield-form-widgets-IContactDetails-number +# Element should not be visible formfield-form-widgets-IContactDetails-street +# Element should not be visible formfield-form-widgets-IContactDetails-city +# Element should not be visible formfield-form-widgets-IContactDetails-country -Show parent address if it exists in edition - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha/capitaine_alpha - Click Edit In Edit Bar - Click link Address - ${original_speed} = Get Selenium speed - Set Selenium speed 1 - Checkbox Should Be Selected form-widgets-IContactDetails-use_parent_address-0 - Element should contain css=.address rue Philibert Lucot - Set Selenium speed ${original_speed} - Element should contain css=.address Orléans - Element should contain css=.address France - Element should not be visible formfield-form-widgets-IContactDetails-number - Element should not be visible formfield-form-widgets-IContactDetails-street - Element should not be visible formfield-form-widgets-IContactDetails-city - Element should not be visible formfield-form-widgets-IContactDetails-country +# Show parent address if it exists in edition +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa/divisionalpha/capitaine_alpha +# Click Edit In Edit Bar +# Click link Address +# ${original_speed} = Get Selenium speed +# Set Selenium speed 1 +# Checkbox Should Be Selected form-widgets-IContactDetails-use_parent_address-0 +# Element should contain css=.address rue Philibert Lucot +# Set Selenium speed ${original_speed} +# Element should contain css=.address Orléans +# Element should contain css=.address France +# Element should not be visible formfield-form-widgets-IContactDetails-number +# Element should not be visible formfield-form-widgets-IContactDetails-street +# Element should not be visible formfield-form-widgets-IContactDetails-city +# Element should not be visible formfield-form-widgets-IContactDetails-country -Show use parent address checkbox if no parent address when creating a position - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsb - Add new position - Click link Address - Page should contain element formfield-form-widgets-IContactDetails-number - Page should contain element formfield-form-widgets-IContactDetails-street - Page should contain element formfield-form-widgets-IContactDetails-city - Page should contain element formfield-form-widgets-IContactDetails-country - Element should be visible form-widgets-IContactDetails-use_parent_address-0 +# Show use parent address checkbox if no parent address when creating a position +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsb +# Add new position +# Click link Address +# Page should contain element formfield-form-widgets-IContactDetails-number +# Page should contain element formfield-form-widgets-IContactDetails-street +# Page should contain element formfield-form-widgets-IContactDetails-city +# Page should contain element formfield-form-widgets-IContactDetails-country +# Element should be visible form-widgets-IContactDetails-use_parent_address-0 -Don't show use parent address checkbox in edition if no parent address and use parent address is False - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa - Click Edit In Edit Bar - Click link Address - Page should contain element formfield-form-widgets-IContactDetails-number - Page should contain element formfield-form-widgets-IContactDetails-street - Page should contain element formfield-form-widgets-IContactDetails-city - Page should contain element formfield-form-widgets-IContactDetails-country - Element should not be visible form-widgets-IContactDetails-use_parent_address-0 +# Don't show use parent address checkbox in edition if no parent address and use parent address is False +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsa +# Click Edit In Edit Bar +# Click link Address +# Page should contain element formfield-form-widgets-IContactDetails-number +# Page should contain element formfield-form-widgets-IContactDetails-street +# Page should contain element formfield-form-widgets-IContactDetails-city +# Page should contain element formfield-form-widgets-IContactDetails-country +# Element should not be visible form-widgets-IContactDetails-use_parent_address-0 -Show use parent address checkbox in edition if no parent address and use parent address is True - Log in as site owner and wait - Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsb - Click Edit In Edit Bar - Click link Address - Page should contain element formfield-form-widgets-IContactDetails-number - Page should contain element formfield-form-widgets-IContactDetails-street - Page should contain element formfield-form-widgets-IContactDetails-city - Page should contain element formfield-form-widgets-IContactDetails-country - Element should be visible form-widgets-IContactDetails-use_parent_address-0 +# Show use parent address checkbox in edition if no parent address and use parent address is True +# Log in as site owner and wait +# Go to ${PLONE_URL}/mydirectory/armeedeterre/corpsb +# Click Edit In Edit Bar +# Click link Address +# Page should contain element formfield-form-widgets-IContactDetails-number +# Page should contain element formfield-form-widgets-IContactDetails-street +# Page should contain element formfield-form-widgets-IContactDetails-city +# Page should contain element formfield-form-widgets-IContactDetails-country +# Element should be visible form-widgets-IContactDetails-use_parent_address-0 *** Keywords *** Go to directory diff --git a/src/collective/contact/core/tests/test_adapters.py b/src/collective/contact/core/tests/test_adapters.py index 4ea932b0..9fac445d 100644 --- a/src/collective/contact/core/tests/test_adapters.py +++ b/src/collective/contact/core/tests/test_adapters.py @@ -1,31 +1,34 @@ # -*- coding: utf8 -*- -import datetime -import unittest - -from zope.intid.interfaces import IIntIds -from zope.component import getUtility +from collective.contact.core.interfaces import IContactable +from collective.contact.core.interfaces import IContactCoreParameters +from collective.contact.core.interfaces import IPersonHeldPositions +from collective.contact.core.interfaces import IVCard +from collective.contact.core.setuphandlers import create_test_held_positions +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING +from ecreall.helpers.testing.base import BaseTest from plone import api from plone.app.testing.interfaces import TEST_USER_NAME from z3c.relationfield.relation import RelationValue +from zope.component import getUtility +from zope.intid.interfaces import IIntIds -from ecreall.helpers.testing.base import BaseTest - -from collective.contact.core.testing import INTEGRATION -from collective.contact.core.interfaces import IVCard, IPersonHeldPositions,\ - IContactable, IContactCoreParameters +import datetime +import unittest class TestAdapters(unittest.TestCase, BaseTest): """Tests adapters""" - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestAdapters, self).setUp() self.portal = self.layer['portal'] + create_test_held_positions(self.portal) self.directory = self.portal['mydirectory'] self.degaulle = self.directory['degaulle'] self.pepper = self.directory['pepper'] + self.rambo = self.directory['rambo'] def test_gadt_vcard(self): gadt = self.degaulle['gadt'] @@ -83,11 +86,13 @@ def test_person_held_positions(self): start_date=datetime.date(1959, 1, 8), position=RelationValue(intids.getId(self.directory.france))) api.content.create(container=degaulle, type='held_position', id='lieutenant-colonel', - position=RelationValue(intids.getId(self.directory.armeedeterre)), + position=RelationValue(intids.getId( + self.directory.armeedeterre)), end_date=datetime.date(1940, 6, 1), start_date=datetime.date(1933, 12, 25)) api.content.create(container=degaulle, type='held_position', id='commandant', - position=RelationValue(intids.getId(self.directory.armeedeterre)), + position=RelationValue(intids.getId( + self.directory.armeedeterre)), start_date=datetime.date(1927, 10, 9), end_date=datetime.date(1933, 12, 25)) del self.degaulle.gadt.end_date @@ -97,7 +102,7 @@ def test_person_held_positions(self): self.assertEqual(adapter.get_main_position(), self.degaulle.president) self.assertEqual(adapter.get_current_positions(), (self.degaulle.president, - self.degaulle.gadt, )) + self.degaulle.gadt,)) self.assertEqual(adapter.get_sorted_positions(), (self.degaulle.president, self.degaulle.gadt, @@ -118,7 +123,8 @@ def test_contact_details(self): interface=IContactCoreParameters) # test a person details = IContactable(self.degaulle).get_contact_details() - self.assertEqual(details['website'], 'http://www.charles-de-gaulle.org') + self.assertEqual(details['website'], + 'http://www.charles-de-gaulle.org') self.assertEqual(details['email'], 'charles.de.gaulle@private.com') self.assertEqual(details['address'], {'city': u'Colombey les deux \xe9glises', 'country': u'France', 'region': '', @@ -127,12 +133,19 @@ def test_contact_details(self): 'street': u'rue Jean Moulin', 'zip_code': u'52330'}) - details = IContactable(self.degaulle).get_contact_details(keys=('email',)) + details = IContactable( + self.degaulle).get_contact_details(keys=('email',)) self.assertEqual(details, {'email': 'charles.de.gaulle@private.com'}) + # test with rambo data empty + details = IContactable(self.rambo).get_contact_details() + self.assertEqual(details['email'], '') + self.assertDictEqual(details['address'], {}) + # test an held position using parent address and related to an organization details = IContactable(self.degaulle['adt']).get_contact_details() - self.assertEqual(details['email'], u'contact@armees.fr') # phone from org + # phone from org + self.assertEqual(details['email'], u'contact@armees.fr') self.assertEqual(details['phone'], u'01000000001') # phone from org self.assertDictEqual(details['address'], {'additional_address_details': '', 'city': u'Paris', @@ -143,7 +156,8 @@ def test_contact_details(self): 'zip_code': u'75008'}) # test an held position using parent address and related to a position - self.assertFalse(self.directory['armeedeterre']['general_adt'].use_parent_address) + self.assertFalse( + self.directory['armeedeterre']['general_adt'].use_parent_address) details = IContactable(self.degaulle['gadt']).get_contact_details() self.assertEqual(details['email'], u'general@armees.fr') self.assertEqual(details['phone'], u'0987654321') @@ -170,7 +184,8 @@ def test_contact_details(self): 'zip_code': u'75008'}) # test an held position not using parent address - details = IContactable(self.pepper['sergent_pepper']).get_contact_details() + details = IContactable( + self.pepper['sergent_pepper']).get_contact_details() self.assertEqual(details['email'], u'sgt.pepper@armees.fr') self.assertEqual(details['phone'], u'0288552211') self.assertDictEqual(details['address'], {'additional_address_details': '', @@ -184,9 +199,16 @@ def test_contact_details(self): # # person contact details are not private api.portal.set_registry_record(name='person_contact_details_private', value=False, interface=IContactCoreParameters) + + # test with rambo data empty : we get info from relations + details = IContactable(self.rambo).get_contact_details() + self.assertEqual(details['email'], 'contact@armees.fr') + self.assertEqual(details['address']['street'], u"rue de l'harmonie") + # test an held position using parent address and related to an organization details = IContactable(self.degaulle['adt']).get_contact_details() - self.assertEqual(details['email'], u'charles.de.gaulle@private.com') # phone from person + # phone from person + self.assertEqual(details['email'], u'charles.de.gaulle@private.com') self.assertEqual(details['phone'], u'01000000001') # phone from org self.assertDictEqual(details['address'], {'additional_address_details': u'b\xe2timent D', 'city': u'Colombey les deux \xe9glises', @@ -197,7 +219,8 @@ def test_contact_details(self): 'zip_code': u'52330'}) # test an held position using parent address and related to a position - self.assertFalse(self.directory['armeedeterre']['general_adt'].use_parent_address) + self.assertFalse( + self.directory['armeedeterre']['general_adt'].use_parent_address) details = IContactable(self.degaulle['gadt']).get_contact_details() self.assertEqual(details['email'], u'charles.de.gaulle@private.com') self.assertEqual(details['phone'], u'0987654321') @@ -224,7 +247,8 @@ def test_contact_details(self): 'zip_code': u'52330'}) # test an held position not using parent address - details = IContactable(self.pepper['sergent_pepper']).get_contact_details() + details = IContactable( + self.pepper['sergent_pepper']).get_contact_details() self.assertEqual(details['email'], u'sgt.pepper@armees.fr') self.assertEqual(details['phone'], u'0288552211') self.assertDictEqual(details['address'], {'additional_address_details': '', diff --git a/src/collective/contact/core/tests/test_behaviors.py b/src/collective/contact/core/tests/test_behaviors.py index 4886f7b4..c4f94f1e 100644 --- a/src/collective/contact/core/tests/test_behaviors.py +++ b/src/collective/contact/core/tests/test_behaviors.py @@ -1,26 +1,25 @@ # -*- coding: utf8 -*- -import unittest - +from collective.contact.core.behaviors import IBirthday +from collective.contact.core.behaviors import IContactDetails +from collective.contact.core.behaviors import IGlobalPositioning +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING +from ecreall.helpers.testing.base import BaseTest +from plone.app.testing.helpers import setRoles +from plone.app.testing.interfaces import TEST_USER_ID +from plone.app.testing.interfaces import TEST_USER_NAME +from plone.autoform.interfaces import IFormFieldProvider +from plone.behavior.interfaces import IBehavior from zope.component import getUtility from zope.event import notify from zope.lifecycleevent import ObjectModifiedEvent -from plone.behavior.interfaces import IBehavior -from plone.autoform.interfaces import IFormFieldProvider -from plone.app.testing.helpers import setRoles -from plone.app.testing.interfaces import TEST_USER_NAME, TEST_USER_ID - -from ecreall.helpers.testing.base import BaseTest - -from collective.contact.core.testing import INTEGRATION -from collective.contact.core.behaviors import IContactDetails,\ - IGlobalPositioning, IBirthday +import unittest class TestBehaviors(unittest.TestCase, BaseTest): """Tests behaviors""" - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestBehaviors, self).setUp() @@ -32,11 +31,11 @@ def setUp(self): def test_behaviors_installation(self): contact_details_behavior = getUtility(IBehavior, - name='collective.contact.core.behaviors.IContactDetails') + name='collective.contact.core.behaviors.IContactDetails') global_positioning_behavior = getUtility(IBehavior, - name='collective.contact.core.behaviors.IGlobalPositioning') + name='collective.contact.core.behaviors.IGlobalPositioning') birthday_behavior = getUtility(IBehavior, - name='collective.contact.core.behaviors.IBirthday') + name='collective.contact.core.behaviors.IBirthday') self.assertEqual(contact_details_behavior.interface, IContactDetails) self.assertEqual(global_positioning_behavior.interface, IGlobalPositioning) @@ -47,7 +46,6 @@ def test_behaviors_installation(self): def test_contact_details_fields(self): item = self.testitem - self.assertIsNone(item.getAttributes()) for attr in ('country', 'region', 'zip_code', 'city', 'street', 'number', 'im_handle', 'cell_phone', 'phone', 'email', 'fax', 'website', diff --git a/src/collective/contact/core/tests/test_content_types.py b/src/collective/contact/core/tests/test_content_types.py index b0d3131a..e0a4ac0a 100644 --- a/src/collective/contact/core/tests/test_content_types.py +++ b/src/collective/contact/core/tests/test_content_types.py @@ -1,26 +1,27 @@ # -*- coding: utf8 -*- -import unittest - -import datetime - -from ecreall.helpers.testing.base import BaseTest - from collective.contact.core.interfaces import IContactCoreParameters -from collective.contact.core.testing import INTEGRATION +from collective.contact.core.setuphandlers import create_test_held_positions +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING +from ecreall.helpers.testing.base import BaseTest from plone import api -from plone.app.testing.interfaces import TEST_USER_ID, TEST_USER_NAME from plone.app.testing.helpers import setRoles +from plone.app.testing.interfaces import TEST_USER_ID +from plone.app.testing.interfaces import TEST_USER_NAME + +import datetime +import unittest class TestContentTypes(unittest.TestCase, BaseTest): """Base class to test new content types""" - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestContentTypes, self).setUp() self.portal = self.layer['portal'] + create_test_held_positions(self.portal) self.mydirectory = self.portal['mydirectory'] self.degaulle = self.mydirectory['degaulle'] self.pepper = self.mydirectory['pepper'] @@ -88,6 +89,7 @@ def test_organization(self): armeedeterre = self.armeedeterre self.assertIn('armeedeterre', self.mydirectory) self.assertEqual(armeedeterre.Title(), "Armée de terre") + self.assertEqual(armeedeterre.enterprise_number, "BE123456789") self.assertIn('corpsa', armeedeterre) self.assertIn('corpsb', armeedeterre) self.assertIn('divisionalpha', self.corpsa) @@ -109,7 +111,8 @@ def test_get_organizations_chain(self): self.divisionalpha.get_organizations_chain()) self.assertEqual([corpsa, divisionalpha], self.divisionalpha.get_organizations_chain(first_index=1)) - self.assertEqual([], self.divisionalpha.get_organizations_chain(first_index=50)) + self.assertEqual( + [], self.divisionalpha.get_organizations_chain(first_index=50)) def test_get_root_organization(self): armeedeterre = self.armeedeterre @@ -139,7 +142,8 @@ def test_get_organizations_titles(self): self.assertIn(u'Brigade LH', brigadelh_titles) self.assertEquals(len(brigadelh_titles), 5) - brigadelh_titles = self.brigadelh.get_organizations_titles(first_index=2) + brigadelh_titles = self.brigadelh.get_organizations_titles( + first_index=2) self.assertIn(u'Division Alpha', brigadelh_titles) self.assertIn(u'Régiment H', brigadelh_titles) self.assertIn(u'Brigade LH', brigadelh_titles) @@ -156,13 +160,17 @@ def test_get_full_title(self): u"Division Alpha - Régiment H - Brigade LH") def test_reindex_suborganization(self): - before = self.portal.portal_catalog(UID=self.brigadelh.UID())[0].get_full_title - self.assertEqual(before, u'Arm\xe9e de terre / Corps A / Division Alpha / R\xe9giment H / Brigade LH') + before = self.portal.portal_catalog(UID=self.brigadelh.UID())[ + 0].get_full_title + self.assertEqual( + before, u'Arm\xe9e de terre / Corps A / Division Alpha / R\xe9giment H / Brigade LH') self.armeedeterre.title = u"Armée de l'air" from zope.lifecycleevent import modified modified(self.armeedeterre) - after = self.portal.portal_catalog(UID=self.brigadelh.UID())[0].get_full_title - self.assertEqual(after, u"Arm\xe9e de l'air / Corps A / Division Alpha / R\xe9giment H / Brigade LH") + after = self.portal.portal_catalog(UID=self.brigadelh.UID())[ + 0].get_full_title + self.assertEqual( + after, u"Arm\xe9e de l'air / Corps A / Division Alpha / R\xe9giment H / Brigade LH") def test_copy_paste(self): cb = self.mydirectory.manage_copyObjects(['armeedeterre']) @@ -171,9 +179,12 @@ def test_copy_paste(self): def test_get_positions(self): # add some positions to self.armeedeterre - self.armeedeterre.invokeFactory('position', 'colonel_adt', title="Colonel de l'armée de terre") - self.armeedeterre.invokeFactory('position', 'lieutenant_adt', title="Lieutenant de l'armée de terre") - self.armeedeterre.invokeFactory('position', 'sergent_adt', title="Sergent de l'armée de terre") + self.armeedeterre.invokeFactory( + 'position', 'colonel_adt', title="Colonel de l'armée de terre") + self.armeedeterre.invokeFactory( + 'position', 'lieutenant_adt', title="Lieutenant de l'armée de terre") + self.armeedeterre.invokeFactory( + 'position', 'sergent_adt', title="Sergent de l'armée de terre") self.assertEquals( [pos.id for pos in self.armeedeterre.get_positions()], ['general_adt', 'colonel_adt', 'lieutenant_adt', 'sergent_adt']) diff --git a/src/collective/contact/core/tests/test_related.py b/src/collective/contact/core/tests/test_related.py index dfcc54b1..3597b3c9 100644 --- a/src/collective/contact/core/tests/test_related.py +++ b/src/collective/contact/core/tests/test_related.py @@ -1,22 +1,21 @@ # -*- coding: utf8 -*- +from collective.contact.core.behaviors import IRelatedOrganizations +from collective.contact.core.indexers import organization_searchable_text +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING from ecreall.helpers.testing.base import BaseTest -from plone import api -import unittest from z3c.relationfield.relation import RelationValue -from zope.intid.interfaces import IIntIds from zope.component import getUtility from zope.interface import alsoProvides +from zope.intid.interfaces import IIntIds -from collective.contact.core.behaviors import IRelatedOrganizations -from collective.contact.core.testing import INTEGRATION -from collective.contact.core.indexers import organization_searchable_text +import unittest class TestSearch(unittest.TestCase, BaseTest): """Tests realted organizations""" - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestSearch, self).setUp() @@ -29,7 +28,7 @@ def setUp(self): def test_related_searchable_text(self): self.assertEqual(organization_searchable_text(self.divisionalpha)(), - u"Armée de terre Corps A Division Alpha") + u"Armée de terre Corps A Division Alpha") intids = getUtility(IIntIds) alsoProvides(self.divisionalpha, IRelatedOrganizations) @@ -37,4 +36,4 @@ def test_related_searchable_text(self): RelationValue(intids.getId(self.divisionbeta)), ] self.assertEqual(organization_searchable_text(self.divisionalpha)(), - u'Armée de terre Corps A Division Beta Armée de terre Corps A Division Alpha') + u'Armée de terre Corps A Division Beta Armée de terre Corps A Division Alpha') diff --git a/src/collective/contact/core/tests/test_robot.py b/src/collective/contact/core/tests/test_robot.py index 12bd187c..0c48de84 100644 --- a/src/collective/contact/core/tests/test_robot.py +++ b/src/collective/contact/core/tests/test_robot.py @@ -1,9 +1,9 @@ -import os -import unittest -import robotsuite +from ..testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING from plone.testing import layered -from ..testing import ACCEPTANCE +import os +import robotsuite +import unittest def test_suite(): @@ -18,7 +18,7 @@ def test_suite(): suite.addTests([ layered( robotsuite.RobotTestSuite(test), - layer=ACCEPTANCE + layer=COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING ), ]) return suite diff --git a/src/collective/contact/core/tests/test_search.py b/src/collective/contact/core/tests/test_search.py index 71ca2fe8..afdae232 100644 --- a/src/collective/contact/core/tests/test_search.py +++ b/src/collective/contact/core/tests/test_search.py @@ -1,26 +1,29 @@ # -*- coding: utf8 -*- -import datetime -import unittest - -from plone import api - +from collective.contact.core.indexers import end_date +from collective.contact.core.indexers import held_position_searchable_text +from collective.contact.core.indexers import held_position_sortable_title +from collective.contact.core.indexers import organization_searchable_text +from collective.contact.core.indexers import person_sortable_title +from collective.contact.core.indexers import start_date +from collective.contact.core.setuphandlers import create_test_held_positions +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING from ecreall.helpers.testing.base import BaseTest +from plone import api -from collective.contact.core.testing import INTEGRATION -from collective.contact.core.indexers import ( - held_position_searchable_text, organization_searchable_text, - person_sortable_title, held_position_sortable_title, start_date, end_date) +import datetime +import unittest class TestSearch(unittest.TestCase, BaseTest): """Tests search""" - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestSearch, self).setUp() self.portal = self.layer['portal'] + create_test_held_positions(self.portal) self.mydirectory = self.portal['mydirectory'] self.degaulle = self.mydirectory['degaulle'] self.pepper = self.mydirectory['pepper'] @@ -47,7 +50,7 @@ def test_indexers(self): held_position_searchable_text(sergent_pepper)(), (u"Mister Pepper Sergent de la brigade LH Armée de terre Corps A " u"Division Alpha Régiment H Brigade LH sgt.pepper@armees.fr") - ) + ) pepper = self.pepper self.assertEqual(person_sortable_title(pepper)(), "pepper") @@ -57,7 +60,8 @@ def test_indexers(self): degaulle = self.degaulle self.assertEqual(person_sortable_title(degaulle)(), 'de-gaulle-charles') - self.assertEqual(start_date(sergent_pepper)(), datetime.date(1980, 6, 5)) + self.assertEqual(start_date(sergent_pepper)(), + datetime.date(1980, 6, 5)) self.assertEqual(end_date(sergent_pepper)(), datetime.date(2100, 1, 1)) self.assertEqual(end_date(self.gadt)(), datetime.date(1970, 11, 9)) self.assertEqual(start_date(self.mydirectory['draper']['captain_crunch'])(), @@ -101,3 +105,5 @@ def test_searchable_fields(self): SearchableText='charles.de.gaulle@private.com') self.assertEqual(len(restults), 1) self.assertEqual(restults[0].getPath(), '/plone/mydirectory/degaulle') + results = catalog.searchResults(SearchableText='BE123456789') + self.assertEqual(len(results), 1) diff --git a/src/collective/contact/core/tests/test_setup.py b/src/collective/contact/core/tests/test_setup.py index 03470273..3022cdd1 100644 --- a/src/collective/contact/core/tests/test_setup.py +++ b/src/collective/contact/core/tests/test_setup.py @@ -1,24 +1,25 @@ -import unittest - -from plone import api +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING +from Products.CMFPlone.utils import get_installer -from collective.contact.core.testing import INTEGRATION +import unittest class TestSetup(unittest.TestCase): - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): + super(TestSetup, self).setUp() self.app = self.layer['app'] self.portal = self.layer['portal'] - self.qi_tool = api.portal.get_tool('portal_quickinstaller') + self.qi = get_installer(self.portal) def test_product_is_installed(self): """ Validate that our products GS profile has been run and the product installed """ - pid = 'collective.contact.core' - installed = [p['id'] for p in self.qi_tool.listInstalledProducts()] - self.assertTrue(pid in installed, - 'package appears not to have been installed') + self.assertTrue(self.qi.is_product_installed( + 'collective.contact.core')) + + def test_test_data_created(self): + self.assertTrue('mydirectory' in self.portal) diff --git a/src/collective/contact/core/tests/test_subscribers.py b/src/collective/contact/core/tests/test_subscribers.py new file mode 100644 index 00000000..380d173f --- /dev/null +++ b/src/collective/contact/core/tests/test_subscribers.py @@ -0,0 +1,38 @@ +# -*- coding: utf8 -*- + +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING +from ecreall.helpers.testing.search import BaseSearchTest +from plone import api +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID +from plone.app.testing.interfaces import TEST_USER_NAME + +import unittest + + +class TestUtils(unittest.TestCase, BaseSearchTest): + + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING + + def setUp(self): + super(TestUtils, self).setUp() + self.app = self.layer['app'] + self.portal = self.layer['portal'] + mydirectory = self.portal['mydirectory'] + self.degaulle = mydirectory['degaulle'] + + def test_recordModified(self): + """ """ + self.login(TEST_USER_NAME) + setRoles(self.portal, TEST_USER_ID, ['Manager']) + dguid = self.degaulle.UID() + record_name = 'collective.contact.core.interfaces.IContactCoreParameters.contact_source_metadata_content' + self.assertEqual(api.portal.get_registry_record(record_name), u'{gft}') + self.assertEqual(self.getBrain(dguid).contact_source, + self.degaulle.get_full_title()) + # we change registry + api.portal.set_registry_record( + record_name, u'{gft} from {city} on {email}') + # metadata has been updated + self.assertEqual(self.getBrain(dguid).contact_source, + u'Général Charles De Gaulle from Colombey les deux églises on charles.de.gaulle@private.com') diff --git a/src/collective/contact/core/tests/test_utils.py b/src/collective/contact/core/tests/test_utils.py index 7da4b679..e775bc71 100644 --- a/src/collective/contact/core/tests/test_utils.py +++ b/src/collective/contact/core/tests/test_utils.py @@ -1,6 +1,7 @@ # -*- coding: utf8 -*- -from collective.contact.core.testing import INTEGRATION +from collective.contact.core.setuphandlers import create_test_held_positions +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING from collective.contact.core.utils import get_gender_and_number from ecreall.helpers.testing.base import BaseTest from plone.app.testing.interfaces import TEST_USER_NAME @@ -9,13 +10,13 @@ class TestUtils(unittest.TestCase, BaseTest): - - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestUtils, self).setUp() self.app = self.layer['app'] self.portal = self.layer['portal'] + create_test_held_positions(self.portal) mydirectory = self.portal['mydirectory'] self.degaulle = mydirectory['degaulle'] self.pepper = mydirectory['pepper'] @@ -59,9 +60,15 @@ def test_get_gender_and_number(self): # Male have priority over Female # Male, Plural - self.assertEqual( - get_gender_and_number([self.sergent_pepper, self.draper]), u'MP') + self.assertEqual(get_gender_and_number( + [self.sergent_pepper, self.draper]), u'MP') # user unicity, if we pass twice same person, it stays singular, even thru held_position - self.assertEqual( - get_gender_and_number([self.pepper, self.sergent_pepper]), u'MS') + self.assertEqual(get_gender_and_number( + [self.pepper, self.sergent_pepper]), u'MS') + + # parameters use_by and use_to will prepend a 'B' or 'T' to returned value + self.assertEqual(get_gender_and_number( + [self.sergent_pepper, self.draper], use_by=True), u'BMP') + self.assertEqual(get_gender_and_number( + [self.pepper, self.sergent_pepper], use_to=True), u'TMS') diff --git a/src/collective/contact/core/tests/test_views.py b/src/collective/contact/core/tests/test_views.py index 9b9629b1..b19a6ea4 100644 --- a/src/collective/contact/core/tests/test_views.py +++ b/src/collective/contact/core/tests/test_views.py @@ -1,23 +1,23 @@ # -*- coding: utf8 -*- -import unittest - -from plone.app.testing.interfaces import TEST_USER_NAME - +from collective.contact.core.behaviors import ADDRESS_FIELDS +from collective.contact.core.setuphandlers import create_test_held_positions +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING from ecreall.helpers.testing.base import BaseTest +from plone.app.testing.interfaces import TEST_USER_NAME -from collective.contact.core.testing import INTEGRATION -from collective.contact.core.behaviors import ADDRESS_FIELDS +import unittest class TestView(unittest.TestCase, BaseTest): - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestView, self).setUp() self.login(TEST_USER_NAME) self.app = self.layer['app'] self.portal = self.layer['portal'] + create_test_held_positions(self.portal) mydirectory = self.portal['mydirectory'] self.degaulle = mydirectory['degaulle'] self.adt = self.degaulle['adt'] @@ -254,7 +254,8 @@ def test_person_basefields_view(self): def test_person_contact_details_view(self): view = self.degaulle.restrictedTraverse("@@contactdetails") view.update() - self.assertEqual(view.contact_details['email'], 'charles.de.gaulle@private.com') + self.assertEqual( + view.contact_details['email'], 'charles.de.gaulle@private.com') self.assertEqual(view.contact_details['phone'], '') self.assertEqual(view.contact_details['cell_phone'], '') self.assertEqual(view.contact_details['im_handle'], '') diff --git a/src/collective/contact/core/tests/test_workflow.py b/src/collective/contact/core/tests/test_workflow.py index ee5a0654..5ba5ecb4 100644 --- a/src/collective/contact/core/tests/test_workflow.py +++ b/src/collective/contact/core/tests/test_workflow.py @@ -1,49 +1,49 @@ # -*- coding: utf8 -*- -import unittest - +from collective.contact.core.testing import COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING from ecreall.helpers.testing import member as memberhelpers from ecreall.helpers.testing.workflow import BaseWorkflowTest -from collective.contact.core.testing import INTEGRATION +import unittest USERDEFS = [ - {'user': 'manager', 'roles': ('Manager', 'Member',), 'groups': ()}, - {'user': 'contributor', 'roles': ('Contributor', 'Member',), 'groups': ()}, - {'user': 'member', 'roles': ('Member',), 'groups': ()}, - ] + {'user': 'manager', 'roles': ('Manager', 'Member',), 'groups': ()}, + {'user': 'contributor', 'roles': ( + 'Contributor', 'Member',), 'groups': ()}, + {'user': 'member', 'roles': ('Member',), 'groups': ()}, +] PERSON_PERMISSIONS = {'active': - {'Access contents information': - ('manager', 'contributor', 'member'), - 'Modify portal content': - ('manager', 'contributor'), - 'View': - ('manager', 'contributor', 'member'), - }, - 'deactivated': - {'Access contents information': - ('manager', 'contributor'), - 'Modify portal content': - ('manager', 'contributor'), - 'View': - ('manager', 'contributor'), - }, - } + {'Access contents information': + ('manager', 'contributor', 'member'), + 'Modify portal content': + ('manager', 'contributor'), + 'View': + ('manager', 'contributor', 'member'), + }, + 'deactivated': + {'Access contents information': + ('manager', 'contributor'), + 'Modify portal content': + ('manager', 'contributor'), + 'View': + ('manager', 'contributor'), + }, + } WORKFLOW_TRACK = [('', 'active'), ('deactivate', 'deactivated'), ('activate', 'active'), - ] + ] class TestSecurity(unittest.TestCase, BaseWorkflowTest): """Tests collective.contact.core workflows""" - layer = INTEGRATION + layer = COLLECTIVE_CONTACT_CORE_ACCEPTANCE_TESTING def setUp(self): super(TestSecurity, self).setUp() @@ -56,7 +56,8 @@ def test_person_permissions(self): degaulle = self.degaulle workflow = self.portal.portal_workflow self.login('manager') - self.assertCheckPermissions(degaulle, PERSON_PERMISSIONS['active'], USERDEFS) + self.assertCheckPermissions( + degaulle, PERSON_PERMISSIONS['active'], USERDEFS) for (transition, state) in WORKFLOW_TRACK: if transition: diff --git a/src/collective/contact/core/upgrades/configure.zcml b/src/collective/contact/core/upgrades/configure.zcml index 483997a0..5a486f5e 100644 --- a/src/collective/contact/core/upgrades/configure.zcml +++ b/src/collective/contact/core/upgrades/configure.zcml @@ -3,8 +3,6 @@ xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="collective.contact.core"> - - + + + + + + + + + + + + diff --git a/src/collective/contact/core/upgrades/profiles/v2/types/directory.xml b/src/collective/contact/core/upgrades/profiles/v2/types/directory.xml index 502cb0dd..ce08d0ef 100644 --- a/src/collective/contact/core/upgrades/profiles/v2/types/directory.xml +++ b/src/collective/contact/core/upgrades/profiles/v2/types/directory.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Directory - string:${portal_url}/directory_icon.png + string:${portal_url}/++resource++collective.contact.core/directory_icon.png directory string:${folder_url}/++add++directory diff --git a/src/collective/contact/core/upgrades/profiles/v2/types/held_position.xml b/src/collective/contact/core/upgrades/profiles/v2/types/held_position.xml index 9ec431dd..d9cb33ea 100644 --- a/src/collective/contact/core/upgrades/profiles/v2/types/held_position.xml +++ b/src/collective/contact/core/upgrades/profiles/v2/types/held_position.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Held position - string:${portal_url}/position_icon.png + string:${portal_url}/++resource++collective.contact.core/position_icon.png held_position string:${folder_url}/++add++held_position diff --git a/src/collective/contact/core/upgrades/profiles/v2/types/organization.xml b/src/collective/contact/core/upgrades/profiles/v2/types/organization.xml index 392bd8cb..28304c15 100644 --- a/src/collective/contact/core/upgrades/profiles/v2/types/organization.xml +++ b/src/collective/contact/core/upgrades/profiles/v2/types/organization.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Organization - string:${portal_url}/organization_icon.png + string:${portal_url}/++resource++collective.contact.core/organization_icon.png organization string:${folder_url}/++add++organization diff --git a/src/collective/contact/core/upgrades/profiles/v2/types/person.xml b/src/collective/contact/core/upgrades/profiles/v2/types/person.xml index 47b588c0..189db30d 100644 --- a/src/collective/contact/core/upgrades/profiles/v2/types/person.xml +++ b/src/collective/contact/core/upgrades/profiles/v2/types/person.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Person - string:${portal_url}/person_icon.png + string:${portal_url}/++resource++collective.contact.core/person_icon.png person string:${folder_url}/++add++person diff --git a/src/collective/contact/core/upgrades/profiles/v2/types/position.xml b/src/collective/contact/core/upgrades/profiles/v2/types/position.xml index 0c5329d3..205391dd 100644 --- a/src/collective/contact/core/upgrades/profiles/v2/types/position.xml +++ b/src/collective/contact/core/upgrades/profiles/v2/types/position.xml @@ -3,7 +3,7 @@ xmlns:i18n="http://xml.zope.org/namespaces/i18n"> Position - string:${portal_url}/position_icon.png + string:${portal_url}/++resource++collective.contact.core/position_icon.png position string:${folder_url}/++add++position diff --git a/src/collective/contact/core/upgrades/upgrades.py b/src/collective/contact/core/upgrades/upgrades.py index 0c84f750..9a75603a 100644 --- a/src/collective/contact/core/upgrades/upgrades.py +++ b/src/collective/contact/core/upgrades/upgrades.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- - from collective.contact.core.interfaces import IContactCoreParameters from collective.contact.core.interfaces import IHeldPosition from collective.contact.widget.interfaces import IContactContent -from ecreall.helpers.upgrade.interfaces import IUpgradeTool from plone import api +from Products.CMFCore.utils import getToolByName from Products.CMFPlone.utils import base_hasattr from z3c.relationfield.event import updateRelations from z3c.relationfield.interfaces import IHasRelations @@ -19,15 +18,34 @@ def reindex_relations(context): rcatalog = getUtility(ICatalog) rcatalog.clear() catalog = api.portal.get_tool('portal_catalog') - brains = catalog.searchResults(object_provides=IHasRelations.__identifier__) + brains = catalog.searchResults( + object_provides=IHasRelations.__identifier__) for brain in brains: obj = brain.getObject() updateRelations(obj, None) +def refreshResources(self): + """Refresh all resource registries + """ + css_tool = getToolByName(self.portal, 'portal_css') + css_tool.cookResources() + + js_tool = getToolByName(self.portal, 'portal_javascripts') + js_tool.cookResources() + + kss_tool = getToolByName(self.portal, 'portal_kss', None) + if kss_tool: + kss_tool.cookResources() + + return "Js, kss and css refreshed" + + def v2(context): - tool = IUpgradeTool(context) - tool.runProfile('collective.contact.core.upgrades:v2') + context.runAllImportStepsFromProfile( + 'profile-collective.contact.core.upgrades:v2', + purge_old=False, + ) catalog = api.portal.get_tool(name='portal_catalog') catalog.clearFindAndRebuild() reindex_relations(context) @@ -35,73 +53,138 @@ def v2(context): def v3(context): catalog = api.portal.get_tool('portal_catalog') - brains = catalog.unrestrictedSearchResults(object_provides=IContactContent.__identifier__) + brains = catalog.unrestrictedSearchResults( + object_provides=IContactContent.__identifier__) for brain in brains: obj = brain.getObject() obj.is_created = True def v4(context): - IUpgradeTool(context).runImportStep('collective.contact.core', 'rolemap') + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'rolemap', + ) def v5(context): - tool = IUpgradeTool(context) - tool.runProfile('collective.contact.widget:default') + context.runAllImportStepsFromProfile( + 'profile-collective.contact.widget:default', + ) # add sortable_title column and reindex persons and organizations - tool.addMetadata('sortable_title') - tool.reindexContents(IContactContent, ('sortable_title',)) + catalog = api.portal.get_tool('portal_catalog') + catalog.addColumn('sortable_title') + items = api.content.find( + object_provides='collective.contact.widget.interfaces.IContactContent' + ) + for item in items: + item.getObject().reindexObject(idxs=['sortable_title'] + ) def v6(context): - tool = IUpgradeTool(context) - tool.runProfile('collective.contact.core.upgrades:v6') - tool.refreshResources() + context.runAllImportStepsFromProfile( + 'profile-collective.contact.core.upgrades:v6', + ) + refreshResources() def v7(context): - tool = IUpgradeTool(context) - tool.runProfile('collective.contact.core.upgrades:v7') + context.runAllImportStepsFromProfile( + 'profile-collective.contact.core.upgrades:v7', + ) def v8(context): - tool = IUpgradeTool(context) - tool.runProfile('collective.contact.core.upgrades:v8') + context.runAllImportStepsFromProfile( + 'profile-collective.contact.core.upgrades:v8', + ) def v9(context): - tool = IUpgradeTool(context) - tool.runProfile('collective.contact.core.upgrades:v9') + context.runAllImportStepsFromProfile( + 'profile-collective.contact.core.upgrades:v9', + ) def v10(context): catalog = api.portal.get_tool('portal_catalog') - brains = catalog.searchResults(object_provides=IHeldPosition.__identifier__) + brains = catalog.searchResults( + object_provides=IHeldPosition.__identifier__) for brain in brains: brain.getObject().reindexObject(['start', 'end']) def v11(context): - IUpgradeTool(context).runImportStep('collective.contact.core', 'typeinfo') - IUpgradeTool(context).runImportStep('collective.contact.core', 'plone.app.registry') - val = api.portal.get_registry_record(name='person_contact_details_private', interface=IContactCoreParameters) + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'typeinfo', + ) + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'plone.app.registry', + ) + val = api.portal.get_registry_record( + name='person_contact_details_private', interface=IContactCoreParameters) if val is None: api.portal.set_registry_record(name='person_contact_details_private', value=True, interface=IContactCoreParameters) def v12(context): - IUpgradeTool(context).runImportStep('collective.contact.core', 'plone.app.registry') + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'plone.app.registry', + ) catalog = api.portal.get_tool('portal_catalog') - brains = catalog.unrestrictedSearchResults(object_provides=IContactContent.__identifier__) + brains = catalog.unrestrictedSearchResults( + object_provides=IContactContent.__identifier__) for brain in brains: - brain.getObject().reindexObject(['Title', 'sortable_title', 'get_full_title', 'SearchableText']) + brain.getObject().reindexObject( + ['Title', 'sortable_title', 'get_full_title', 'SearchableText']) def v13(context): catalog = api.portal.get_tool('portal_catalog') - brains = catalog.unrestrictedSearchResults(object_provides=IContactContent.__identifier__) + brains = catalog.unrestrictedSearchResults( + object_provides=IContactContent.__identifier__) for brain in brains: obj = brain.getObject() if base_hasattr(obj, 'is_created'): delattr(obj, 'is_created') + + +def v14(context): + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'plone.app.registry', 'typeinfo' + ) + + +def v15(context): + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'plone.app.registry', + ) + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'catalog' + ) + items = api.content.find( + object_provides='collective.contact.widget.interfaces.IContactContent' + ) + for item in items: + item.getObject().reindexObject(idxs=['email']) + + +def v16(context): + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'plone.app.registry', + ) + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'catalog' + ) + items = api.content.find( + object_provides='collective.contact.widget.interfaces.IContactContent' + ) + for item in items: + item.getObject().reindexObject(idxs=['email', 'contact_source']) + + +def refresh_resources_registry(context): + context.runImportStepFromProfile( + 'profile-collective.contact.core:default', 'plone.app.registry', + ) diff --git a/src/collective/contact/core/utils.py b/src/collective/contact/core/utils.py index 99ec2824..0d6d2e66 100644 --- a/src/collective/contact/core/utils.py +++ b/src/collective/contact/core/utils.py @@ -4,11 +4,13 @@ from collective.contact.core.interfaces import IHeldPosition -def get_gender_and_number(contacts): +def get_gender_and_number(contacts, use_by=False, use_to=False): # noqa for now 'is too complex' """Return gender and number of given contacts. Returns None if not genderable. Returns a 2 letters code if genderable: - first letter is for gender, M for "male", "F" for female; + --> if use_from, we prepend 'B' to gender, it will manage 'proposed by mister X'; + --> if use_to, we prepend 'T' to gender, it will manage 'propose object to mister X'. - second letter is for number, S for "Singular", "P" for "Plural". p_contacts may be any kind of contacts, we will try to get person of it.""" gender = None @@ -35,5 +37,9 @@ def get_gender_and_number(contacts): gender = person_gender res = gender if gender: + if use_by: + gender = 'B' + gender + elif use_to: + gender = 'T' + gender res = gender + (number > 1 and 'P' or 'S') return res diff --git a/src/collective/contact/core/vocabulary.py b/src/collective/contact/core/vocabularies.py similarity index 59% rename from src/collective/contact/core/vocabulary.py rename to src/collective/contact/core/vocabularies.py index 151379e1..c4527f9d 100644 --- a/src/collective/contact/core/vocabulary.py +++ b/src/collective/contact/core/vocabularies.py @@ -1,11 +1,10 @@ +from . import _ from Acquisition import aq_parent - -from zope.schema.vocabulary import SimpleVocabulary +from collective.contact.core import logger +from zope.interface import implementer +from zope.interface import provider from zope.schema.interfaces import IVocabularyFactory - -from five import grok - -from . import _ +from zope.schema.vocabulary import SimpleVocabulary class NoDirectoryFound(Exception): @@ -26,29 +25,33 @@ def get_directory(context): def get_vocabulary(schema_list): terms = [] + tokens = set() for item in schema_list: - term = SimpleVocabulary.createTerm(item['token'], - item['token'], + token = item['token'] + if token in tokens: + logger.error("Duplicated value in vocabulary: {}".format(token)) + continue + + tokens.add(token) + term = SimpleVocabulary.createTerm(token, + token, item['name']) terms.append(term) return SimpleVocabulary(terms) -class PositionTypes(grok.GlobalUtility): - grok.name("PositionTypes") - grok.implements(IVocabularyFactory) +@provider(IVocabularyFactory) +def PositionTypes(context): - def __call__(self, context): - try: - directory = get_directory(context) - return get_vocabulary(directory.position_types) - except NoDirectoryFound: - return SimpleVocabulary([]) + try: + directory = get_directory(context) + return get_vocabulary(directory.position_types) + except NoDirectoryFound: + return SimpleVocabulary([]) -class OrganizationTypesOrLevels(grok.GlobalUtility): - grok.name("OrganizationTypesOrLevels") - grok.implements(IVocabularyFactory) +@implementer(IVocabularyFactory) +class OrganizationTypesOrLevels(object): def get_container_type(self, context): request = context.REQUEST @@ -72,14 +75,15 @@ def __call__(self, context): return SimpleVocabulary([]) -class Genders(grok.GlobalUtility): - grok.name("Genders") - grok.implements(IVocabularyFactory) +OrganizationTypesOrLevelsFactory = OrganizationTypesOrLevels() - def __call__(self, context): - terms = [] - genders = {'M': _("Male"), 'F': _("Female")} - for (token, value) in genders.iteritems(): - term = SimpleVocabulary.createTerm(token, token, value) - terms.append(term) - return SimpleVocabulary(terms) + +@provider(IVocabularyFactory) +def Genders(context): + + terms = [] + genders = {'M': _("Male"), 'F': _("Female")} + for (token, value) in genders.items(): + term = SimpleVocabulary.createTerm(token, token, value) + terms.append(term) + return SimpleVocabulary(terms) diff --git a/src/collective/contact/core/vocabularies.zcml b/src/collective/contact/core/vocabularies.zcml new file mode 100644 index 00000000..87e7b5b9 --- /dev/null +++ b/src/collective/contact/core/vocabularies.zcml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/test_plone50.cfg b/test_plone50.cfg new file mode 100644 index 00000000..88b13202 --- /dev/null +++ b/test_plone50.cfg @@ -0,0 +1,11 @@ +[buildout] + +extends = + https://raw.github.com/collective/buildout.plonetest/master/test-5.0.x.cfg + https://raw.githubusercontent.com/collective/buildout.plonetest/master/qa.cfg + base.cfg + +update-versions-file = test_plone50.cfg + +[versions] +plone.schemaeditor = >=2.0.18 diff --git a/test_plone51.cfg b/test_plone51.cfg new file mode 100644 index 00000000..82dd94fb --- /dev/null +++ b/test_plone51.cfg @@ -0,0 +1,41 @@ +[buildout] + +extends = + https://raw.github.com/collective/buildout.plonetest/master/test-5.1.x.cfg + https://raw.githubusercontent.com/collective/buildout.plonetest/master/qa.cfg + base.cfg + +update-versions-file = test_plone51.cfg + +[versions] +plone.testing = 5.0.0 + +# Added by buildout at 2020-01-06 16:41:12.265037 +PyYAML = 5.2 +argh = 0.26.2 +chardet = 3.0.4 +createcoverage = 1.2 +ecreall.helpers.testing = 1.4 +ecreall.helpers.upgrade = 1.3 +flake8 = 2.5.2 +idna = 2.8 +mccabe = 0.4.0 +pathtools = 0.1.2 +pep8 = 1.7.0 +pkginfo = 1.5.0.1 +plone.recipe.codeanalysis = 2.1 +pyflakes = 1.0.0 +requests-toolbelt = 0.9.1 +watchdog = 0.9.0 + +# Required by: +# plone.recipe.codeanalysis==2.1 +check-manifest = 0.31 + +# Required by: +# zest.releaser==6.17.2 +colorama = 0.4.3 + +# Required by: +# collective.contact.core==1.24.dev0 +vobject = 0.8.1rc0 diff --git a/test_plone52.cfg b/test_plone52.cfg new file mode 100644 index 00000000..f8a7c224 --- /dev/null +++ b/test_plone52.cfg @@ -0,0 +1,11 @@ +[buildout] + +extends = + https://raw.github.com/collective/buildout.plonetest/master/test-5.2.x.cfg + https://raw.githubusercontent.com/collective/buildout.plonetest/master/qa.cfg + base.cfg + +update-versions-file = test_plone52.cfg + +[versions] +plone.testing = 7.0.1 diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..e8b3ad71 --- /dev/null +++ b/tox.ini @@ -0,0 +1,174 @@ +[tox] +envlist = + {py27,py37}-lint, + py{27}-Plone{51}, + py{27,37}-Plone{52}, + build_instance, +# docs, +# coverage-report, + +skip_missing_interpreters = True + +[testenv] +skip_install = true + +extras = + develop + test + +commands = + {envbindir}/buildout -c {toxinidir}/{env:version_file} buildout:directory={envdir} buildout:develop={toxinidir} bootstrap + {envbindir}/buildout -c {toxinidir}/{env:version_file} buildout:directory={envdir} buildout:develop={toxinidir} annotate + {envbindir}/buildout -c {toxinidir}/{env:version_file} buildout:directory={envdir} buildout:develop={toxinidir} install test robot code-analysis + coverage run {envbindir}/test -v1 --auto-color {posargs} + # coverage run {envbindir}/test -v --all -t robot {posargs} + {envbindir}/code-analysis + +setenv = + COVERAGE_FILE=.coverage.{envname} + version_file=test_plone51.cfg + Plone50: version_file=test_plone50.cfg + Plone51: version_file=test_plone51.cfg + Plone52: version_file=test_plone52.cfg + +deps = + -rrequirements.txt + Plone50: -cconstraints_plone50.txt + Plone51: -cconstraints_plone51.txt + Plone52: -cconstraints_plone52.txt + coverage + +[testenv:coverage-report] +skip_install = true +usedevelop = True +basepython = python2.7 + +deps = + coverage + -cconstraints_plone51.txt + +setenv = + COVERAGE_FILE=.coverage + +commands = + coverage erase + coverage combine + coverage html + coverage xml + coverage report + + +[lint] +skip_install = true + +deps = + isort + flake8 + # helper to generate HTML reports: + flake8-html + # Useful flake8 plugins that are Python and Plone specific: + flake8-coding + flake8-debugger + flake8-deprecated + flake8-print + #flake8-pytest + flake8-todo + flake8-isort + mccabe + # Potential flake8 plugins that should be used: # TBD + #flake8-blind-except + #flake8-commas + #flake8-docstrings + #flake8-mypy + #flake8-pep3101 + #flake8-plone-hasattr + #flake8-string-format + #flake8_strict + #flake8-quotes + #flake8-polyfill + +commands = + mkdir -p {toxinidir}/reports/flake8 + - flake8 --format=html --htmldir={toxinidir}/reports/flake8 --doctests src setup.py + flake8 --doctests src tests setup.py + isort --check-only --recursive {toxinidir}/src + +whitelist_externals = + mkdir + +[testenv:isort-apply] +skip_install = true + +deps = + isort + +commands = + isort --apply --recursive {toxinidir}/src + +[testenv:py27-lint] +basepython = python2.7 +skip_install = true +deps = {[lint]deps} +commands = {[lint]commands} +whitelist_externals = {[lint]whitelist_externals} + +[testenv:py35-lint] +basepython = python3.5 +skip_install = true +deps = {[lint]deps} +commands = {[lint]commands} +whitelist_externals = {[lint]whitelist_externals} + +[testenv:py36-lint] +basepython = python3.6 +skip_install = true +deps = {[lint]deps} +commands = {[lint]commands} +whitelist_externals = {[lint]whitelist_externals} + +[testenv:py37-lint] +basepython = python3.7 +skip_install = true +deps = {[lint]deps} +commands = {[lint]commands} +whitelist_externals = {[lint]whitelist_externals} + +[testenv:docs] +skip_install = true + +deps = + Sphinx + +commands = + sphinx-build -b html -d _build/docs/doctrees docs _build/docs/html + +[testenv:update_translation] +skip_install = true + +deps = + i18ndude + +commands = + i18ndude find-untranslated + i18ndude rebuild-pot + i18ndude merge + i18ndude sync + i18ndude list + +[testenv:release] +skip_install = true + +deps = + zest.releaser[recommended] + +commands = + fullrelease --no-input -v + +[testenv:build_instance] +basepython = python2.7 +skip_install = true + +commands = + {envbindir}/buildout -c {toxinidir}/{env:version_file} buildout:directory={toxinidir} bootstrap + {envbindir}/buildout -c {toxinidir}/{env:version_file} buildout:directory={toxinidir} annotate + {envbindir}/buildout -c {toxinidir}/{env:version_file} buildout:directory={toxinidir} diff --git a/travis.cfg b/travis.cfg index d253aa19..fc4620d1 100644 --- a/travis.cfg +++ b/travis.cfg @@ -3,10 +3,10 @@ allow-hosts += code.google.com robotframework.googlecode.com extends = - https://raw.github.com/collective/buildout.plonetest/master/travis-4.3.x.cfg + https://raw.github.com/collective/buildout.plonetest/master/travis-5.1.x.cfg base.cfg parts = download install test coverage-sh createcoverage [versions] # Temporary set to avoid unified installer download error -Plone = 4.3.17 +Plone = 5.1.4 diff --git a/versions.cfg b/versions.cfg index ffcbc4a9..2e20bb72 100644 --- a/versions.cfg +++ b/versions.cfg @@ -1,40 +1,37 @@ [versions] -collective.z3cform.datagridfield = 1.1 -ecreall.helpers.testing = 1.4 -ecreall.helpers.upgrade = 1.3 -ipdb = 0.8.1 -ipython = 3.0.0 -mock = 1.0.1 -plone.api = 1.4.11 -plone.app.locales = 4.3.7 -plone.formwidget.datetime = 1.2 -setuptools = -vobject = 0.8.1c -zc.buildout = -zc.recipe.egg = 2.0.3 +# collective.z3cform.datagridfield = 1.1 +# ecreall.helpers.testing = 1.4 +# ecreall.helpers.upgrade = 1.3 +# ipdb = 0.8.1 +# ipython = 3.0.0 +# mock = 1.0.1 +# setuptools = +# vobject = 0.8.1c +# zc.buildout = +# zc.recipe.egg = 2.0.3 -# robot -plone.app.robotframework = 1.2.0 -robotframework = 3.0.2 -robotframework-debuglibrary = 1.1.2 -robotframework-seleniumlibrary = 3.1.1 -robotframework-selenium2library = 3.0.0 -robotframework-selenium2screenshots = 0.8.1 -robotsuite = 2.0.0 -selenium = 3.11.0 -sphinxcontrib-robotframework = 0.7.0 +# # robot +# plone.app.robotframework = 1.2.0 +# robotframework = 3.0.2 +# robotframework-debuglibrary = 1.1.2 +# robotframework-seleniumlibrary = 3.1.1 +# robotframework-selenium2library = 3.0.0 +# robotframework-selenium2screenshots = 0.8.1 +# robotsuite = 2.0.0 +# selenium = 3.11.0 +# sphinxcontrib-robotframework = 0.7.0 -PyYAML = 3.11 -argh = 0.26.1 -pathtools = 0.1.2 -watchdog = 0.8.3 +# PyYAML = 3.11 +# argh = 0.26.1 +# pathtools = 0.1.2 +# watchdog = 0.8.3 -# coverage -createcoverage = 1.2 -mccabe = 0.4.0 -pep8 = 1.7.0 -plone.recipe.codeanalysis = 2.1 -pyflakes = 1.0.0 -flake8 = 2.5.2 +# # coverage +# createcoverage = 1.2 +# mccabe = 0.4.0 +# pep8 = 1.7.0 +# plone.recipe.codeanalysis = 2.1 +# pyflakes = 1.0.0 +# flake8 = 2.5.2 -check-manifest = 0.31 +# check-manifest = 0.31