pax_global_header00006660000000000000000000000064124570335430014520gustar00rootroot0000000000000052 comment=4d095aa02d6fc3ccfa7e8e263f08d7ec58dc3e20 python-oerplib-0.8.4/000077500000000000000000000000001245703354300145045ustar00rootroot00000000000000python-oerplib-0.8.4/.bzrignore000066400000000000000000000001161245703354300165040ustar00rootroot00000000000000*.pyc *.swp *.vim dev_* doc/build doc/html doc/doctrees dist/ build/ MANIFEST python-oerplib-0.8.4/.gitignore000066400000000000000000000012541245703354300164760ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ python-oerplib-0.8.4/AUTHORS.txt000066400000000000000000000001371245703354300163730ustar00rootroot00000000000000Original Author --------------- Sébastien Alix , python-oerplib-0.8.4/CHANGELOG.rst000066400000000000000000000150231245703354300165260ustar00rootroot000000000000000.8.4 ===== - FIX: Auto-discovery the Odoo version via HTTPS (fixes #9) - FIX: Simplification of exception handling, error messages more explicit (fixes #12) 0.8.3 ===== - FIX: Use the user context by default if it is not supplied in 'OERP.search()', 'OERP.create()', 'OERP.read()', 'OERP.write()' and 'OERP.unlink()' (fixes #6) - FIX: TypeError: object of type 'BrowseRecordIterator' has no len() (fixes #5) - FIX: Error while creating report on OpenERP 7.0 with multiple workers (fixes #3) - Project migrated from Launchpad to GitHub 0.8.2 ===== - Integrating the new brand 'Odoo' in addition to 'OpenERP' in the documentation - FIX: On 'browse_record' objects, unknown field types are now interpreted as 'char' (bug #1289781) - FIX: 'OERP.write_record()' and 'OERP.unlink_record()' methods does not use the user context by default (bug #1325421) 0.8.1 ===== - 'OERP.inspect.relations()' method: display root models in red by default - 'OERP.inspect.relations()' method: add a 's' flag on fields.function which are searchable (#1272660) - 'OERP.inspect.dependencies()' method: display module names in lowercase - FIX: 'OERP.inspect.dependencies()' method: unable to find an indirect dependency (bug #1265860) - FIX: 'OERP.inspect.relations()' method: relationships of starting models are not all displayed (bug #1287833) - FIX: Error when using the NetRPC protocol without specifing the 'version' parameter (bug #1267188) - FIX: On 'browse_record' objects, error when using += and -= operators on empty one2many and many2many fields (bug #1284768 ) 0.8.0 ===== - New service 'inspect' ('OERP.inspect') able to draw models relationship and modules dependencies graphs (output in any Graphviz supported formats) and to detect 'on_change' methods of data models - Able to save and load connection information ('~/.oerplibrc' by default) - Experimental JSON-RPC connector added in 'oerplib.rpc' (the 'OERP' class does not support it) - one2many and many2many fields on browse records improved (able to set easily IDs or records with '='/'+='/'-=' operators) - Support for the OpenERP 'fields.integer_big' added - Add XML-RPC support for OpenERP 8.0 (new available '/xmlrpc/2' URL) - Documentation updated - FIX: Fix the 'xmlrpc+ssl' (XML-RPCS) protocol (Bug #1221146) 0.7.3 ===== - FIX: There is no way to create report for multiple objects (Bug #1248314) 0.7.2 ===== - FIX: Unable to make any request if the 'base_crypt' module is installed (Bug #1168977) 0.7.1 ===== - Dictionary interface of OSV/Model class conflicts with OSV methods (Bug #1167796) - Fixed the documentation (formatting error about the 'common' service) 0.7.0 ===== - User context is sent automatically (OpenERP >= 6.1) - Able to use named parameters with OSV methods (OpenERP >= 6.1) - Auto-detect the OpenERP server version (enable or disable some features according to the version) - Add support for 'html' type fields - [REGRESSION] one2many and many2many descriptor attributes now return a generator to iterate on 'browse_record' instead of a list - [REGRESSION] 'osv_name' parameter of some functions has been renamed to 'model' - 'OERP.timeout' property deprecated (use 'OERP.config["timeout"]' instead) - 'OERP.get_osv_name()' method deprecated (use 'record.__osv__["name"]' instead) - Documentation updated - FIX: Internal RPC method requests like 'read' should send a list of IDs (Bug #1064087) - FIX: Use the context when browsing relation fields of a 'browse_record' (Bug #1066554 and #1066565) 0.6.0 ===== - Dynamic access to the '/common' RPC service ('OERP.common' attribute) - Dynamic access to the '/wizard' RPC service ('OERP.wizard' attribute) - Able to set a value to one2many and many2many fields for 'browse_record' - Support for the OpenERP 'fields.reference' added - Support for XML-RPC over SSL added - Timeout configuration for RPC requests - Documentation updated 0.5.3 ===== - FIX: For 'browse_record' objects, able to get/set a value to a many2one field which the model relation has no 'name' field (Bug #1012593) - FIX: Able to set the 'False' value to a many2one field for 'browse_record' objects (Bug #1012597) - FIX: Able to set the 'False' value to a selection field for 'browse_record' objects (Bug #1014252) 0.5.2 ===== - FIX: Able to get the 'False' value from a field of type 'date' or 'datetime' for a browse record 0.5.1 ===== - FIX: Able to assign the 'False' value to a field of type 'char' for a browse record 0.5.0 ===== - Access to all methods proposed by an OSV class (even ``browse``) with an API similar to that can be found in OpenERP server - Access to several browse records improved (no need to wait the instanciation of all records to iterate on them) - Documentation updated 0.4.0 ===== - Project migrated from Bitbucket to Launchpad - Net-RPC protocol support added - Database management (via the 'OERP.db' attribute) - Browse records are no longer stored in OERPLib, each call to the 'browse', method will generate a new instance - Methods which need a user connected raise an exception if it is not the case - Browse records now store their own original data and fields updated in the '__data__' attribute - Browse record classes now store their metadata (OSV class name and columns) in the '__osv__' attribute - Dictionary interface of the 'OERP' class dropped - 'write' and 'unlink' methods don't handle browse records anymore, 'write_record' and 'unlink_record' added for this purpose - Unit tests added - A new design for the documentation - FIX: 'name' attribute of a browse record fixed (does not rely on the 'name_get' OSV method anymore) - FIX: 'OERP.report' method (previously called 'OERP.exec_report') works well - FIX: 'None' values can now be sent via the XML-RPC protocol 0.3.0 ===== - ID field of browsable objects is readonly - Unable to perform refresh/reset/write and unlink operations on locally deprecated browsable objects - String representation of browsable objects is of the form "browse_record('sale.order', 42)" (like OpenERP Server) - Implicit management of the 'name_get' method for browsable objects - 'join' parameter of the 'OERP.browse' method has been deleted - 'refresh' option of the 'OERP.browse' method is set to True by default - Update operation on One2Many field is no longer planned (setter property deleted) 0.2.0 ===== - Updated tutorials in the documentation - FIX: fix some exceptions raised then update data through browsable objects 0.1.2 ===== - FIX: fix setup.py 0.1.1 ===== - Update documentation and README.txt - FIX: Fix setup.py script about Sphinx and download URL 0.1.0 ===== - Initial release python-oerplib-0.8.4/LICENSE.txt000066400000000000000000000167431245703354300163420ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser 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 Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. python-oerplib-0.8.4/MANIFEST.in000066400000000000000000000003441245703354300162430ustar00rootroot00000000000000include AUTHORS.txt include CHANGELOG.rst include LICENSE.txt include README.rst include doc/Makefile doc/make.bat recursive-include doc/source * recursive-include examples * recursive-include tests *.py prune tests/__pycache__ python-oerplib-0.8.4/README.rst000066400000000000000000000061501245703354300161750ustar00rootroot00000000000000 ======= OERPLib ======= .. image:: https://pypip.in/download/OERPLib/badge.svg :target: https://pypi.python.org/pypi/OERPLib/ :alt: Downloads .. image:: https://pypip.in/version/OERPLib/badge.svg :target: https://pypi.python.org/pypi/OERPLib/ :alt: Latest Version .. image:: https://pypip.in/license/OERPLib/badge.svg :target: https://pypi.python.org/pypi/OERPLib/ :alt: License **OERPLib** is a Python module providing an easy way to pilot your **OpenERP** and **Odoo** servers through `RPC`. Features supported: - `XML-RPC` and (legacy) `Net-RPC` protocols, - access to all methods proposed by a model class (even ``browse``) with an API similar to the server-side API, - ability to use named parameters with such methods (server >= `6.1`), - user context automatically sent (server >= `6.1`) providing support for internationalization, - browse records, - execute workflows, - manage databases, - reports downloading, - inspection capabilities (graphical output of relations between models and dependencies between modules, list ``on_change`` methods from model views, ...). How does it work? See below: .. code-block:: python import oerplib # Prepare the connection to the server oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) # Check available databases print(oerp.db.list()) # Login (the object returned is a browsable record) user = oerp.login('user', 'passwd', 'db_name') print(user.name) # name of the user connected print(user.company_id.name) # the name of its company # Simple 'raw' query user_data = oerp.execute('res.users', 'read', [user.id]) print(user_data) # Use all methods of an OSV class order_obj = oerp.get('sale.order') order_ids = order_obj.search([]) for order in order_obj.browse(order_ids): print(order.name) products = [line.product_id.name for line in order.order_line] print(products) # Update data through a browsable record user.name = "Brian Jones" oerp.write_record(user) See the documentation for more details and features. Supported OpenERP/Odoo server versions -------------------------------------- `OERPLib` has been tested on `OpenERP` server v5.0, v6.0, v6.1, v7.0 and Odoo v8.0. It should work on next versions if `Odoo` keeps a stable API. Supported Python versions ------------------------- `OERPLib` support Python versions 2.6, 2.7. License ------- This software is made available under the `LGPL v3` license. Generate the documentation -------------------------- To generate the documentation, you have to install `Sphinx` documentation generator:: easy_install -U sphinx Then, you can use the ``build_doc`` option of the ``setup.py``:: python setup.py build_doc The generated documentation will be in the ``./doc/build/html`` directory. Bugs or suggestions ------------------- Please, feel free to report bugs or suggestions in the `Bug Tracker `_! Changes in this version ----------------------- Consult the ``CHANGELOG.rst`` file. python-oerplib-0.8.4/RPC_SERVICES.txt000066400000000000000000000076741245703354300172120ustar00rootroot00000000000000 | | 5.0 | 6.0 | 6.1 | 7.0 | 8.0 | ========|=========================|=======|=======|=======|=======|=======| db | | | | | | | | create | X | X | X | X | X | | get_progress | X | X | X | X | X | | drop | X | X | X | X | X | | dump | X | X | X | X | X | | restore | X | X | X | X | X | | rename | X | X | X | X | X | | db_exist | X | X | X | X | X | | list | X | X | X | X | X | | list_lang | X | X | X | X | X | | server_version | X | X | X | X | X | | change_admin_password | X | X | X | X | X | | migrate_databases | X | X | X | X | X | | create_database | - | - | X | X | X | | duplicate_database | - | - | - | X | X | ========|=========================|=======|=======|=======|=======|=======| common | | | | | | | | ir_set | X | X | - | - | - | | ir_del | X | X | - | - | - | | ir_get | X | X | - | - | - | | login | X | X | X | X | X | | logout | X | X | - | - | - | | about | X | X | X | X | X | | timezone_get | X | X | X | X | X | | get_server_environment | X | X | X | X | - | | login_message | X | X | X | X | - | | get_stats | - | X | X | X | - | | check_connectivity | X | X | X | X | - | | list_http_services | - | X | X | X | - | | version | - | - | X | X | X | | authenticate | - | - | X | X | X | | | | | | | | | get_available_updates | X | X | X | X | - | | get_migration_scripts | X | X | X | X | - | | set_loglevel | - | X | X | X | X | | get_os_time | - | X | X | X | - | | get_sqlcount | - | X | X | X | - | ========|=========================|=======|=======|=======|=======|=======| report | | | | | | | | report | X | X | X | X | X | | report_get | X | X | X | X | X | | render_report | - | - | X | X | X | ========|=========================|=======|=======|=======|=======|=======| wizard | | | | | | | | execute | X | X | X | - | - | | create | X | X | X | - | - | ========|=========================|=======|=======|=======|=======|=======| object | | | | | | | | execute | X | X | X | X | X | | execute_kw | - | - | X | X | X | | exec_workflow | X | X | X | X | X | python-oerplib-0.8.4/bin/000077500000000000000000000000001245703354300152545ustar00rootroot00000000000000python-oerplib-0.8.4/bin/oerp000077500000000000000000000035521245703354300161540ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- import code import argparse import oerplib parser = argparse.ArgumentParser() # Positional arguments parser.add_argument("action", nargs='?', default='shell') # Optional arguments parser.add_argument( "-n", "--session", help="pre-configured session name") parser.add_argument( "-f", "--file", default='~/.oerplibrc', help="configuration file to use") parser.add_argument( "-s", "--server", help="server hostname") parser.add_argument( "-u", "--user", help="user login") parser.add_argument( "-w", "--password", help="user password") parser.add_argument( "-d", "--database", help="database name") args = parser.parse_args() def action_shell(oerp): """Open an interactive Python shell.""" title = '[{user}@{server}:{database}]'.format( user=oerp.user.login, server=oerp.server, database=oerp.database) local = {'oerplib': oerplib, 'oerp': oerp} code.interact(title, local=local) actions = { 'shell': action_shell, } def main(): if args.session: if args.session not in oerplib.list(args.file): print("Error: no '{0}' configuration in '{1}' file".format( args.session, args.file)) return oerp = oerplib.load(args.session, args.file) elif args.user and args.server and args.password and args.database: oerp = oerplib.OERP(args.server) oerp.login(args.user, args.password, args.database) else: print("Error: please supply a session name (-n option) or " "sufficient connection information (-s, -u, -w and -d).") return if args.action in actions: actions[args.action](oerp) else: print("Unknown action: '{0}'".format(args.action)) if __name__ == '__main__': main() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/doc/000077500000000000000000000000001245703354300152515ustar00rootroot00000000000000python-oerplib-0.8.4/doc/Makefile000066400000000000000000000107661245703354300167230ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OERPLib.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OERPLib.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/OERPLib" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OERPLib" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-oerplib-0.8.4/doc/make.bat000066400000000000000000000100141245703354300166520ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OERPLib.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OERPLib.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end python-oerplib-0.8.4/doc/source/000077500000000000000000000000001245703354300165515ustar00rootroot00000000000000python-oerplib-0.8.4/doc/source/_static/000077500000000000000000000000001245703354300201775ustar00rootroot00000000000000python-oerplib-0.8.4/doc/source/_static/dependencies_v1.png000066400000000000000000000635071245703354300237540ustar00rootroot00000000000000PNG  IHDR+KbKGD IDATxw\Tw3޻X+آ)jhLrMƘn1G5xsc&]5lEA,4J" e(~&0cg9s3p2WjH][$/$/$/FR0lڴ)##C*֬Y3a„~xFFƦM-xA//u  s222N<)uQ  U~W X5~&edJ]zBlYFϺPCy( d`-EE_ 8H`zZ]FfYBUIhA%s wqiz]E[@8sRO ƇJ]h W$/$/@|fSCV~|vn Bc^Af`d%c[c[K=ۻς',}>Tnhn(,W?fS׃Yz'\O9ï9jlE.S#]M-Yg/mRu0U*32|'}l:Nd^cro;^]Zyp&w="@k-)o-l7"! 3Z n3&Q݈`c[Ҋ2Yן< V>x(`DY~O<H\,#GNoïxLܦOvt{ 0_nswW'x>1gO0Q}mn{36!CEMj NjwfLnȽp6}dq g|y\Qy=m:˿={L"e[9<ֲJ!oïھ~&.[=òVGA8naߚx%8.G ]2ww8ow;< 9XL/KԸGughn$&p;muO n(IPV=A;G_z>6|Eѣ9^VEY NcwQ}eҡ#3CWvw8o1t!^\&eS2,]&:pTT`4 D L&L&,}<IݲXzKBOݯ{#"] 'Sucx{?w>1$ЋRE5 O@?STڴSLRϜRTfZVR\&/cC_ظGKGo5lA&}(TwYZk)PwcÜ#8 5}~/镜`A_y۹L1!C }IY3Ԥfh:Jo^\ ,͋G;ƙ;yă ݯG7-z-ժTu'ԝ8#Å'gLCvZA&iUz۝ɷ;]}`/>>n \ ,_ 0_uߐe8? FVS"}A*~NF?WZ^)BLLlbncY`wדsA003Cvkoq/{3vǼCb ,Ek:5RGŕb&6} ]Ϸa_Z2n;>|**6|ͥCR_uI+_7(NqbϮJp?'s-#wm֭%sL1 }iZAQ~qͼ^YQRXu̗S̼$EuRnky gBcpu:nZ{Lz^z~>ym}"MGgKQw>ܷgUwغKcz90(W=b , c`zӄRM?"8^O#/Gan^H8)uSH`z5+aU$>;4atT<]OMT8={aVBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXnܹHB.FRݻ?qҤI'N5jԥ ,@7| 7nسg?VMLLF1}􈈈H`RAժjA:;;333sssU*L&ˁW G >>^^^>>>l]]; H`@o)FUcZbbb,uUT*KKKKKKӍjRA&XZZ---%@O = İu~?G"v<(,,eJRT]tvvzzzįA EՊXoת/&&Fzyy&SL)++Ǻ*H kkk ޟ7w-,,Zq_pp9staL&ƍ'NHKKKMMUտ/\p޼y=\!ij+++ŰmSS C ?.lns1uMOOj~~~rի)6}7l IE neeeboS;;;\.͛'>0B\O:lnn9o޼qY[[kZ[[fݣ }}}⋉'JX<iA7o^zŋyyy4ldbڵak [uʎ=veJ}ݺucƌqssF ,F))) kA022 &LxgBBB<<<.j.]:vSSS+++Gp‰'FFF:88H]#h4҂K._tV ӧ:Tqqqɓ'[ZZĦZTTȑ#<0&455;w.//O7U&0((Hܺdq,{{{SRRrrr I>#G @gf#F 611NT9sѣG=qDkk'MN8Q.d2k_A o5ixbaaaGGL& Zh2ީTL1uMOOoii2e͛'N+upw$F5l+((P(>>>AAAf :thpp1iii))))))gϞUգF\dɓ]\\. ,w˼5??800P.?YR'i}]7ޘ4i# t[ɲ,,,rs='CBB<<<.oh4gϞ=z#G?1u~ʔ)C @$vyyHHHHHCBB[F; dddMrm6qD777 G (nZżU.{yyOd)))gUUUSLٰaôiӂ?/F)..p-[A y'BBB lii)uMQQ8رcՎӦM[n]LL@!ZŋYYYbzRihh$~z+$$$00Xz}jUUUM>}2X@yҥϟ?ܹsϟvذa&MZzÇ fgg'uYkk999cƌ~7mڴsss ZߚUUU%EXX\.5kVxx\.b\CCI&'uH`QUUUff.ozjggСCrżPbCO111Ztt@/E Byyy 1"&&7ސ偁R;TTT$'' 4hΜ9[n8q@@ iҥK]ֲ2Jeff&CBB8 6(**5k{DŽZH`zh\#0bĈ9s >|ĈrJb(R*gϞh4aaaqqq111vF ~.\Νkmm500 ]fȑ#>h +:t\v%::_>}X@7(**[ Aptt0a¤I^x ##ݑZ>śCCW^yeƌ#GdR  Z/^lll/$$dtqU.]?8qmԨQ111~I@ pw ";;[^z0(((<<<...<<|ذavvvRW9cbbl2sLWWW+O>-yyyj",,,&&7011RJfgg$&&:uJL0a111R ,@6;;;''G>իjf 6lԕ׵9rd߾}r劝nE TUUUZZZZZL& _xqHH\Gvtt5j3gΤ+mEsssvvvϮ^*ѣ_zѣG=ȵOuw=p@ii{llw}7m4KKK=),,500zG V]]]FFn*A|}}V^Y}N^^^bbbJJJZZZGGGDDʕ+cbbSNmذAX?o\]]]nj|p 9LJJJII)**9sg}6k,]hK.9rA&iZGG?RW@P(iD}ZYYݻN8P(BBBΝKwޯرciiiǏhkk311Q*ZVCCC##~Kڲ(((PD}VNLLLNNʒdƍχz(44Tp;w<|pjjjSSFSNnfܸqU @q]zeJeaanݺH">~*++g͚+̚5Qpoߞ'j=^)@S[[{SNedddffBBBƍjժѣG9L2}O:VGtҸ8\.uix&&&v WT?oΛ7kJJJv-``")\1u=ydaa Ǐwnjjii)u4͉'LMMO駟Μ9[[1⭷_^^^_~L&6Hʕ+ ,Ygtti =$jt\]\\&OK/9Z2=ZZZۗZ77~>:t?_uYY-!ݻz`ڴiӖ-[Z[[jggYfM>}ڴi @ ,7n8q"+++==MMMfffᱱ|3h? IIImmm!!!V 544:tחi4[6m޼yTiٳg hZ1믿kVkaabff6{l''' @w"}Q*_ IDATNgJOO/**d~addd``eٳ')))--MV=WKOK.mllܱcǹsYт /^,uFttG}$--Bp… k{H ,tcǎΞ=p1cƸI]#5>>>11޵kWttA_}۷?[lqwwomm着 }}}?kd;f``p=88X {RR" &,]tܸq^ɪ3H`4ŋOիÆ ?~/8nܸC{555{ILLLII興xwbcc. =mϞ=+WTn-6mt %HF.WVV:뿺?o޼2`Otdy]577:uJ\O#ddd ̩6oޜ[o Y"""j߿h4SSI&[SNm8qDϕ^Fw<΃o޼yR=M\Ӌt]"##=<<=ҥK}]|||^^>իWoݺuΜ9Rӗ;99.\𫯾uttxyyt]iaaahhhii蘟V(_f"@F `@kll<~:u<22RL]Ǎgmm-uQ YYYֳg~fϞmcc#uiRmm/lٲ 6I]Q?|VkffVWW5#FԭYjkbŊC-^888xǎ.]j3fxG-ax`$cǎeffڊƎ,˗5''{ǦMfjj*ui޶m|Mkk>l֬YR'=?J200صkHz|͂ j ׮]sww!>>~ʕ7oܰa_-XpҥK "q$ׯ?~ΝS'NrZmzzz||?PRR`ذ0***VXիW{ ~0...JR##Yf%&&J]Tvg}vҤIW^խ^zuBBB\\͛Μ9׿ummm ٳ(wG 8~M]uh5.xݳgOYYY``O?M_UVV|򔔔W_}w177-:::55U^CC>lɒ%V'466۷o3g֭[ $h4'Nغu={Z[[A={EfΜibb"i ,MReff;vL^5**j!:9E||ݻ˃z)W܎F׮]_DDDH]QW]]+aTTѣG+9p+n޼aÆe˖d2&Zy{* A̙3o޼3fXYYIW5?X}Ogg3g=zر'NM]5$$_T*վ}_[[;㏟#Gğ ͛7w\ H` ; ={VфDFF7E[?Ƿ~[\\O>cƌ!h4?pAAA;v cر|422ڸq+"UU}ԉ'.]ZZZvW_}$%%_JNNnmm522RTs΍G$H`^S\VK]Ǐ,uE ŋꩧå }@~~%K233ׯ_fccc+?iS&eeeIRU=z|=)w]|@X[[Ϟ='>}}OV0]ZZZӏ;z̙ΐI&M>^LZL&5j?>{#Gr^^CCCϊLLLƌ3~x&/]vmΝ_}U^^"""C:yyy/}w?I]Q?4lذ/n_7|'OT*G}nݺ۷;^U__o߾={߿XTZ[[O6;w+H`HɓG9rȩS:::M6mڴI&999I] zVVVO=… 'L\vMF_0hZO̙~MWWW_~4Ν[lYNNk?龦xbRRݻT*L&dC?~llȑ#N 9GMOOOIIh4F7njnn޽{Ν;9bffM>/^\XXiӦK1~|vZvΝk֬޲eˬY)))III7nܐdZjĉbX///}T0pЯ4ٳgU*N絸СC{=zBХF?#<2hР` Z>O??~͛'O H] z3gܹo3fO?=oYlmmW$~1%%ׯ_W_%&&kƚI]~iҥMMM[l D WXqȑKnܸZϥVO8;vHP lmm"##g͚$@P($ T*xAj]`fffnn~M8z ,;>xSRR_4mڴhCSXX}o422rѢEO<񄣣u_inn~Wo駮RW#Vݹs5kl2k֬yɩ*((("""444888((gJЕBy͛7Z[[;::;::Z[[:::[ZZ:::n޼) =_ZXXZ[[ىCCÞ/Xё~={lҤI111#G qܯ7nkeee.Zh޼y!!!Rׅ~СC˖-S(~)C_{XiUWWk;wOzx V-((HNN>p@nnnmmVmHH\. :tMrƍƛu{@N]]L?eCCCŷ&1niiQ*چZ*ur4kkk1urrrrrrvvvB\baaq/?sw$A4MNNNbbbRRٳgA2ed 44ZѣG_JzG,X0sL>׿ ˗/" ~UV555mذaٲeRMw̌zLfaaVA066$In޼Y[[{ƍ7nnH@dmmmkkkcccۅnM]S~C666644444nN~]#ixxx4N"1%%ѣgώ6m׆さ_~eqqɓn]GCkݫV222ںu9s.hhhxvC}'>>>RW$TTT>}ӧNlii144ttt677Չs|Y[[y{{4MD[WWW0F_RjjjjkknRtv)trrrlmmp~555ܸqڵkׯ_~kת+++kjj:;;=-,,<<<>>FFFuuub&*>MH9;;;;;+Ii4MMM.oMMMo.讎wttd[ VWWWVVVTTTTT\vQWw+bt X`޷o_rrrJJJCCߌ3f̘DZ_}޽{53<|pBzjSSm۶͜9Srp$JO?mٲ%&&F~]EE.-**uÆ 2dM}}}uuk!le555tk;;;񮉉8]o%a7jkk%fQԨjqOsssJvggg nnn\w)b񶪪JA?dȐ6 B gSN={3}^uu_~??/_^pc=&^4 յkVXz{ީ_޽{ >#ggg+7o^x… .\xsd2ryPPPPPP@@8LTix+NFA\P**Js|m2v)&qf;'GFlikkkbbbmm};@tUSSakךt{8[l$:h (j @%?+**z+W^*`9 ,UTT$'''&&9rDP5jܹ̩nT*ٳm۶#G899= Ŷm|Mgg/"**JrpH`{_|>pS]]}A46000000 wd=Qw旓-ċ/'c.宽%qrr[g])ꪪZnLxW7AiDUY]]]ŕ&&&Ҿ \^rEEE2;$$dذa!!!b&kii)u ,O(Ԕļ<3gΝ;w̙RW~ܹs_|ƍG}tSN%GXbŁ^}wyޓ$\kk{キqƈ[H]уtRAAAAAPRR"5h _pppdP(ԊIxۤP(XT(ƟfzXCCCx}*looXŤU X---&Vq/J*..xb~~~^^ޥK[ZZ1r|ذa#F6lСCE$@VUUrqN[DXss?qÇ/YdnnnRׅDn߾_www߱cDŽ OYbEnn5k֭[o>Kttt\| AWMf.珪g%-߸qCx8A8M555:;;kjj&bq}AVVVbg1WuqqDZ:;;4ٹߜJKKus.\jbb2tÇ>\d===ݑ}RAAAbbbrrrZZV0aC=C1BЯ;wn۶m|M{{c=xiӦз˗)hH` q?O...7o1cKgggII.-***///++%VVVb8h 1Xg_[T*;+**[qx1A&{{{wdVR߸q^\J1ioiiKT5i iիgϞ=wٳgA1bĨQyz!XPTǏOJJJLLr劳N>Iߨ222Flٲgy =O|k׮߱cGhh-׮]{W>+9b [VVVRRrJB!cdd$^.ι,Ng3ļXeee͂ d2լFikkkllg/ǮvMZi^LcssssrrT*uhhhi$@oWYY1a„عsrKC?=>>h/BppEarʒ%KN:~5k#W^Դnݺ^zHF)^. ``` vvvݲRMMMbc٦͛b*: FFFXR)СCCCCǏ?di^'Gttt?>;;;''';;mmmVVVq% IDATG;vq.H`Hզ]򬭭g͚5wٳg;;;K]]v^CCCW\O[[[K](Zqw}W.رcRWn@G)oܸq„ [n ;h4tOuuu bծ |-n\Q\ommmddt"ٷeq-ݦݲ8%WCC8I666*f 7o%η۟_5Q]]/JJJT*È#d+p**???'''''YYYb;vرcrcH`^e߾}|bccV e˖oVR-X`R-??ɒ%YYY;[H`gϮX";;{ʕ_~#61߅ҍ}VVV]KKK{{{q1絵566177733166c+++1]ǧ .YSSȑ#:n8wwnjP*999'Ozh.)nGR={6------==(44422RoG (Fsĉ"77yFFF ^ 믿 =uBggڵk7m3ADE۟OVӟDBeeegΜ9}3glll"#####'N8f>xwpt1j'N:uԩS===.H`q:t;>~*(((qW545:nٞh4Immu5פMML&y bLwEA VT"-& 1Ʉ 093~KϜ;^̙3Ǐ:/H>-..>}?L?~Ŋnz뭷bbba X S[[onܸ100p۶mh_SSӵkΜ9sĉSNݸq~ҤIZO:5UZZIcSRR222"""4iAL] `6H` }_~e||ѣGkkkjutt4Wxg9swݳgO^{5k֌9Eߩ{WO>>:t+H`-͛7_|ńzjӦMУXU'O^t)((hڴiSN1u|֞?ԩSIII'O6mZddddd$'n$ݻ7>>>==]TΞ=[V?c+ =Ƕm۲U*Ն q{{{Sٳ+V(**z~i.}$l߾}֭_0#{_:u*99\sȨ(oooSWEEʼn';vر栠YfEDD+^@[$ݻ?OMM|ǣ ^crss~;v,]t'N4uQߺun;XXv޽?~XXضmۂM]9ONHHHLLȰJ'ـ)))ǎKJJζ 5k֬Y&NT*M] $@wݻwϞ=鮮_p9sM]Esssrr͛< /Y` 2r{mݺUV(Xkp…kfdd]_(Ą|ggpM;zht())9~xRRRBBBnnn^4g„ X3XRSS߿o߾W8PVP>COٹsm.^8v 6,YuUUUW]p-[<==M]z hjjڹs yE"[߯MA~f̘1}5uu|577gff&'''%%TWW̞=;***""=SN۷֭[ .Taaa666%//oӦM;v쨮^l7%''TWWoٲK_ U)..~we˖!C"4)8x^4sh U__t;88DDD̛7o޼y~ ,6x}퀀˗jqHJJp_\zAL]о_ױ1117ntss3uE0X+nݺ7 w҄())9tݻ7|GGGٙ:@֪=zzzΞ=;::zΜ9{6uuH`]222v߼y'XpԩS=qϞ=7oNKK x{9S g Wnll͛gr`b$֩a֭|||3g"8pFEEEGGϞ=ô:tҥKΑs΍X%iii6m2a5@ ݰaC999vڵk׵k׆ p… q8vv믿5ksv1_Lܹs'55gܸq:ML::::88̙3_;$} ~Լ^{MSBy悂ǏO6Ark177'|Lat|򉩫\uSOۛ"0 8z@&|||֯__"000?k`N\m6St ,B0u wz0h``VV NrXieKjk׿Sd!k"?\P(6~WVZy*ڴl+ʫ22X[[߲q{{%KC&Lfggs7qk뒥!ϟ;LZݰ?3<<00pP> yy 3 {ҐA ٙ=+l.ROr_B^4_XQ5zJg!D{go&%f566ihcc3}ƨ0/6 ŭ[% G2/]yTǸy4|dh{zsot;% ^w|wYQ_fJDǩNۖR z!닅{c-`JزҪ~\||o !Gh `oMWuwr/+tDB 6@9=o쫭mn_>aP͟Ǐ2tEuu\\^]6<"еwvvv 5{I/ !\\>}z}`xk Z1aP[ᝮ ?an2UiieqÆ<:/jlqѷii7u =UUW;e#l:ؤTڼ~vpv#FY]{٩?}0q脉CcVn5`*ƥu?v{}s4gp, }KBT-P!S(z{M+v]z;3̚ f+AA/,/I!ˊ"*0S2 :N=VRutrC!b}02>0` zvqh@+{)9r8S).i#CB,ڵ.S ^Ը|my@^nС+V=1-whלƸ9U}=E\OYskoI-YُӧOҪ?N-2ߊ3|w7'\.k0e-y'N>jҒʖ[$عs47cBG+M3eg}+]1v`7p$3bƨ}}[˖,U !}5;Xkw ^._ik-i-k kk(pros@oofGooգBT~BO^|PY 5t!9UB󵸊srnݒ$ weUB?9sƄ;;;\tMu? if]'/_][P^^٧B֍ɭWޯ,P*m&M2ypJfAee;?mH=}}.Kbv Q=nƝo˖0x1dYQ_KnDǩ0vTܐeX_,܆!;:X5O Fx !FUfe*xl#f])<}}܄# Y=H)kEqE7nik?z!-kݾݑ5nqqu`V=ӳ/]-+/35&&"8[M+W >~~!3ƥurJ!aFSo;XP?!ĩnA``=}}uDbC׺>LSc{on7OEWם4|m80vV\2dxU/GnG3n֛TW;{z=ڧB({۩RB(66mJ .%=^)ǯ%&\)/jhhtpݲ.4US}Ç?hnՖ֠U.W~?lxÆ>˖>𘎇UL|.Ϻz544j!Z$v˗oWU U(Ç˼Ά|WIȄ FSSW_UhW[n7|β3id3C`zX!"UϠ`E6 !gBӧo*x_*l@&#Gz ! 9I(-طs;2`=_i.pԠ5XZ? x-Ða:ܹ?xBo5o!'Odz!㮩)杜wqqxA^Ӹn UB<|tlΌAu !ΞTՖt=;{&hn ijm]`605>ؚӗ?T*ۍ{QS@KVDǩGg;wb(Y9Y ;kn.>+CT*l*4mrp!_p|ŗfM{td?;{ӳϴGG.{2tH/#.]Mo܎W !;١W/I=OZ˖Fx98ZA[le0Gǰ!ΜBh~m3U>`݋€qoTnn666' AC{`Za0So!BT~*өWV1ikMSчǛ,U͙vq;*s hj8l5T.C %+'|Xݵc-ͅ_[, (}W!([}N^<ߌAz;߼kEECs_~.fd亻tkSE5V%0g=<\_w ^-u'%f0«$fg%'e !&_}dQ?Жw#4i)=`1d5۵`ad5J +݄~%{M+vLY65 fuvKz;X6Ln*DOQ:xIh"9SvZKA2Pr>zw]=ұ\Xu{j77ꢢ 7j6VTTܽrOw]۱~֕´7ZǗ. 0qR¸TW!KUU_7XL8jÇs?4}¶ퟘpE4G9WTԤq`=~_ܿd*0p`CCsƍR]]-u?|ظ#g<=fq39IW4ߡhll`Ugܒ#W4O=i 5͹Nӧo,uV,VکOBqmROgcw^`B>M߾}{i益we8-B*&≅J.h"9S.tZKA2Pr>zw]=ұ\([vK,Y&,ҙ1#hٓEokK EiD3j:5Va}0Oc|}(?Nq]!1#κ:yzV3g=r)#V CO/xnkIDATǿ2f~1 ‹#5ё?{b"\Ĭ RqV}|#]4nqㇴܒWz5)^ ,!8.,I^:EFGt`]H`-ٻf uwvv(;7#0Tt l1ɧm3/R1!_YǓ @|pXJ;N4ztc5A7TU>}l||0À q=`vHO7ŲkGe7m7>v|tA޽45dtHa@͚=zQdÈ+ݰ6z޽FBR!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ X ,HB R!TH`@*$ XLs qqq.Q-jF a@90-ZdwH+`J> V]׶caaavF  < X 6jSWH+ ) D,HB R!gtHEIENDB`python-oerplib-0.8.4/doc/source/_static/dependencies_v2.png000066400000000000000000001105011245703354300237400ustar00rootroot00000000000000PNG  IHDR`bKGD IDATxwXBl(bbǘc$jvc=Fc RU@APAWzSAz1'{,heܿkvع}߇& :,$/@\ cQ]ȦKR]tØwɲ2YsRz "͇dI H^dBaQI]Wa@** QH^ `ܝ2/6ރ ;*/_@c2-5xƧVf4:ŭw^z]QjɂAS4,L h(xRtԣͻ\.#4żr'ϣL}U_Λ;Sncw|كƘgE5uxxgILvsr|²r?Pwpvo3Ne.axI4MW[- 0Í;-#Y6߷F3>%"k>L<΍45 %?p'7=w\l4`TaܥUWmh6@!y0h e%JK陋m-L (βs\g7EJ PPOd v?,%hRRokgNlz-4AB!yCE;W:/ MpxCã'AO{kX =w #* ڪ@C.B D@ y\ZNן+jk*jj:GӃl 7h٪uJ}G8:Wכ&ij0J}MfMrAEGN\_@)gg/0 ͐Gb $hGG R]xY/ay6#nDWP}+aȭ]mRRr1,I|' :A}pO%GMVvfY^)^<UǏ8q"ՅH7ypӮi,;89^>wi\ni2'knNJ>~;6w\3_Mֶ9wW,]p5}Β\nKiEοߊX/! wh..p1/RONƼ@Øn1/@\ qA .L `ndOu [NY@>qG_^4$/ y$/@\ qA .H^ y$/@\ qA .H^ y$/@\ qA .H^ y$/@\ qA .H^ y$/@\ qA .H^ y$/@\ X\.$Ϟ=YxW_}Eu9Iu 5_tb h4A3f̠4 J(秤\t)))Nh4>/ځ`R['B {Mu JJJ?J:N@ " ebbx{m|ՕRQQ9s… ;/0Ln #--*==F[uttuuuH7`rB |-[tttm`0`x##d+cBa\\~5r_~:::*+++:)//ohh wc2 E3JJJ~1/w!F | b+iӦG2 2颱ь(wE#4A>.+t9@PSkBF ׭[`0.GGCXbGPoZYYp*** >zzz&&&&&&R2 ###,  @OlETWW;[[[ //W\ ֯_1*޸qm***d<833LtB)**8a2FFFFFFm @7khhx=a)**"Nhjj+֦*zEF Rz\u&#Q\\L.V4pWɘ!qښDrMMMSSS{{`N___QQ=_YYYKKF}~j]rrrzWSSSQQQQQӧO233?Nmdd4h OCC_$/A^y7v[[A}155555uuu%oh|^###sHz{~.[ZZ*prssϞ=12]@ (**zےL&& SsA > o\MF4FF$++H4FFKK#C~9H)򫦦I'A0LCCCr膊 U555?}c|˗A0 }}}Q#Mu@yŋ‚ǏPRR"X,Vxx8O3W^޹Éyass3Ad"әe^xУL vByyy eeed_!uuu [[[GKK@볖^K%$$p8rk?~(H^@r=IXX d^KUUUBZ͛UUUZ,H/$/ ?~#rǏ *++ fffF;w1ֹɤb:" bG)**x䗈R +++cccL H^@<lLYY>^ՉN%&&~jiiXYY1 *B= vY8N]]A&̔.@(**yJ``NRؔ{455E]Dmz @PTT9a#?UQQ111ڷBoXXyOGGhy,rfRRҳgPPP077%ښcD! yn@,>у>| caaaiiIbaaeYMQQƦuuud w"@`aa! blmm̐t $/=|LX}W*={***jjjL&S]]Nݛ r&é'7WRRBQˋ]fM[[Aq1666jjj @W^4D Wf#"===l6 Ai4F#7 BPh, ;rKKKEYLJJJTTF Ts’ɓvaiiI.ȂYzP(8p 2N555kjj?>qĞ FqWﷷe1T Y0@~Dt:= `߾}=YW466>xgΜٲe AJJJVVV 2p Bd@@WJJJ'N|>m?~&##|>_WWٙ b  >m^:O"1K:88zyy}F477]rmŊAAA 4n6a„˗/7㽾Nzh1 yrsrr333>w BѼ$www x{ɩ B[[{С&M2d &ˏӧOD.;z1$$###˫㕭a^H?SSSO>tttX,ruuurraHm_G4%??㩨8::E!p6}}}KuiF WEEE^^^\.WOh";;;<ѣG))))))dZXXaFeddDu }jkkshP^^˗/ %?#KKK&CDԳgN>{P6nܸ>}P]ګ8~\ݽ{Wh삂͛7:99EDDc0 Ȇ|Phmm>rѣGc^xOH^@^,yyy<OIIΎςUZT]]}16d2nj(7W!!!|>`1"99ܔݻwYf_D<% iiiiii7oLOOoii1665jǨQLMM.$M<{  >\4{ Aĉdࢬ奪JuimU{{iΞ=+:4eʔ[VWWEDDx{{`=?|0%%%!!ڵkϟ?pvvfX...xw!y>|}z  4d[tV^^~I6Bui.+>_Ĕ***CGGٳg>ӧKE O7n\~=99#G$ 2 H+rT dee䴶2LkkkGGGC2Dg={sɤ$EEE??Ѐ޽{S]ڇ@ݻCA)HTRR _d (py87U-$/ 5jjj2222VYYID>}\]]E .tٳgNbIIIJJJƍO{Eui C7W555׶m===/^ ^KOOOOOO//R]$/ :yE-B-NӣL?>,,GIIҺn?|lْ`aaϝ;WsOpVWW3rHooo???CCCBB(rњ666!-Z`dҫW &X"A|UvvΝ;:d2'O_Z[[wHW^z5##{yy#LB!܆:nee%bggIu e'pA ˗KKK===#""BBB @555ݼyիW\QQQ:6H^Ǐ߹sG,.9\AGGJA*Bw\ƌ#"yqե IDATJ oٲ%11t޼yXPmmmbbbBBŋKJJ7zhooqaQiīΝ;wܹ};w^|`0,,,D9#FBI"ġرcϞ=t:}ԩ_|񅝝]<5- s…&#GM+k466*)) 6L4퇠[TUU?~\$/ W߼ysQQŊ o [ W^tҥK>}Xə߯g0,BAA2AFt\TUU9pA@qy -ZԷoߞ@r^p!66ƍBpȑTgϞwnʲCEP^Νcٗ/_ cƌ 0a>#!yqѣG۷o߻w/Ǜ8qe)@ĔZR [mmmJJJ稅FY[[{{{uuuÇtr եIIB#9UMMMTTΝ;+**ƌ_t^^^^\\\llljjg``ѯ@ y7hii"sC(j+U233Ϟ=[\\laa1eʔ07b"E r׮]gcc -[BuQ(++ĉG-))6mɓ K6!yzd"2mTTT>|8لĄA9rf#pv'N=zk׮r--;v .D 9s&Rw/$/G(狢׹峃ep8d2`I&Qu ݮ:t~ի.JvܻwoСAh4kk먨(EuQRF(&&&]#"",X^ioDNDbǎ{kxx)Sթ.M yϟ?OKK#Ӗ܆MMMwww2j6l.5ATUU?~fikkO:5,,6˒~m͚5A8zw/{{\P`0|; K{a0g^d Uimm=rŋCCCgϞvH ***JIIIMMMII>7111b5W[[[\\Ç/\@ɓǏWŋ .,))=t:599d֭[,Y*((+WZ ??@.T ^|333gc!@pܬ,rlKjjjEEGү_?k|~RRÇO:4ziӦc>uS4hhD]?Eۗ$:$#""0 } yi\.7##CԍRIIiذad2|p,&,;;{O.--upp9sfHH˶u駟9r O };vf͚ 2̞M6P755M8믿(VUUu}=|vٳfӧuI4$/=.99ZݻԤF-TЕO8pSbbbHHHkk]H.]DGGG_D~7߬^9WPX" U۷ĉ|>ԩ-";됼K^^h`ˣG|h5z{Ç_iTrlر=z=|-++!!>K.ݶm[GGG;L&>#+V^d޽{^YAAo߾.\*&B0se˖7;NRSS _t)AQ?~52jnii<ϟgٱcǎ ?~<رcӦM4Ch333Kqvvv`(**3̙3#Vd7VTT;vժUnnnT 骪8m۶r};V%|ׯ߼y3;; 0L[ܜINNf111,+,,O?8p u~_|allIP(ttt}6եɾ7\􀀀E.[_~4v?#77{ҥ~~~Z[[۱cǶmvgg篾*$$`P]e7nܼy3)))//O 5jԨQnnnT!:WUUWX믿ϙ3'::zTW'/7mt%KL>0)##c'O4hҥKg͚%K yx3P%jHyooouuuk@O>|pbbjPPдiӼ(BCC+HIIp8'ONHHР<91uԐO?Ttϲe˶o~UWWW 7O>ݵkΝ;BY/_OuQzǎ[lxgϖ 섄۷oWWW+))9bbjgϞr UTT4\|>?000333##Ȩ&.ɕ=@  u֭[kkhh믿6nX]]=iҤ˗<$]cc}6lPSS3iҤ+W0[$/ :::nݺبbY,|}ȑ gg3fLSL {͌/^~zk$''=w~Wkkݻ.\_Ӈ$П~IV!yYVWWG.ڒr=g``0zhr\ N<… &L1co >}#)$߁f͚gϞsR]پ}m'Nj*kkkt V7nիJuE Țk׮%%%ݾ}_~#G5jǐ!Cp '++l6-<<<,,LKK@ZH8:(Ǐ_fͣG###. @П‚ꢺ |>Ν;W^MLLLMMmmm5jԨQȤDGGO<ʊ@u$ZI q۷o{xx,_Ir߿zꈈoVGGꢺV\.7##IjhhKusk׮N2eƌnnnbʕ7oF3#iVGƍ6lYlٴi. @rܹ~kmm"##AD(fee%$$$$$755Vź- Babbbtts皛. dI;ѰaΝ;%ӧO7o޼w^sFFF".nٲ_~ݻ/2c 2@qqKnܸQ]]ݻwo'5>WXXw#G:88̜93,,LOO@\~}̘1hf$RRR,YoQ] A}}׭[ŋ'~7% ЅUV:t?2d}$/ ^xqUrx QRR9r$9ޞdR] 555DGG'%%ihhL81<nnnL&3%%Ȇj֭/_x+@f͚Tov?(((믳gϖϢo޼I-ؖ#FJEEEtt=zdkk;gΜӧߟ@ƉݺukРATC fdd`4ɗe˖SN/Z(""BEE$Qmm?k.77(KKK+z/H^ ;;L[RSS[ZZLLLȴי o>ַoٳgϜ9ښ@^X?zյ@w"[4 ˁw{֭[wޭpŋkkkS]$˛;wnffe~G6zNee+W+**FiccCuuHNN>xǛg̘+}A͌8Nu-VGNNN"-jjjo߾u֎9s,[А$]~ի---?tP+ 7n\rʕ+***Ftpp" jjj|8w;wO˗/؈ t7n޽KFMN&:t@7.{̙k׮L:5""ɉ@N=yŅbC#aÆǏXbذaT YBݻ.]jcc-mڑ@$ӖfCCC???oooOO>}P]rssf"""z"P͌˷lقVG+99yڵqqqNNN/6m>,777<<ɓ'۷o>}: |؄Juuu2mF'ƣGFEEeffΞ={ʔ)Ҳ?043Chu$6o|g}DuQoݸq̙3O555+$/!/\p…7ovtt~'TW@1P񂂂"""<==1$ɧFwwwڻwoˁG@\`%K ?k֬};vޞr .\xW^~~~ T\\_=z`С . Ң.ٽ{wDDɧbggg: UUU;wܼyP(5k֪Uttt. @"M:533sϞ=SL@]qqqX+ gϞzִiñt.H$%K]Zhu$c믵kN8G $ BuymݺUQQz%%%={lƍZ+..޻w}G1gΜI&ihhP]͌O:o,::z̙hu$Kۏ???yD@ѣGϟf555) σDp\GGG__߱cǺ0L </..n/^;w uAg_͛\VGD O,kŊT@ &?А2Ȼ˗/^xٳgd+耀OOOZ@B֭ܿ'Nhjj ҹ |~@@@vvvFFw SSSˁ$N:v;wx{{\ˋT[[YEI H^Tii[[[Ǎ7nܸaÆu---ϧO>p@x/_-[:kjjbXhu$֮]jժh4Fss)SΜ9 y#/_&p8:::Ǐ֦: b H/43A#y~#GFFF.X@YY(?C8q'!y}eee111qqq𖀀@\=MA. Zp!4B3#Zjjgdd$p]vijj.Y/PUU( ]Νۓϋ@fǧ3QF̨. @l۶ \@QTTīdƍwVSS׿EuQ=mʕׯ6mZ=)r_~ܹآ"===1cƠ@^2c ===@ hfVGrf7og^b~فw63ψ@TTT츸.;zhr> եHrǛ1d?"BBBH~xb֭[nmnn9s~Ka]& ,X}9xF$/R,333666...;;[MM߷o_K2l[nE3#xhu$ݻnݺgϞMĎ߿?==]CH˗/Ԙ? \L&H.{̙~͚5kXsd hҥk֬>ԟ IDATdŋ[ZٳgÆ eeeAAA~#E׋/Fq 555=&/iii6mj~\peĈǏy7mڔ#H# *++*++ϟ8@ڥ EѨE^,]\Ws8\???%%%k1zqqС5k<~xܸq]oL]~葫ߑ#GW.--=yd :S񇻻j~۶mϟ?OIIYrc {byDP(3fmϟ_VVvʕ'1v!psqqqqqAcN|XbnPPЊ+TUUqF1OԈZDGݻwٳ,رcǏ G=ljIѶ?jժÇ{xxJ^j3#xOwOf9y_V[ooooo~a/ :z<;8l`MDiIK[ɘ8|*N3._}ΝI&eff8 s>>Cd#~~~***Tb']'T_ "ZIˑN2B W^MNNǏrJC߹\D43`f>+yXI@PCF۽{aï\-/HC__` bʽ8|pHHۖqC.rիW?Q]**^fyzӟVq~CBqt浫:()S%ϟ7vW%.AؖS aEd2F삦&vuu]vU9 c1yd.b痒пn }̹Yuoہnd3ҟmiioiiɤJEDvv8bwIIInf<<4z+r_W.ځNifIў>}vR{%]ob2c]Lu&>#q•<>+q݂ۏeٻg /$tޡu?l`k;7ospyUߟwFii`m*>_0Ìz)/BO̮+{Nw~x|p ̢{?m_]a…ׯ_1cƽ{aۊjjJ66nߖp7 E౰Աԙ?woLƒe~:Mv 6o(zcftwwoz9k6X*-`Wο{\]HM) [>/F#?aAœզf>?;.+AĆ a" 1ƾ$F7dS6}dc'n֬jl"AET@A4dcYJ0|s\wޙw=gǞt!\;X^OM| =;sBXXk?{u?֫ZkԬY!\[ջB+h4']>h_7x{{/YfIR{2srnW75yJ^70LC-S/iHϚ\\=xPqCʩ^^vYbk.,=w6p^va2>땸''<3ѿwffF=BzP(*Ss4U O]v-(5 YYyvL eh͛7u=go<|Xˇ.Z6xb6ISv4Z mAK v9)Nh g\p@c}/L"kBRu^v=l-4=ÁgtO\RRkx~kBK;{CKz]~J Lml)#j{8eڎ6xU;go!֯M~0/o͛}5ri%X:I쎨VkZ';L iFՋow2#z)4/>Z;Ywpt255vN^V%u?֫Zk)>ASGpj7|sΜ9vvv K-9̞3DGzԏ a?'O\Bt֡E;wC;%Bccns^^vT=wH5(yCkfOZIi#yT8gk UTj-uάTkzĥW1,\VV&{))9u\RˡS^ӣwS.]gqWgչyֶcQ{(yMIװB-]488xٲesiH^jѵbAmpF]'ouYtfQx8ۧ=TRZ\\jaaҩzwnTװȑC=TGRrӕiv޾g_߀Uk'f#OZ4VmGORgn \] !T{Q;!4Z=iSR)vFܡÆV5^߁5@93M!D.$Yw+Ú~RPst?j";;O4IV߿[oo={?r}!Ĉ13 &M2ԣCח_ yj)Sp2336330y!u:vlglli;½R!vLJ8't tU:[jPu}N ,C\ ^{Vmml)S듦juLSSOOk֧ ˎkpG4ؓv10yHQV&߿t# FϢC\ܭ--ە߼Qt>Q2|o]r>.'avB\rgOLzqmI 9s_Ps'l0>7nx`˞ȍ:Džs/h)rEFUMGWWڵk׮]pED@ ĉ 4^Gy7)8ZM4xcǾÇӫyſgϮZlTˎ|z5f T^z]lѱF:G_ʑB nvFsq,E rMne~ɓMMMF%/]mlܸ֭l*|Xplhm84/ Quu5[%[ls_??_׺?jgmm=w ܿ"y{K.CH^s/҂ z7 V^]I^{ӦM+++> @=[YYYuY<<< hѢLP?/rlllVVVkϘ1cZ$y]]_~)//e)@ EEE۷oy5z޽AV^]j$/ ޽;??uH^bzzzk 됼4ŰaölR:$/ 4nܸ}?iy $/ ԥKݻw?i>|x\\ܓ4\hh+WΟ?إ$/ `nn.%yh8}}?v) @?99H^."yhR"FNKK{t @c̙34ٳgm'yh,&윟_TTTW^NXBj$/eiiibbrj$/`eeuj$/СCj$/`ffv޽j$/Ю];&Ѯ]Wk$y===BQ@JNF yh*JRWzB711HI^4Io߾Z# @c)Ν;Wk'yh۷o?|GI^ڵkBݻWk'yhׯ !lll4օ :vhnn^Μ9h; @ceee>NX$/M."yhTR"FIMM޽գH^%55H^ѣ$/wŋ//>}^jj*]zwa @M>sgg)S4yx7XBOO ͟?iڴi {8}^/;;{?s: 'XxqiiBT޻wO. !r{FFF+Wl?RR)))?---5q6{:u*JR)P(:::#F vBP̛7/$$dƌM}' [o5yd}}JU'љ2eJlҥNJKKi3΋|U@INN9q;vlJ?U@3r9fk2elذ ڰj<{HOT޾}{OBOO/44lذ1\WCk,kk뢢F5Q* 8Y IDAT(իIIIFq}oookA&MԘ>|ʕ+*hTR\`|P(g̘1SLqss@} 0G ~8խƍ7oܭ[wy{RSj*v &WEWWwɒ%qqqvwO?=~x KF{ 344\bɓPYYg}ǕBk׮H]UIII޽٤ɋ6|(iĤo߾6lЯ_\B-z}QUb``0|pBs]dŋ:v$/Zp׮]B]]5kH]Ho]vB?~d?8333''?7n\ǎ#""VZU\\eZ0CCCrٳ^^^[n*ԩS]t._~gΜ2djjKJСC%) o…GkddԜ/wi .8::>JRh]vEEEܿwر0W\\liiYN]OF0TT*W^+U%;;[l8xs6M-a}}GەJ; B.]fΜiӦC[ǧsΓ&M^$b۷dttt:uo(:::3f4hP=T ! MMmu… &<f͚GVۼyssmjD<~믿~zԨQŋazzz?W-ڵkwڵzKkH.H]\iiO?mooKRwiѣGS\R}v׫W1c 6o34m۶M0W^۵kWQQѯ_߿сE 7od)S|Bk׮1117nڵաi˫W>r䈻$h7N|cook.kinݺ%ɞ~%KH] ٳgGfi#yr$/Њ(ʴ]vEGG'''0@x{{3R: Zr>+u-m״i>M)((߿8`ll,m1$/ZZ$%%F9}#""Fikk+uuzW֭[w骳^lٲgٳgOXXԵTTT7Gi N$/Z@nnn\\\\\jD1cDDD35p۷oddԵ9wޕd#FXbԵ(ٳg_~R#ɋ#ymRQQcǎǏF7no߾}aaa&LۻwoFFԵ{_utttPEˑΎ>x`YYwxxxxxxPP73y磣333;v(u-můSOرcRׂ7\fʹiӤH^ hCٳ'33$88Xxzz2./EEE2,<<^GAAA޽ fkA_'L{͟?_ZEˑ@rEGG_|,000,,,,,WM;vݻG!u-oQQQ:u4)S,_}DH^zt\^UJ_wĤIRRR222ͥEČ9r۶mcǎ4=zڴi˖-ki yz$/򦥥x{{RfGtmI&}RעJJJӯ_7J] I||#Ǝf===y -G{ٳg޽{޽۱c!C:8;;K]h5k̜9s!!!Rע͛iӦΝ;K] CFFƐ!C|||ZI$/ZX "55566v޽.++ٳ#СCv*uz3gΜ8qDZM\\ܰa~嗩SJ] CFFFhh]LMM.H^ V ĉAa>|Ƞ0t_.̙_J]Vw͛ѣÆ ڴiSBH^$U SmP ccc mdɒy>|_Z믿znݺI] *v l᱋ yz$/~]zB=(@+T* vcǎJ]6>>..nW\177P۷o Z .xzzߥ+--vww߶mԵ%''=o۶me$-GЬ'N߿*));;;; Vc…~jjj޽u{w~駌 kAڼyӧNl2}}}˩+-GhRAaTT[YY 8P&I]h!!!r<))IOOOrZĐ%KRׂ?~W.\غےh9@󨬬R($/Z򕕕8pСC.--ڵkppꎤ>}{𡏏]8~x@@W_}kI] ġC&L`gg}v-7EˑZB#I5SOPP$KKKk&qᠠ+W>RҢ~8ph^+}ߦOtR###-Ghrc <`N:I]#h+nݺӧO[YYI]Kϟ?ĉ...R +//7o?wyGkF!yr$/quU VYYimm ===)u--TFFz-kݺukԩIII˖-ӟ$u9DH^Z޽{ɪ&11T5@* ֎ڠ}EFF tB0`CH۵L||)S 6nد_?0-GzUI:x[ <==U$ 8cǎRGGGgffUW_}GI] 4>X`I-[fnn.uEGH^mMnn*IHH8s挮ꦤ&d+WJ]K r}sͩS&&&~7s̑BH^m٭[RRRT)̱c>|0Zc޽{ĈR"(`\~a}}}ˁf$&&0󓺜&DH^Py3%ݽ{0AAAR3iҤ -Z;=zSZJr~رc/_naa!uEMÇ.\(a5и7n9RBa w!SSS㓒nݺվ}XZZJ]#fpÇK]cY[[K]ĔJ}z-u-ѿ7|S*Z{n׮]׿h5C3HX]z599~LAIII YYYJ5 {g;&N(u!! EP(:::-Qעuwȑ3g޹s_~ fˈСsuTRr=99Y*@>B\~ԩ \xQ__E5:G[vhS1xT@A(A._|Ehh޽{mll<&yI=vPGVׯFYjUYY*aj@;隴?{n;;;+ @d2L6g!č7=rWpĄUWãlS{]~5S BOOOfƌBGO <==U)L&dpҹ:oZkP{O<{Ϫ|fb?^̌<{& ڵks̉~7ϟwm7yy1۵3N]B0zn.fEzouK7"[ێzhu QG rQͺs?,k_ں5W#7@}YXX !ҒSRRo߾xb!߾}̤.f!rnƫW !W[)_|qp__lOP|w~a.]"uE-BM^2үɹU^^aϯ)G{V d>oݒzС_mkWs!MGo,RGT]ƍB!OG[â7}<{TmS?J ;u2WMԟ^[ں׵Wc6T)uΝ~),,,777ݻEPPkjժL%_,МxO<9d+jFf=3ߨ^F{Z'?I¾xںLU[âLOO[gn/T=;;oo\瘘t7w/o;/o;UKBYlu~u@s[jG[jL&d3fBܻw/--رcǎoJO߾}}||:u$uZՌB/ٲz-4[hQ׿Κ5k޽66 ۠ jK !:vjWPpҹ;NV[]\uLX,pEeؘUT(;:,\hee!止䜸،JՖP{#IDAT3ү\?bD.]&gqu}DT bT7(yxxHZ5I$$$| ;v옏z$/ڃhD 33SDFFT*:tdN1O:Hi]8&&&3&999 @ꊴ jbiiiiiiiiǏ߾}ŋ:uӧj^Vرc~\\(uEZ1ɋ_@\@3zhկw=~'N:u\__Sxzz25Z2Ee)aL:qG}sN??ذ0+BI^^;PG7ncǎaaaUUĜ>}:33s͚5YYYNNNbt"a@U E!aA_re^֬Y3em"mM6LLIIٳgU#Ŝ>}O?sZ5X*qssӓph+233,Xn:qƌM'M\TZ)&..o400pvvVϝԯ_?+++i+-T*w_&&&l޼ysi$/hV:Ŕ;wN5wӧ;d޽{IZ8Vk׮O<9zxh$/L&dN1 ܹsN:ySmۦ>}'H1PҥK.^SN]bE9$/hA&Mj),,}<<<ڷo/iЂXb…ׯ_:u;#ɤ."y@֡CA 4HRXXwƍ7nBXZZzxxQdޝ;wpFNNw}|rB/vvvRզСCQ{ 233U>}z֭Q۷o_SSSIkP(lٲhѢDŋO4X@2((ꀑׯ_W1˗/w^Ϟ=a^Z`ҥ˖-=ztlllhhu7$/BŤӧOoٲ嫯R(EZΜ9gݺu͛"uQ@Lw\mCDD޽{YYYO>s̙3gl]3C'ĥK/_bŊ+W.Ydڴi#p$/BUm:^L\\_]\\,̣ӫW/FUD߿/,]455^ӟ$u]#G)...77Waaab<<<6HKK7lpߐ!Cxoi]H^zxt+WTMioBQrssppp000vFIIɦMV\߭[_~y֬YRׅ y6<<\ϫŬY&;;[Pݦdoo+]ZBwUVmݺU.5j۶mFV0###L&&Nj)))NNN^jUYYcǎ......NNN斐z~aܸqR yƂ\UիW={Bkw Zŋ+Wܸq3g}iӦJ]4E._rj}7n! {QN>}}8Jn߾=22rϞ=O?ŋʽZh) T\Գ))+Wdgg={̙3gϞ]~˗JnϞ=]]]\\\mmm h~XRlٲ~LЄH^KGGή<8{:KJJFFFNNNFC=ƍ6lLNN611?~|dddXXԥ9wƲ=Jiii7opR׷scfp;vlڴiϞ=J2<<;vԥYX5RjqLnnn\\*100d={}۷酇/Ydܸq:t4HZcϟ?ܹsΝ?7n,,,BtA}cΝ%*h5rytt;snݺM4o[PPAb$/@ҥK.]߿jÇ]S)))i͚5/^TMGnnnڵjs̉qㆍرcz!CJ]Z HDDDܹsׯ+JCCÞ={:A#ϗ寿1c/TH^<^N:uԯ_jׯ_:vLrr狊`h177M6I]Z4ccccccSի9S 囓>}~1zի4;h%#yXvvvvvvC ~ݪqLJJʺu]VYYcmmbw ;ұcǎ;Wk/((ȭ"99yڵ.]R([mllMMM h׷jcqqqnn*rssʄFFF={;Z]:"y"oۻZ{iii^^^>2ǎʺj}dTd\\\͛}':-*UX^^~ʕK.]|ŋ.]tRjjիW˅666n2={;cccv@[D144tttttt^YYb._Jd.^:ݺuS12,CWW{ݻw8p`E FMf޽/^$yФH^ ]tҥKa}'N(U=] Z$/M4BTH^ @S!yh*$/M4sǦ >:B>`.1{63n}TPQ^* @Ey(/TPQ^* @Ey(/TPQ^* @Ey(/TPQ^* @Ey(/TPQ^* @Ey(/TPQ^* @Ey(/TPQ^* @Ey(/TPQ^* @j0u=W7qkk/ڶm8q8|ϣΌ#lQMTn0a %O^[5;myIh4|FmOw>N2&]>}AqkMf4f&7?jm?Oټ?]\c{ҽEBݦ5Ttz ^c9/0gkF5imŁ*4r7_J2oXKBBngx,ByF}o(~Eq !]mw?yn4fʐy!cҲ>$4\&Cڥ*e;RslaًvSd}j*B}~;a2Fh?9QY#wj1k/{K$F#4HM]F E F6Cƨ)V/m+-sߔmZRF1_8 J!ޙ+[81`*BY=!DBMOe B`CU5- F[gm82mM5ttQU'~#G|=y[gX#M۽Rq"깥2-+( yFVbKvt o4 &@1lٲgҖYձ6dKp'b? Ҡ}&Ptߺ>^.tB&,x|lmY/B${,y4erijY\.WQ5y!x}\)h/d~_2o_5~ϸK:W罗ŷTo+4By|5k⮮'y ^7%p>8⮮W~0` ZÜPgR0Py(ȼ d^ 2/EK1-&X: 5(BHjaSu* ֨SG;F#e}ofBp4bCJ#<}}^ 2/SZZZhh9N766tlPᡯd2}}}_Z%vbL&dX]]]ni4G\!Q$}7VVV:::Gm ۲>bh4# áh,Ka*xbX___Snٲp#5;[?viV rϞ={՜9s}{ Fիp8!ݻwBe;  ٳGa*xbb8$${SKKK?N( O ,HNNnnnTNoj{%}}Ȫ*>h"U7n000;rѣ]/_RYYDݚy]\RKK… ϟ?J.^HW\0<#G8qbĉ###c}] II@(=%%%%00TKU`0T!/TPJ9zlaTT!bQ{|ɔ+>^tIZR^^N5jTo(D灁PO Rn '!ƍeeeNa @_Ti&{^' ^WXcdxq5PPǎ+-711 ,WH}x CF6}yYU󄄄KzyyeddB/^0<1==MMMic-BKGp4@_`0W_I$u֕x81x=fFHҫSy@@qfffyyyYYYVVzc <H$bX,>z`C`Ȑ%eTʕ+ !gΜ~*[[[Be ϝ;'2~xBHEEAjj . J\R+V BBBBھ S'Dq'd^! BΝ;\ڵkeB8PSSٙEQPE444444;v,,,LZEqttTUU%%%YF!P+w\.WA?$I?R2LY ؆ !AAAQQQ<~q>/jMv ߲elɪUbbb_,X 7UP9o޼sX|}}/^HINN~wdkW^M^\G}t1z}^wNqqqϧ~˓e*xbM6۷dca(,,,<"""֯_?i$:ϣg| S'FٻwoJJJppĉtSXXXnn 5 y y(ȼ d^ 2/0h4ꤡasSU2@x]yut Z)N0` 0rSxm${gGN>M _Tٳgw3g@ o}\z\?@n!9/ӂ Auu5Soڽ{^tI__?22gdd,ZHqUDDč7 92zk׮EDD7˗/'&&TVVQ$=ep^W+WҺpϩ/+W*1LOȑ#'N8q"éȘ>}zAAw}'M@I@(=%%%%00TKU`0T!/TPJ9zlaTT!bQ{|ɔ+>^tIZR^^N5jTo(D灁PO Rn '!ƍeee~TJIS:p8˖-:Q"bŊPL&Ս7N*]]]>___?vXia}}nGG4H[Ry<FѽM_^V7^I,7UȜ, QBTlUkLȖe}|dƍݫ^eD}KoZ`]a(,,,<K:@X||rl_h4ӧZ@ˡ*>?{Us1WwM $ g%␐UNNN---GtvE,YD5.[w*m@i)|T޽{/]YUU322-Z*""ƍuuuuuuG=zk"""˗[ZZ*++[SU28+Wjii]pTICCŋtʕ+'vȑ'NL8wttdddL>S<6j}Ν;{[[۵k,--wѯqտmr<缤ʮUa0T!/TPJ9zlaTT!bQ{|ɔ+>^tIZR^^N5jTo(D灁PO Rn '!ƍeee#JHHoWԅgϞ6!ܭ%|4 j,[zg\C\||+zl@2LWWW7n8 tuu|~}}رc&&& n-WH}x CF6}yYU󄄄KzyyeddB/^0<1==MMMic-B566+¦&ꪦ&###>.[w6Mr=(Xm0 }Wdݺu%%%<O"έzl)͌nIWcc򲲲,ccw}@x"H$bX,}2H.*]ܭU 0d^VВZ}v[[[*qvvv3gtVA-!Ν^E?~_ѻ?'H0e)xb6l EEEUTTxҟ~ۻ_7^~!Ծ6:~ Jvw)ַj*?@0w_T7|{ogg'ճܔ "s4_D eS!UݯUs3U"[ޗaaJ$7v׈=U_ׁvs^pkkm۶9rDNONNwuue0zzzgϞU\s۷SU;vxt΂ tŶm>"""֯_?i$:ϣg| S'FٻwoJJJppĉtSXXXnnncWǏ .\x ###%%K$***44ҲpZ=@Zt?H,_~ի{ssD"}e!AKSRR~Nmm.*$ !ƍCF,d^|~vvvQQQ]]ϧBʶfff@-`x͛O8$!GSS"F?~<E @WWH$"O>{ӧO0alKjf7440/jiiuuuL[[[OO`L0L5äI4559~F*@MMMkjj!׮]KOO bdxzzΚ53A o+›crܼ;wMLL|i@ رcb2L&bFuߕC(+++G6mcJwbh4Ñh40<1BHllԩSlҢ`8]wLќb1U"i4ڲez3f_|QUU?tٳCCC1)))??)///11o}wnjw?7ߜ4i5kVHH֭[8']a KJJSuu55d:88X'P߼ys̙jiiDE_rss >ۨopBBHWWW@@\."HTuvv߸qCjΜ]\$ IDAT9/^餗rիWGGG6p84, -W{ <{600ܹs{;LOL"'Nrrr=ztoq슿gϞ9::666ĄBbbb>cv)"K,Ÿ&%EL?VEikk˭]%xUxwӧOڵko 8!d:;;m޼y߾}III|>_qϞ=iiihgff5WmzϞ=ӧSv"GFFVUUE)ھ};! ::ȑ#T`ΝT=dR-,,[ZZ*++!fgϴt:F"t:^WW0<~2qDS__ё1}tB֭[8")ߩD"9~8!d@ L<ЗǨ|д?~زe{7cƌqIidd4mڴ%K۷/111??]Ձ hnnsN||w}O2775?~whh9--ɓ'H,3 o>>>׮]뱙ҙ@SSS]- UKͩ9|p TB=*[EaXǾg^.]$-)//'5KP@Bȁ')7LOӓr ²2BMG$;ШAvW7<2/eeekY[[kjjJڵkopn߾ب@`P"򶼼VB:ppp044o̙[FrMǯXT!xuuuۧ(KWMLLtuu;::HVx<!JBɵ˪'$$,]+##啕xb%uttPSpBNmRSS䤫Koddg>6fZZZ*ѣGQ?R ̬&Olee5eʔSZXX(ث7l ǣ+=n2j([[[kkk6mmmdhh8%&&N:u zꫯ$ɺu6olii`0^x?C'?~x=fFH/ ŝgffK$,cccjEDmD}NLLL|~KK|#ʘ1cܤ;1I566J0TR̙3=Ru JȲ2#2/0ח>x𠴴Ç|>hѢSN2eʔ)&MR)M}2ecƌJ\".77̙3k֬VAm^^WZ%-6!1lYTG(ķbŊRO5$$D[[0e)xbNNNoΦvuO<;wAiFFFFFFrҳݻoUTTPGPR^:{{{===U@/g8mmmw=y_Ϙ1za0...K,ٺukTTԵkתbC7yr:_~ϛ8e_{BȨQ_]]333MaTQ[GtٷzTYYz9 7_D MLL!'NxDۄ[[[Bȝ;wU}f&uO?DM%%%mD=ߩD"|2uH$xԔ޶zu{._|СM6 chh#}L[[CaJKKP˅n<8˅rJoٲEdժU111D  t4!HTuvvΛ7__ߋ/RsFyZ{~Gǎ k{Eq|ꇼ/ÇeeeVWWBtԩSmlllllJ'O&k@ rx<۷H(L&ݝfoݺ '(7E(X[[o۶M@ wuue0zzzgϞ}i߾};UcǎNٻGGGt m۶ B["*Wܼ~I&Iw5Z%:Sv풫z00%IJJJppĉtSXXXnnno8~˗/'H*'jks^AWWtv֭[lt9uRDD˗ʆ>as^@ڨmYJKKKJJq7xc"x) ߿@trrrvvvqqqssspp`P!0ӝ;wjkk~C[o%$v=ji#BtuNMFM}}{I¥ikk.0M0fSI?yJ%$$D"333*, s^`xH$VTE!x*`0,YfΝd2U漀ڒmŋ%K\zZX]JJܹs7:ޞnll<{l__9s8;;h4Uj&x<ԡ;w練h4C8xp8TEWWwjpBԖBp͚5ǏŲmh4֭[CCCU(+//OIIIIIvZCC'NWubȼP޽ )SX,777_sssUG ɓ'\.fff-Zf:4yȼڒͼB$֭[w%WMMͥK>}ZWW?ްaÔ)ST.0iii׮]ן9sy!޽{w޽{nNNNaaP(d2...,ʳo@MMͩSDE P[rJdd͛{H$:vXDDDEEg}dMMM 0XD"QNNׯ_lff6w\??s/C# 2/˥H7j={ʎ?~ܹ}} y1 Urȼ1BK"k۷*III{rɓ׮]'^ D"Q^^իW\rƍ/^QY___?!Wg舍Z@9+˽~΂ (d^@my!$$$,_\(ՖGh!!!7ntvvԀH$ͥIKKrya PX\TT$M+WƄs5mmw}f;zzzMȼRy!dgg8p,ArJJJJAA7p@Ұ [nIw^{{4:zhUG CXL"UJy8B/OT,_z5222)) K`${)III?~o=|cccUG@"D'nNN5eԩd ^ųgN<)iPå/yK8p@iӦMNNNg!GzLurrr{{u@@@``o902!@ Ζ. *-- ӦMj骎'Npܬ,]]ŋ)EAͼPZ[[OYv-L"&'''''gffbpiӦaG5 ۣGRSSY-ommR-fOr/_H$l6{…cƌQuhP[yz !X@ijjtϟ?`nnp9shkk::2/rYYY5Dr'XuL/  \.7%%E$l6;((hDx AȼP%Hs]v- P Ν;3k֬6=aU/233ҤB5e$5Uyůr\" K``\k A˼P @ .PCx jS^wwwUȼ!>m鬖Hdll%؂?@DW^p8 3fXlͶPuh AμH=xȑ#D+V9ؘ|ً/X`6mkkF.d^FGeggS޽{MKKeZZZ?DA ٳiӦQ ɓ':4 -Ue^(mmmKdddQQ t'nܸ3WW`U6 0]~ZC֦!bggT Drʕ766z{{K:4 -f^(B̙3~ƍ}PAA=qă&MpB6C2/ÙX,.,,yfVVVVVVQQH$2119s̙3ݱ] 4.oUUU988|˖-Ru\jjd^={vΝwyGՁ  ު%!"o߾8}}umذTA*/ZZZ.Z)A pSPP@Ւe``0k,jV& R\\|رS\fؗ2/gjyEGGGDDTVV;_|񅏏P;b8==ԩSkMMCHHHHHCy򚛛SSSe|а!rwwǺwPrB33UV"wW%%%144OAO|8..N$]vܹX/yl2"*rOR;P۵gPsqqqqqq=rtt Y|)ST O>THN>q6oK.]SSSGGgׯǩ=J{8{mݺ'(;wᇫW7nRkȼ t*ےall[oQ>O6d:FEʎ?plll !\d} ŋ!LLLZZZWQg/kuvvLJ̝;wӦMxGGGs8Dlٲ{xx:(5 @)((\\\,H-fͲVu/@-)7nܲeVZ`UWW/X{̬B-----إK*a)--mǎIII6666lO㣢n޼q`!F dggK- Cz3ƏԩS\.t:}8CBBd[~* lػwŒ-cǎMNNvssSUTۃz7b+*))9a#*)d^^ISS9nZZZnn/Ǝ;gjb ~E"\r%&&&11ŋoU3PNyoܸqƍ" 777ooo UcǎqB[[իW/_srOOO//ӧOkhh\|yҥmmm\n>}jnn.nwihh;vl111)ݛ`eeq5k:(uwSNl޼yڵFRuP* @?~|ׯ>x`̘17ߜ9sPRkkɓ'cbb233MLL>P immLOO1/_<%%eѪ o YdIpp/?pU///6EFF_~&&& @=y$22O7n8NF✜캺:CCC???jbҨ9111)))t:}ŊoԪ ԗH$ s͛7dՠbE---Uzݻw.ZoU{ETTԮ]-[_۫:A t]]]7oLOOOIIyf[[[oE%\K) ߏ_jkkgϞr`UC=x>Go (*EDX(T(v1`'hL$j,F5(v55 zǚ8;>Wg2Ξ=W^3fcncƌJNNLWSSsݻwgffRoޱuP]]]]XX333'MasssQyΨ֭[ԚDԂDxE.H?>}رc>6lל9sD%K=zt޼yksss++gJII:N*99Æ oD@V__iϟmذA'!EMMMrr2$QzzzUU㭭m S^^~i5.3g1C-fwܹSԱ/VڶmcԞ| [xӧO߲e#2d^@|rm*ۢH%H=:rٳg߽{ggg5e8kt1#QRQQqa+xW^=zhQ%&&YӦM0`#d^@PoɽzիWݻW[[1vqƍ7ȈF:F+(( uuu/^驭-@X:2,u1˗/oٲ޽{cƌYn+Ix`2gϞ߼y3͛7khh:(!@z1mIJJիmmmǍghh( {qx{Ĉ $݋Ō:د:t ^jkkm۶t__NNNNA 2/ NX,փ&t6,Ǐ,//wvvvss;3wRG#Gt钴Á+xZb޽~~~ZZZNNNHpȼxM\\\bb͛7 TUUm#''رc/^066^`3+@ܼyaʕXHݾ}~ؾ}cF|?طo_]]… ^ފ7o޼~[[ۀC:"A Tqqkר-666TǃΠԩS .222u\ ,,,d::tŋE 4z={rss'MasssQq-_<--mٲe7oVUUuD- t eeeqqqԲDSSS*bee$9~xtt4N2e? Yii۷~ɰz}Ǐ7NԱ@Wnݺݻx/o,k֬a0?ӊ+ȼ%%%Qs[=z`0̨l9V3N!!!u\ ؋ݻwdzxxܽ{Ov|x/ |}}=:vC:" ";wTVV~رt룣9reeٳg/XΡݬ[. ڵkVVVZ`ܹsk׮.]SSSuPѽ{|||^zvbȼ@)((vtNNرc tǏSEŌ;-X@FW WZ /bjj2x`QG 2/ж˯\~Q.{Y1cÙBTPPrرϟ-ZۻO> :j1~ǎ 'X ^~<{lϞ=_V^aSȼ@ΎvZyy ^ N?|hhh||ψ#DtRYYYcƌbFK'N޽&MZf ^ =eĈ!!!ȼ|9***:::))Ǐ...Ǐѣ/_>|844Çvvv^^^x43l֬YŊٵk׍7,,,֯_憄)ϟ?????00>? Nq0Çǎkoojdd$DԩSAAAiii{7oނ DtvX̨RGɓ';w<}ʕ+}||E@GQSSO?o^^^PW@xڵbwww777KKKeeeQG b,ի.\2e;`1ΩښN:ׯlٲ+Wv]At7nܘ;wӧ---EȼjkkoݺE=OݵkWjh{{{ #Gedd7ӧOǚС:tuNo޼=z4:  ,Xv̙_wٙΛ7KI۷gffN4駟~=zG.]t̘1"dS=|PJJjܸqD&&& ĉԫsutt.]驭-Ōڵkݛ$ ɼ|-[ݻ7f̘uֹ:(KMM:uj׮]ccc  0y쪫_~嘘ׯ_ɩO> |||L"'''xa0wbF@Yw4h!KNN655Ν?Ag{77/Z[[$d^:7o\|ڵk#Fpqq8qȑ#1\+v@,f***ƌ$͛7LLL|}}M{}uʕ2333777wwwSSS&:GA0̩S&''cΣȑ#~~~>}5kO?'ի6}v82/b=x ***22ҥl͚5bF?,u9UVVܹ`پ =0LOO褤$33>2/b....:::>>ךӦMsssVPPut\@}ס^^^ɛ7o̰QgSWW믿fggO45jhst:}ҤI驩m=P]]ҥ肂]]]wwwwwwkkk𩾾… AAAW^ٳ /^+ZhժU~~~w}cQ;v˛2eʏ?DҲK.IIImw 3/wٽ{У"##Ĝ={666‚J4:twiH:#B޿ogg3yd995=qd2nj|UVYZZ {G~䉳c6u[__q??W^KgffZXX8;;:ubpfunn3g @gz]@@uݩ8PTTtu5;wRSS)b{DaXQQQZZZXdI^^^BBŒ3M|@#nΜ9ۚ:}vl<[YYم fdd\xÇGNLLlX#&f/̙]v]Zewo`aЙɓ'ӕLn:;;;%%%>۱YKVVAF 1egR?СC^3fLXX;pm,H ? O@2^@7l0f̘uֹqV3vܜImO0a˖-F7n\[Ѫ˻w1b _Bt8B/< !?UvUѫ/ONNݹ/RݧiS:zhM^ϦfiS#$=pm}搙σi˂rřb031؃B %~營mٕ:㧞ZQÞ&@n+gee!C4 5?0 !DZZjJG#_o[ߠEyH?g}"mm5mCi}|QުO>B44#^Ͻx|%ƾ}LLL'NMd^w͍G!>*w0rY-\\;{###./_ipEF">~blol;]yҢ 2󽢢\+c~vF8D#r~Ic8W`@|fƇz 861eXazZtXawwR?;uh:"@iu뼆yv؝[˹s'W楩.'6icǖ-=dU}!DNZnوe_=vuNǪ8O~}P5z@Bȥn(/QP1n!|P/V=Bg@I.&Tj()[%66/3?P ^7LU?yglo䨁7?'=PZZ÷_sh1A܋W٭Z¢_~ofsG#4_MI\v\}-sscBN?m`Kd&Bǭ[3/02/=Pq~xt@݀r4!h4vWmFRS Ũq/4Fg(P%<~!÷޽4ȗ/?...'((ȶN<"͞4+$khL APPF%| ,nZXY5Eh ?}r9c$ΤS^H UFBH]CѣKi}4jMk&BiFCR_ԥ2/!yԈaNEEAYYQEER]Tr|Wlݺ5..n… _B+Gɧ'+W˨UU,¢w 2 ֬er%_}}sgfhM?|Z{rDg<u{'MkdcRZ[BfJ7^$?!//yphhF#GYo{U5'i8 "CPkGkKLJSx9/_ VxAe}ƻw3/[ ƚ^}?{0aZCj͙ct:uJI5rJKo+*j ճgB ث=@ v>~(UUV\@KKcW嶸c^w\僂lll:=7!7T1̬WUB'WVWR9r~nݕddr{E+fϱUQQPQQ7s9!/o{3UGMMYAAvpm6*ʫ !#G 2Inu!ǔMc7k+*[hZlHrBH+?5f/'fžJU^rO՟ke͙ko[^^lO ϳUk׹XXv,%%եˆ?P-_`3@Xg`)/ !,,t ǣFF0\?n'gLsA\䕔]\M-L~VJ͛7kjj.Yif~vۑ~;[jg Ʈp:hn<?"G4?| .3FOr1e;),t]{pmCþs-=Zrˣ(1z.ǹ=#/>{2ذI?c/JN\OOY[c_xDΏfOp3p ?<-|}?IxSH+?5f/'1Y㰇rёzM+c5k]ؿ~\y%1cﭧ=4+B~' ySYS;!$?7"~Q P_waag2:cHvsgJNN?p”)Sns^:F۲{Ywmg:>Ҽm(~Q˨JO]|y1:FWOj5/_y]b?s͍5Wd}΢3+ﹻ]tkӉ'{Y(BƤs/̍yId~ ~<1lbK{ %!陈{tFaa`0ĝQ[Kx?0wەgSEuOg߻ {'yGCn}x_`0?}tct!tϋ+ &͞4F _'sL!KkKrfYRSgDžؑV~k^N-B=0UYW$Ct8F̪Z7;.S6;Tm|+`VTdx<)TSwvM+sc{9!?0~ իZW?=;lرOX'""b֬YQ/_Y.SaHaeZekZ1c!n²rv`V5\3U zkҊi5=n mJU;9œبق Z#u׶n !3f̐h,B᭼?~hhhtm۶ @GUkBHQ@!۷oߵkWFF`-=/gtڿ3\8?$f\ dA/,K,9tЪU._,E-ۛ믭u ڵkWLLLL cKg$+_@מg'q dA/Dk֬qrrnЌ;wfffxVywִ !dY2?(E^^^x45hP/ v**? )fBiG`trI]MyBi YwEbvj\fkK,ٰaìY߱UJ Xh~:M!AA(VÍRPb#atFkzD-#&\޿y jT$tf}Ylَ;*++ _2((];󒜜奥%++w[􀀀#G())FEE5[TWWobbdbbszv4̉k#+زeaHHgΚ)p&&&4-""fbb"X7y1Bȉ'lmmu&//vҦ<`tD=.]cǎ~a $<<\}@.\@9::6$c6,?~|]]q ׅ rTܵkׯݻ&$$DEE}ݝ:4UjWXϝ;WFFʕ+Ԗ8YYٹs MgçNԌ(,,JII5jԳg϶m30@硩g&%x'&&[FRcccBHy#EÇ'pn &P6Yԯ-ل.]4 4F9p !u377'$%%qn";:yh=~0hH69/ Zݲp bB(W{={F._Ws?ԣTEEEBHAAA}y)((B 97B9ڱjv]xgB,--_-,,!Ν<𸴴sw tȼпG|p0AgFy1떅;OBڛOv}h,kҥ]ZEEEbqFWWW7ZKx7ꪮ~Ν쬬Tuuu@9 `0L&>3o4~`t7|ŋfk !•Q积Oݥ]z!ŋ QOދAy5­[[ %N`f͙3rĉ'NB<==R~ɉ322"ܻwlp; #F70Druuߛٮs^ !;vSIIIddg///BȪU8_[[Z -*****:zUE!CPurss/_xbгgOBHddd jBNl0tϓbVXѰx}=/'F7ލS0"x _,yC^^tdd$j:eժU;wӓ8pM>YAVV666vΝÇWPPPVVt"yy???HQQqqqq'NsL[}5_#& \=z'??z|Q:@bxxxHKK76d^x9uyɓ]VVVTd^_4Fu͍N5UxٳylllSy~X_E$SbbbSȼ.77իW"Z^^^ZZZꎎtz@@ȑ#UTTlmm-711QRRRRR211ٹsg}}=FKH`0l٢#//ohhY&G]Zڸ Fl$""FMgr [[nݺ]^b MQ;VUU}=zҥׯס[q@annޥKׯ7^Hxx@CBNM>}B@{pxh.\JyԌ;a8h0\.\q[ݻ !nnnB#@7y1&ٰȨwMҦ-SN嬠U\\ܢ~q.w\!$<<5-_[|)MM͈ªQF={l۶mzr7q* IDATvƍׯ///߿E:t? KK 7 \z"%&&ݛYؘpGEÇ'pn &P6:sm~go&tҥ]xq777Bȁ_Oqww<Θ9!$))scVV!DOOwΝ;Ҧ/]ĮpyBauhVK>漀ju' \|h>}jX9/WS>|@kjj233 !Լ .QQ3J222Z:::6"X'8qHZMgɓ'HKKKKKKII 4F gǍGiQ bjԨQ,+--a2/yftҌjUQQ>A,QnU]]Ν;YYY...s `2L&}f8kۨݻ :4) >}7,Bq\DF=积O ^Yrņ('pnfE q-_TsŽ͙3r j‹UO798cFFF{5:E1 y&!dS~vۑyhmmmBȎ;>}TRRYˋjժ֦zxx.CCC=j*v!dȐ!T˗//^X.ٓ~BJgNYAVV666vΝÇWPPPVVt"yy???HQQqqqqGu&Nxȑjkkoڴ)((H.ݻwٲemhܼy~MN<!$00011q隚FFFVzaK泩Ǐ/Z{ʓ'ONJJRSS_WXXXZZʵjϔYf /4$Ԍ3!B @{덡sh3gWCkuK0bοӧ;0Cy=vd^Z{޽ڎ hhh|kHBkx+4ԭ[ ***\yeeed^ڄree%Fd^@ZZ`pmD@X,Fڈ  VX,w!TTTڈ rmD@JKKyheee]vڈ @kX=zpmGjkk#ZMMMȼֻw!}ڎ @k䨩u҅k;2/sCCÆۑyh/^4܎ @k!&>ݻ!C4,BUX,֨Q!*iii yh#F4Z @ܿ{ׯmmm-E@p7nPTT477oݼy\^^Rd^wMJysrr&MTd^>jԨ*  Xii*  d'''uyĥKLɓyA@vvvݻwQ+++rʌ3xWCŮ\`0yWCN8訮λLD 1޿{fkb @˜:uJUUG2/-uI99fk"iii22/-~333kkk~*#p>#GΝ;ȼd͙3GEE]d4 q…W^EGG ~sqqyh^jjjJJʵkZ4/ `ذa- s^ŋȐ֢17n4441i6yӧRR-‚9/l۶MOOo9/MzEDDDHHc @l٢)иÇ 6` @Sn:hРJ0^ ;p^4j:::oM#իgϞmM;L&sl s^ԩSOe ?~ܾ}ի+ybݺu[nv!$33388x߾}js^!dŊ-b@N8qջwJKK Yyήd>>>#Fnȼ@gauV@\z511X]]MaX%%%TiEEE}}=!"//Q#1u?ȑ#ݻwzȼ$9r/!dʕRRbX,!`0L쌴 B Ʒ~;vXoohW__/+++(ږիg͚%##d2.Mhgn# zqzz:Fk W__i&QG~RSS;,%%n!DᨯǧiwʕFFFmu @* NL#EcXEEENNNMBZZ# ߸huHWESZZ/4%@yyy)))B?# t͛7}}}MLLD 4o̙Ν; @LQ#cǎh  SSSWWٳg<#h)+++---w}X8{l޽׮])pZ-ɓ'{쩬\b}_~ɓsi̼XѣGeeWhhho7oޜ6mZYYk_<SQQ1jԨSzxx0TGW^ 5k#`2[nݸq#$WAA*//:tѣ###@ȼHYfX,"eeeQGg̘q=j拴رc]Fa0w܉}ݻw;fee:(ho˖-;wܳgzѦv%ɓ'5SꢣEhjj^~}ҥ)))~.]ZZ: 77ӧOLLtppӧwTTTmmHcNQQ̌ѣGggg:.hFbbСC߿O]deeDuڅ "JJJbbbt:!DJJĉ@4)yyyѰƍ={aÆɓ' 䨟333ϟ?/ڨ)L&sƍNNN쇑tHVUUߺCxHb=ztѢE#?6*z޽{KTA[lYddǏ>ȼH,;;7oW>xEDyŋϟ?M&icc>h QWYYʵ!DZZ*22RCCC$wzxxqwKD">GDE2}o߾\u7n]P⭺QQQgϞqtttuuurrb3N,==v>}\tĤNǏ_d No#[p!y; HH)| #)))??_T!;EEE{{OΛ7/--u0%%%Dή>>uuu ./yd Ύ;ȼHFg<9sF$HꍼWNNݻ !K,ѣ/D qm:wǏSqG~UJY,ք Dݻw߿رcy\cyb`зo_E̚5kԨQ>xSSSvGSA_:ujʔ)11qܹ>_yꚯzϞ=6mZfMϞ=T*UDDܹ ɓ'8pʕ`9|ttyinno{]@@  ͛7۷k׮4(**jcƌe)رcʔ)ڇ~سgϖ^zrc… b ?/T[[ۿAZM<%K=e˖iӦ :0555!!!;;bĈǏ2e8UTTjG/_/ Z'N,--=rHϞ={7n ֮];gΜ͛7O>] 4/ztŋwڕtuooǏ1B<Х曫V*..vrr;Kw?mڴHSٳg„ /c =Gƍ))){ٽ{睜ƎFTKtt-ٳG,ŋ ɓW^-wtǏ7...Nu4/zFSXXgϞ\CC#F-;@aÆgyF,̙3>[>|x޽onn.o=G]Tcccvv#LYYc=3n8GGGky沲^l߾JNNV*rgAiii8qÇ< oh^ 聪TqGĄs4踛7o6,>>^,իW رcׯ_/wtF3{o&===((H8@h^@ڵ+55ڣ&N8p@l߾}J2>>~ʔ)rg^{kkk/OugCe=G"11111~hnn󋊊 }13>11O>rg.I&ڵ+::Z,<׿x㍸3gʝh^ 车$%%%''ZXX-/.W(QQQ,{uuuC ; :?eʔ//;м9V._X]]miiT*Je@@[KHH0a޽{ǎ+w7{섄Ҿ}ʝ$555&&f֭ӵoh^ t[ܗW`F\wElll^^^IIYYRRҸqv1aGGGϜ9sڵV4/zv_B???t$L/_΢|||l"wtcN0!..H8w@h^mUWW'''''']zO>D777{g#""΢ΝuGyD, %%% :}͋yܑZ.((HIIIKKinnvvv'<; I&;vB,&55u̘1_}Ռ3΂PRR2zh//={C8͋y*Z]TT$n q-6(9s|Grg+7n߶mY:4f̘[l͋yܗli)LXX@^zܹ999AAArg۴iSiiY 9v Ey{4/VSSΝn )w:4͘1c.\ojj*w}c_~֬Yrg䊋GsN]^dEh^6###55u޽gϞ  6Low)__?Y&???//;vȝ͍ ܱcGW,EѼ:VkkkQQQzzzzz"Ie,[쭷*((2dY%%%rgmOϘ1cڵrǹW4/z *qSj[[ۈR9rHB!w:i**;;H8]UVVVDDի_|E@Z_~K/~ٲe]k-͋ytcǎeee\v>,,LT3fr]T^^x?Org钚۵>~\y.]i^ 577+T*Bx#""/w@!-~777t=og}V\\,wHE,X࣏>Zho-wAh^jllڿ:R""""""F5`ZZZBBBӻ7>|_y@*O?tRRҦML"wDh^ȑ#0#G 744;#Ƞ(88x/Y۷o&%%XK.TUUܹ344T8EѼtdfffee+88Xܝ7$$Dy|UVsH=ZpҥKJL_:u*&&w;Cys4/ܜ455?<<\\\z֭[ܳgYÇ,]Y L2iΝz6EѼZ]^^.$TݣG0qQ@999aaa6lxg΢T*UppG}o?k֬133;Nys4/KRgeeefffgg_t400pȑ#F۷#k7o.++;/^\TT.wt۷oϝ;wݺu~f=G555b UXXjoo Z-IDAT͛7}}} /wURRx; :ŋg̘vrH4/znܸ+0YYYMMMb tCS*]]#F8p]dddL>d˖-r`4/z?\xW<&iȑ};#܇>11v.]zzzʝuK,]v܉:͋yt7UUUb y1CCCqQRDD} BTnذA,:lذaނ ΂s…3fdee}gs̑;Th^ ;xb^^ߺuat &Lػwرc΢juxxJ166;:FVVvQ``q$Dh^566>|X{RիWn fnn.wFؼ\|q>:tW,fɒ%΄ ֭[gmm-w"iyYlijkk Ǎ'wt0R 322/^ثW#Fȝ@'YlYNN)9%%%00^,2h42dY`o)k׮=s{??Ao5/zJ@ܹscTQQ[^^h<<dfzlԩrJebb"w V tp_޻>W\ꫯI6bt~&77WW<<<<<<yAT*ѣG333 ,YricccwwwqwooЭ9Q3N.ȨZ}M]JZhѣG988ȝܡy)?9#[; 7kjj a6nlmm$0Y #C!h/0/śvg`v9GW tl0ccahh&#C {QPP0k֬SN;NgclWupw111 TTT-L||… <<<ĦÒt-OD;-D{orSSJ;07WoTw}?߻w܉d@pO B3g g,XrСb 3jԨkZhЯ)A }1A"d /_n$!AG[ZZ;i ˟}ْ+V̞=.ץyxvvv111tZ]^^.0}Fdff&wdv s766˭ܽHٽ޾wHptT}-A(,<|0dȐ|///ɩ6/nFyzz:6ojR9s99G #>ήS]Ɔʨ!!lAP{25T㒖v͗fk/]OL,<msvW FFFtYf P__!qޅ ՙ-L@@B;2 OO{ArO>:];h{?C|,Q[Cܣg sᷚU^ݯ!emmT*J o.,,۹sA칕zw|r:ݤI&M$O-Z… h%XXX~﴿:َH]r_3fw}7x`nڼ̞mMLJtR]葟:)ήSj2^)q={a8hֳamsRi!{allהw+/R3Bp441-vZ yy3ӧOOss__Yυ?%hh!0h/tz'@w̚5O?̼~zIIɊ+ ^z!ChS8C<:=za=/$tpKK뽜 =`?G;릫P'>=T>9|||f>=|ٻܕRnF8Z*T0i8y\{۽+.>7b9iG~P}_y}:BP(fƍ}Fquu 6l߾}N @' =K ۷''j}ۡK:-'tJff^x!--AVtCݴyII.4B(>}{5dؽHju맟$)֭ӧ/$QւFp>/25U|sgӯ̌ݻfs->{02Kzտz]t ?M)/((/((XvmeeFqrroQt_Jr c~Wg$'ñ{Nz7nqaÆɝ+1hx˖-ӦM{w_ڙgYw*z>dii9tPBhnn.wR@SN55a1T V;{UV999-[Ӌ@7}]RQQ!0eee[nm{j߿gϞrȩeŊ-RK,yWMLE[[q"F~ŋA1!!!55.)]WJJ[oUXXs-^^D] @w `-l[lڴi…B"F\m`` kj$233y~AT˝ˣyC+bJKK"&>>FݻBN24dz!kٿ;35~ܐ ܍MXXXXXx;w\| }:tq/@[o>c#G;^C8sG 5rGtk⌘hիW>\TTt\nnnb!e&>!Wwݽ{w```JJR;C+w~h˖-rGT*۾S)+++--+//omm555uuu3t~hKƇ <};ƒ^x  7}t֍JFHihh8~SLYY٢E\"YXxzz%Kl޼aժUf26\HJVb-NIMM[[[MLLܴg'ʛFٵkG}m۶httvbn߾} 줲K. ?P( bff&kpZZZO>ȑ#} h^ 'SSSBP(I1jĉG=rѣGw!ԫW!P(|||)~USSӚ5k/_^]]=cƌۡy122􌍍G]vȑ⒒-[\vMa|||{%kv! ׯ_lYMM͌3ϟP(MѼ@{ԨQFҎ\vRk-[jkkA() ??GyD +V[NV⋯ܡ5t1{nko]]]iikoYYw}nÆ ѣ@*jz~iVVccct}666aaam71֭q㆑sy1l @խYfڵUUU)))G600;~B=$T*ś*lK.UVVVb"79ܻcǎ_ڼyhܹrB{4/&&&b2m4qZFiӦMͦ E%:EV'%%} 666{W(u #cccqƍeeeǎ;v?㖖333OOO//////gN 3gά[ngϞ ]z̙3yEq4/ e``````Ť~'ׯ_ڜ3h vU 7o~Wk֬)((prr~U\'4/n(VUU `mm-b===@ZoyO>#22ז>ڵkG=qDkkksм j;XWWWUU%΋ڴi[ZZOgǰk N>aÆ-[;v7ߜ9sܹБh^yhGT*ٳg1;wGmRLJÁ.ڵk;wONN611*jkk\ '|2>>^TZXX bzvR\TUUUXXm۶SNi4ccc'''68W^ݵk֭[5MTTԗ_~9aSѼ\mggo˝2y~omoݺ?jTΎ;}tkkxyzzSKt˗̙P[[0a„I&EFF `ff&V*111+WTVVVVVVUUUVV޽Fј:;;v Ews%J5o޼Ƿ_h^Y߾}n1'O>zs֭rNypڎ;w?ĭ|+++cz꥝3hРA 8pfff\q ]Ga:99999EFFzj:&//o?ckkؘ)hҧO>}j#77믿>sZ~d˫Gr\<& ׫NPUU,s1trrѼ zk7tsdo޼)>qU_G@YXXJ۷o={̙3էO>s̙3g Ν;wmAi2NNNann.ӥh^t=/Ry Th^=5cQIENDB`python-oerplib-0.8.4/doc/source/_static/dependencies_v4.png000066400000000000000000001530731245703354300237550ustar00rootroot00000000000000PNG  IHDRZޚrbKGD IDATxyXTG7j覡Y1k#n,4hDtЀԌs$w;38NmI4J$.%"hp@**6Gݜ۷cӂ v^""L"p㙚- n-B,gG#Fbbˏ]7d$:QMJHr"ㅦUCsٲs/%S֕H !vsCUͭ8t͜FBYęq\:?Jܘ~o$]7`lJw}[:T*z%]5v漳AB*)|?li+,~ʺhcz%,F=m%21BȃaGiB*>(ܴ?hх>Ïj)'0`l0@ f ].aa n]7ao!K}o:J]m&,8@`H?oOhI5/9trQ***Nbְ @`̌?~<dz?~ZZjLwީSfI$۷ B@ BΝ;R)S-á pT%ҷr|֭|>#>>^j~ԺrPpU+ILLp8BPn!ȑ#|>>joo;Sq ->q83g\D2~YzzzfϞ7kΜ9D6FoVObb@gV{nBHddj%={hMS(+V>s4[tҥKu ]FԶ6Dr=PFs?쳴4{dee-^=+...==СC433rJ\\`[xҥw.Zޚf) U+Wr.\hii)/^x+WԢ,#vcǎ'&&655⬬iӦ駃1xF&n˗###mll=0АBB,___BH||j7|C mϓjmZZRYYI155ZTI/}B-Z]7YFlƌtĊ B&{`  0 @m'N7& ^xAKQOOOSSӸqĦ&+++###XL8YY-644dZ׷&ϥZT~ԩ_=000++sԩ%KhM366LaMx䱽{i5Xd0mٲET{eeeJܺoߒLX '=9#"",--++++**rrr,--.\8m \. BP0#A/ҤI|U xԝ;w={=_ti۶m4wL0~!www'߿_.8q"{ߪU^PtSˈB>˗//]ޞyyymذ!??#f Zv90@ f ].a5>?yHv`!:ڲesrSx6` lQ͛UUU]]]|>.88ٙ)P(]VPPܬT*탃سryVV۷[ZZ!Z2Hm2Z"}y挌N YfP}ѷN+/-[TRTThccn:-2bzT:fOOٳgٱ؏`!0VM?- 0깸̜9=z855իyNNNyyAXX@ dή644 wuu%VUUegg +WtttIII)--ɡ-[;kfH*KKK+//"}}}&ˈ;99x߿?==}޼y}[RyǪI]FӧObBA :#<<< 888DGGgẙ)SBΜ9SXX8ذŋ'OLᥥOe +766vqq)+++**>}:!rXnXnn.!$**jĉLֲeSRRo 6Umk@ PGRXQQQRRR\LF_-KԦHhlBѷ4kPsssBHoo`+Ѯrvq&2n hkk,=ceth[ Z@`trR6mZPPИ1c\D"ٺuS5Q*JI~Kr\ի앻 VRY[[+zLKQ%[듌P 55y1TVV/--eVh B!(h}kllfaaASjjj4*Q(zzzZ\N__LJCח+Wnb1++]^ö؏ ע &333BHfffwwwOOOqqٳgU Ѐ… ]!jkk?ΞEwMMMb֭[L!ʊiooD?]]A3$蒢FDnb1ѣmmmRT&8p߆؏UCZfnSNMKKKOOOOO)B0??)0c HTYYz-KV``H$9ujS`` }\YYYZZZZZJSLf9WW[n%%%1)T'/^u+_̨D"BHLLɓ( 3pt.^MM͛7|]pp3S@P\vYTg򬬬۷oB,--}}}i.RKo7oޜiaa1k,/֩u_~e}}e˼Jmll֭[E7YFRPP[__/Jnj9{lCCÁ>':6mR/IKK+**޼y3?9%%͚5+00;%%E$M:^B@.\~rLuuuuu-[Xd2YBBBuu5U_____ΝX替Ξ=G_777>}KrPx…հKAA!&MS*'O,,,d>|)V^|HHɓ%%%/T {^xoPx 0깸̜9=z855ի4쒓S^^n``)233ٳ ]]] !eeeUUU!!!jaEEʕ+{zzRRRJKKsrrhha/--\, BX,.//բ,#WXXhffϟ?ym@ݧwlUg0ݻo8::w377tO:ennr ]X𤂃_gݵkWjj*!3g433rYtFxx)SÙAYx3f ~R\./**)oߖ...dd1z`PTT@ x˖-#;66$cpBWWW !$22ښ⋄ .#ږJbEEEIII}se2}Aә/jlYBѷZ|Ӟ8q"ܜ;J߿`ט`2b4OT>,ߖ:::nkkK_"{/ .ە+WJiӂƌr%֭[­9RT*LH*[^5`M ֶ*Z@@ B7Q.{Lj)EB` mkk+!d޼yq9,--KKKԮejhhDBID4566jkk)555N ( =_CO ClII7U׮]kooa۞p`.!$33ٳ .\vC&?~=8/b[14beeE˴D~A.nQC.)*((P[aD4*=&Je2YKKKnnm?|0M:5---=====|3D"QeeeJJJJJ,Y"ԩSNNNupppeeeiiiii)M2e s\]]oݺĤ t v쬬 !$Dnb1ؓZp`C`t p87oloo755={jE__?&&&'''??EOO>((=Z*;;;/00YdɒVٳgkv x"sAx"u Cnb1BȂ oܸQ[[#=== gggjR`#`˗/RQQQՉK90,v ].0jp8,Trt.c5hJ'Gk?͛pg f ]F̘xGԝ'F'Nh9sJ$Ȓ3{Ys̑H$1jo~mzj< ݻwB"##U+ ٳGnBXbE,//AHjwtҥK>X0ꅇI${р7gݻ'++kYqqqjlllll11I,geeM6O?iru᧛hG./_rCCCGٿ Y|}} !| !D(ҷ>O%ҷiiiLJee%!tKXhQydd$!/oGYhvd3fBU+**!...g. o}<<<!3fx-x8Jl0b$&&._>mq2cۉ'ͥ^P+edd4n8&H,3-TZ"}mhhȴ~V+s:u_ ":ujɒ%ZteČb>S>pBxL&׿yÇ_zݻhF50Ν;G7ve\XXXL2eBP(:;; ;TvΝ;?#Ք؄ T6dΛ7Ћ/oԩ ,P}:DoZ[oߪ5kwBaI)**/ tMZ!ˈB֯_矓h#M:؛>M.555wꫯ @BBBkjjz{{ !<o„ NNNlllb.N$brBahhHg db### +{ٿݻwWZ<|J{D"i6l@#A,YqqqǎDeʕ|,;vTTTؼӟiJ <$Cvٵk??UM YF?~Wٍ\.wҤI/rll,=i8.Ӥ.!v׮]_}~@ >x𠮮REYYYWW-iaa1'ҽF2]T*522z C-!d޽F ظ{8\|3fL[[KURyQWWԠ6/fɓ&MR݁@vvbݽ 311YhÇTav H$oo۷okw>jjj|}8::u|O.CI.TVVFFF===>zp܏?xݺu`` P<عs?W^y̙3Cx뎎_UVV=b10v^oo;wJKKJKKKKKE"QGG!ɓ[2 J ƍ#Zm 0r<6B544ϝ;)4ImjLqqqqq1c<==gg1c<s B_B|||.]J988MmUUUg|Te„ O3Lٲe3ǒd::: Ű6oGzjcgaa(T(--MIIy!!DOOǒWWWWWW>t ]UVVd)//H$3$$$66Y,,,[ ]{{{g͚}ԩߕQ`d5FWfffñ={j:b/^Hdӛ0a N0^c!;RYUUKqq1=-366DpvvGoi;v̙3g(Ihfdƃ !oHC~=PYTRRZUUEW2hZ]ݭmyyy{{;!{ҤIԄ ܡy^ ۸qcXXؐT9Ã-2G*/ɉ''b``0~xb. gY]]]qqjݻ2L__͍~)biVhinMM͛7|]pp3S@P\vYT 8,Yr<++---KKK__@~%ҷ7o촰5kj5M0l_~e}}e˼Jmll֭[E7YFRPP[__/Jnj9{lCCÁ>;Z_򗴴͛7W5Ј AimmMMMr^^^_WE[>{ ݻwE"ѝ;wD"H${.]ZhggG%5J&M¤f_~)+++++2x #]`T奥%%%tږtKTT}agg/3g=zT\\zUv)//700 uuut7VjCCpWWWBHYY";;;$$dP-XrcOOOJJJiiiNN?4!/--\, BX,.//բ,#WXXhffϟ?yt޽{o񆣣c$2b毽O:-S ?/\.DW- Gcd/枞W?뺱O[pppvvӧbB ͞EZ3OLB9sLaa`./27ܼ0萵9sNA/RZZW_UVV*c: 6 ۷ !^^^˗/YlmmuQ̌sp***RRRT |.\ <<<]`Y|}}/]JQkB7%XYYuuu.\а&99Y.DO.[&Knbӧ>}ѣ/ɓ9N{{{uuu^^ޠN Ү!8333SRRI?`+tڴiӦMcRd2ݻwsssKJJ>_~ETZXX0pjӄ ۷o3t&573'S6uԴt" 3fD)))j@HTSSvS`` }\YYYZZJa%L29XsnJJJbRUvvvVVVtRkkk&T2buuu999?@-ѰGTא|!!!mmm)탪tN4iҤI4Ν;ɧOnjj"*Wxyyyyy1`h!,((Ot]bjjf+\PPùyf{{ٳU.111999---zzzAAAY\.wժUم---gر~~~ %Kddd̞=[Kxx8DE;/^$.&"uSˈB,X~ƍGYXX8;;TZT5$@ xwRSS+++===,XPRR2affKSSrssp &0QSp,xq4\3 T#|S$/_'4!`{QIIIAAAQQ۷ :qx{{{{{`: v0t}~_~8::zyyZۛ}uX4ccc bEEEYYYa<<g s.率mYƍ' }|||M/// B1foo_;saRb(..D2ӅBQQQؖ<$$$$$*++c600pvvPaddf x6uuu3A"z-}AWWW>wmMJVVV^|y֭7nr :)?䦣LVVV득r]]]P(bUx~rgă ,eeeR>,\Q-}N߿} w5kߋ˗?oOh(D7333oܸР"in&t\./..NHHxCBBƎkgghѢsq]Kff޽{׬YHo(~\S^^^DDvgffČ?YZZΟ??--ML&ۻwԩSMLLAhhhrrc$ۅB@ BpΝR]6H[:::|x%SʅB!ILLT$11B2b#G|gg>nޣ֪ثk׎7tɒ%L vA}Onлq人?fͲ}?㔔f]`0477_t)...&&ӓC]7/lܸZ8ŋ;::h'NhogΜd>d̞=o֜9s$}m۷~[ā2|݄HJhxkϞ=Zte Ŋ+fyyy_lU4^S-0~AK}/!tҥKj^T*?p5k|}}#7x?~:`$C@ EQQQbbM"""lmmw>|W-566۷/88XZZ]6,,L5򢧧pmۦP( I${ٳ2m4k.k޽===YYY/fڶm!СC3vAk;Z"}~EBBBB`+ x<^ss3Mijjx<Qn؁!MMMb8++kڴi7>%%&jRpĉ?Sgg+W&L@裏/[ a>R믿Op8ljH.뺍@):y /6cǎ;w~x|p hll 7n322藁~p\SSϫ]]ˑ666aCCCGٿ Y|}} !| !D(ҷ]ҘJB@ТHB_|A۷h"2b3f &VTTB\\\{tԩVE/<{,SӄAKJ]`?à CCCBŋoߞݭl 0, ŝ;wnݺJ4iP( n) SN%%%]tkQQQW=Iwܸq]]]tZ=̖j{j vĉ~si@ nll| d4557Iljj222dWnf-s:u_ ":ujɒ%ZteČb1]A nBǓH$,=z𡅅ŠӫZ[[ǎkhhݭynw[NH[n]vڵkYYYUUU|>? (((888((JmW? @ b8///WwܑJIC<O-$J/\wߝ;wN*+Wnjӷ0_l١CŽ?>T[ oٲET{~  =zdbb2$p8Sޯ~K2a'+ήT*999 .6_VmOR``4xӧO>}{BHggk233/R,h$xp7o޼l2///33Yfmݺ5**ѣ' zyy!P*k׮yW|)>>gad;w.!dѢEw8wni^Ϝjtc+ʛ7oB\]]]\\!L&p0:D9p@UUX,)++ۿ``=4^ȾRvgUVѿ 8. .g޸q#gÉϧM6yzzB7mTTT47"cbccUQK$^zߟXzzzf͚7+449cKm2Q9˙i&7|oV)ooo㣚I75LRI8K14PP=@zPR5ǡ ]`tƍ 8[[ۨ(z.sOaU__e˖W_}np&O駟뺍Tuvv&$$+\.xŊΝd;wI&}'LDsN___CCCccPX IDATm۶,###__۷k~!www'0+S~oWKlkko;qDf!@jQ9sNk.vST*/_^t=322ڰaC~~=RIU]]]󎅅񫯾JѮ_>ǺqFPPМ9sq .]]]۶m 5#""mۖsI$#x566po޼{=}}}zސP(d٧U2ҥKG9sT*?+^}Ucc᾵IF,伡a5iҤ&v„ VVVvvv666VVVVVVC2JOOOOOqFoouhhܹs̙CW'hax(ʒ!-ϟ:ujܸ\R*?cBBBrrrgggxxxTT@ ]@C#0By~FD}}}T*RB\.5//Ou?ӕ+W]0gΜsΝ; .+..f,mmmӦMc,8!eeeǎ_j][ hhd] D"IMMlٸq6'$/_ncc3k֬yCB5},zzz!!!oMHH())g .v!|oѣG-}SFXO?Oׯ_ds}饗BCC<Ta5zqV&  MLLtLAN:w]rE DGGAAAzzznFZEÇMMMN[o!@  ##>>^j~ԺrPpU+ILLp8BPn!ȑ#|>>joog@}gL.]p 7Y|@ӡ7nݺd?LP(LHHD7oڪæ + K8pbǎ߷LjjjhK.]t]v~s6mdaaajj~ak&~իW?2nܸ7xرc---n ]`򢢢:&&ӓ^ضm[FFF[[XUUզM<== !'O޴iSii I${ٳ2m4k.B޽{kkk{zz/^̞m6BC}AK >}yeJy ||| @ٳļ<SSS 9@r~۷oΝ;wȑFFF o߮ rqqQSS 裇/?>zheee||˹g޽4mݺu^jmmhMMM)nd8g3ʭKKK߽{WPP --='YqP( Jbo`Etذa222D"eĈ}oW㳱:t !xd2ޡЯ|||rrr^|wOOϱcZZZ:uӧOx HlR!iii;;tUUճg~>99yϞ=͓;^0TVVM>=11qϟ?~rOa>?6.0nff7&E$Ȩkq[[VsD\XɌ\knF)S`˧Nʸfx0i4W"-~cƌA%&&bKΟ?RPP.j___iiiqqM6UTTNf %;;{ӦM%ɓ}}}a.0niԨT*HH O*BHYYݻwx6.d2900PEEEHHh~~~D봷 ߸q"tAHDDD]]ߟDyyy???2XɌׯ_~رD"y1Cu*굙L0f"(""]RRSlݶtٲe!z=T*/v455=zTAAH$:;;?}v jkky󦛛СCBx5xLF0gggcݿ Fw`P...uuuuvvFg2pv&#utt<j۶m܉J%$$\tϓ'O^x+A $IBBb̙dR h4ZVVVdd˗[[[---]]]+@4\. M#---66ڵk---&&&˗/_p!́? Vii)gyF?~ ߣG*++̜,X ))w\.vKKK HMM1cƶm.\B %333**ի...˖-2dޡ dv9'''77QDD˳HHH#/\L|n.qC˗/+++oݺuŊYiiiׯ_377_xEm o߾effbGdٳgc l\_GGǵkעRSS3sXXUUUYYYv---ݑ#G!y*&&/`0_|w̊+a5΂ gϞao斖ӧcypFFFWUUD? Kspp :88رcd2J:ymLLLdddEE۲eˠHhoowj)**ƒ,3g΄7r7kDDDTTǍ8av}WW/^ VWW?&))ILLСCjjj,Y{nbbb,,,\\\龀 p)lg,RRR///S/JJJ>|…qnnnD"FT*5###$$ʕ+/ xť ?KGGGaa!6PKaaauuСCͱ1q555NrŊNNN0`@"FP ))Y]] ?77 .w[jffvqsT۷o#""Fe˖5kֈ\ӧNnoowppX~AqH'eggcO455IKKbUUUHGCBB )E<˭^x텢kHHHRSS-,,-'111-Çyzz/=ikkqFPPPnn \\\[@D"䤥<|l|xO>}…>L>}ڵK,;.~nKYfoD!TluuuǏOJJ8:88رcd2gxJKK;|prrq6o!H{#2,//oll[ZZZ.]1lذUV9;;_HH'BU#G~~/ri,Xp9 jjj,Y{neee{9s {رx'HZ[[ 矬Ç͞={G9!!!񍍍...0%0#HӦMC{{;6סCm*((~h♙3f7-..?Xlٶm&OwPpӧO߱c.LccݻwZJJJ(ɓGZ Icã?f͚+V3qttLOOrڵk_zE&B:::x6xh1c|H$s]x1//z~ ++;w܋D"EGG9r˗s?ᘻvΨTjIIIFFFFFFvvvSSӈ#MMMUUU6WFEE 8999;;ÃZ[޶m[ffAKK{\\|EE@;Am֭IIIjjjRRRxG7(h܋Fݾ}?++]!TÇZZZϟM c^ϟ?,]TRRJII?~``-[ CBB~@=~?Uի3g\2((,r|cc;w\௿y󦎎Ύ;C.F=xK6666 f F{{7SRR$$$/_榭w\ەikk[[[wɈJQܠqu{{0777޽T]]ݝ;wZYY $`[II/]sN `)++KMMMKKʪj4i9/^EEEԘ-\PDDhmmΆsرȑ#9990..>};vح[ ѣGץKN ]ǏoݺǗ IDATaaalX\sssMMM~~~3jjjBCCuqq5jq^rA>'/Rϟ?/**>|8 RAAAgΜ5j͛׮]+,,wPpl??t##9d;]Vmmmzz:QiijЀP` h!!!|||+Vpvv600wsM6%''|ٳC ٲe(Arrr|||߯wDipΝܒ>>3gbx"##_~~%KHIIIYYY~~~۷o;ǏoذX_rH@@޽{]]]yz HxI]]]JII͙3{AkkkBBBtttFFʕ+UUU SQQ;d2Ǐ݃GssshhCVZ\رc fffxG&H]{{{VVjk={6LBG>ex0k׮ चmmm55ׯ⁧ŋXt] @O^~aÆk߿Ȑvpb,Ւܬhaaammmbb… !!!Ŋ֭[bŘ1c 4ݛ={֭[pÇ nݺgcacX}vٲe;vgIbbٳqF޺ iMJJJJJήw`u5dggajj wW--- xbvuuMHHXhޱnPԛ7oݻ߳g@ZZZ80eʔSN͜9Xiڲ[>|ӳw`{{; Ƈ?OGGImmmaaဟhϟ/((uԍ7.]TRRp977+W ZZZZZZW^%xzw7opt'ww۷ڵH$Q h4ZIIɭ[n߾]XXH&UUU-,,,,,y@8ÇO%&?O<044;󍍍mۆw,%?>tЅ NcHITTԆ Ǝw8݃ hhhHMM}[|"%%5gΜsΙ3ƪVssɉ6mڴd^o߾iiiM4͛.`sI//$KKKcz]@@@xxI~7'''F宮~~~;vmvp J^#z!'O8q"..oxP(+--a`W\ޱŋN2e׮] t СCw644;.qFRRݻwGC=ޢf͚+W9cǎ< c Hfjkk˃W}yNiiϝ;7q]v-Z o< ]"JLLLJJ*))hYMW<<(((>>_ K.vrr;>hii]pX;~zǏl߾]@@ MMMk׮x{n.y#.pΝĔCZ[[ؘ >_GGǵkBBB׮]*++w\gϞ꺻 _ffE``͛ƒ/7o^nAN}:m2+++;s̹s}foo3g5*-- Ç}||RRRLLL:88ȑ#Cݲe $_ܿ!11qʔ)i@RRRqKKK͛ 9cLzjHHHFFºu\\\Fw\J=|V\tq o߾8qѣk׮;(pVWWx₂;;;# yիWZ[[zR[[q̙oߚ]NPPl޽tttpVCCC+""w8jjj?<<<Ο?a€ qnݺUTTD$g͚eiiiee5aܿԩS/^qwwPQQ;(`ӧOYX7*//ֶ;X%((HBB_8s̼i֌ l6O>; fׯ_yxx,Xoŋ:::˗/?s ޱjiiyĉu cǎ z{{oڴ /`0zy.\H0~իakDǏ;4Ǐccci4UUU ^444 ><##H$j߳gOzzޱNjll\JJ|СxG舍=vXqq)S-[6dw4mѢE09F[tivvvQQј1cp=" 믿zyy8(,,7o̙3]֟H0566&'''&&޾}۷o ,Ӄq`ԩS5556m244;(~@@@]233&]]!Cܽ{^K@@??7HΝݟH0߼ywihhԴ1c.H+h4ZRRұc222FqF d++Gzyy %_9sSpp0ޱ),, eÆ |Pqq+W%\H0p|ƍW^{.@011VPP;4]MMMhhhXX؛7oa\JKK7nۇZ XO%_nԩx/>vصkW\~ &QYY9~;vx8qX@ill<~Ç~׍7?իWLAcK\\ ANNNC 5kѣG{b:88A""##M ijjSػu5󶱱#F lnn4KH$'644?s& tpJ~~ѣG^FR-,,?nccʣ[lG#GDFFvtt8;;N>z{`ҥK9R\W߶mۖ-[Ν;{//ng;p^vӝ_3^>>++3gШfrrr5Ë~^p);FZZZPPЭ[ƌc777k;~Zc* :;~ ^u!!!''8qbƍ/iideeϟ??gΜ1. Tr E/odSիMMM?ngg7|pl*КUax3rԩss/DV>>:5|`MLL|}}nݪm``!2 T\yذaxo[mss݇O6l`gg4k,#?׀LCxdɓ'8oQ(4(koQ̱dWe*#.>`$6,9X cǎgʕ<`͐v;P~ȹbbC%>,ɑ;`qqq9p|EeE\ #FH,\v83BtE3KceQB N=LTt2??ll{BBk`]$%GByFuر7oL:X>i |n&L*),x' gh4HK#>,9iĈΝrJtt4]z=JqpMMO< ~M_AOeD{J|GUΖM+Kc^D'Xm;v8PY}ٵJT2VmwZl2Q O?8"qe' 1yc$BkeN%gr|0hl#& n.DщFݩVݮZZJXJ)XyPV9{!­R<{$=}eYY)>:ΓG3/33eHIPe‚i(;n׵k-k͚())!9InkZgD=픕>aLmqbkׄ-~CLVI'2ed)/!woޏ Xlx]ݐjgfSJn=U|*Q= z:1L/]n62"I\dd9~ ((tD?],ք1f͞iI 7WE,:`r`%JC[:Zn^{B^^enc^!]q$~&LJW+9v&& /-ljdK%;l,&&4yIƜ N+yXBMm } e'L]*I_M50yS=LϼV:0Y)Fzl x :u ?ԧSl1bTˋzΑ^#9玟mCÇZ5Fj`6{w|Dg1KQ3:}x죯 2Z3{tEi**܆z몜] BHRRt 3׬ #Ъ5::`!3dSOlm`R~:zʌi==eP^؍0RV!C-kB#GvWk9b0ϔիWgffq !Hn݈G*[ <7 7#C%״L1eűVi9zfC[ß9#Ǚ6ܭ'Jsՙg~(B#UI ?JJ읽wθ9+5VbW'ګk5**3M_w0 k‘@~y?8Ou><K8vJ|D3=v9s릯;|KG BH_pE'Y?8;<0fEypճ g~}[W5sxЍ_56J#M&# &(׵],xK[{床F̷*gb)SŅBjS(ja;lAw DDB2y%"*ieo=NNNr,bq+&CXXZZZǎ|0K><<9cEu/ vFJb '.D(KӗvJ<-!7go‹bƢ[ӛڛ4}ٛ!4Yzr_\ZsʿI}kk>̊ZNҴejBlf-D&=|7~c%fVdBeItҟ9<xhe1rIXL{4E:J9Iq!ƻg,:֎拱!9> $rw]],O dA3G?pJveS{! ANKwXi>tc_KHNuŖ89mJiJ_6?^rBBHCS+bU4!GΝ,&&BFFB!!EEeMMvʻw_Cd"whlh}#??xl|O|ll$#bZŭ|)S߿۶mϞq`)xڥ*' BB)+;wnkQFYԲT?^jy=+bb?ZI'^^2gn?>I4B~=BҋKX)Ggr|4mDP},B7fCB{5&m IDATKD$c?`#t/#%B}`\=BHVVG|sMM#BHXO,2z^m5ާ0T HđY.&ҿz:u[CgzLzEG]N1իJ*22gc51Wn>:Fn(B#WAz8+QPS!Fw 54 !Z[:+F[x6o| "O ]z/D8|hIm6Qa4D# ݮFic܊#{O/O#M0D@ LVG˨}KHX &Mî tu|G 4sejڦk @@D::([&;et Ҡ0GNKQz3M+K{YRUZMíyύ#<׮۪ƍ,3n܈)SL[\7L&vusڔdF@2MMmO`16*+ c@NnB?㊑k9섏ӧO?~wTLXɱy˰ 9JKB,nˤ !dh¸LьB[79 ڣhvMWo 'c\}V}Z@b xBUU߱Juh*+BvݖIQUwTu9ƅ B] iTVh.&׃7h;nާ0U3F!blH?^O'5֬!0GGbO8Q![#bRVI~rTUd1OBfUXDE&S{SUB+|Gؠ/ $8د^}'&; ݻw׮]ee}SuI K blu W^^A22EVLV_p촐!̋m{aÆ spB~KMlQLMLh6|]eK!vث78? Pe '5n+Wzf"23&98F_ O>yU {e˧O9?Ê j#VSWW j6W;;OT'Tq2K=yQ@e1}IDΊ^go0wp]ާ<~gGKӗN#6Ǐm=5ӉuT5(Ȝ#G$21.V_ꚓo?F,tU*dU&t~ȴ -B(7終do*tUQӧ1c">}{_QC/br+㮮nr: j׼r~u5?w 9+ج1 <C//koxO d*/k^QZ:Z >yQ;}yXLzY`AyV=wuT/G lFb|j3_^#39>,ړӭ*+T2Lz]:$叶Ԕ}j )Ս7?f\Bs)Ǐ6W_NKe^D&S|}cKN?cȈ/ rh؂/jj(*04F bOْ%y--eK,'ճp6zj~ޓjnozǀw \"Ml^%dB65޽=^?|vqQY}] Bh|\tǖ!/ rVQ.]jӓioVoV NKMy\6cƌ 6l޼=*t77c@'}Ot!3E}zޕy]=jĭ8 7>_ʼn%K~q-:3`cq_[ŋDE48?Ch길>K. bcsxp !rx T@@@pp'OvV=W;`ŀLC~ptp꤅S7oޜ.[Qy϶mx@ =z433͛?-<2 d.@?{O.'$.l8=2CvƉy'RϱQA%I}MdNGs9Sq*xK~~~l[TZs|'@; ԣ,v)*V2{=dJ˅s6EEŕ+W.[LHHŭv%>Zٺ/5(VZ}NGEEE!!I&EDD0fu]&@g$>>@ hjjL&G !cll,%%%$$u߿L@۷WUU/4űmµkװv nގe^D"f͚յĤqWwwN<>|!dccX5Bȑ#l4R]Ծ'S8QQwV x!}?oip xG<<<ƏO&YYv}v}}}{{ǏK +=vXJJxPPЇH$R^^GfeeIIIׯaaaC }jkk+((g^M[y樨o߾}-22ۛ^{h `л z7 <˖-e^_C{tڻ cfffɤD"u-266nkkjt 1v>X72^)2uݖ1SLO:q9+d`h4//E@=ލy@`l~LlذAEE{'w``W o TWW366q"!!ԃbE"""wܡ?liiJ$BBBhѣGׯ_?vX"=WWN?`Xi&#&G !tر41cD555oo.wcnxV\͛<&hltRtőŋB  #@#qqqK,a-M2=ЯO K.tR{{{O+@555=iv())M6ի=i6-XT*RHU@ ή޽{ݖB Uxܝ;w- XE[\v "`aam)]gjjZXXܵ.@_8;;Diii drPPqbbbEj}BW o>EEE!!I&EDD0&7\~rMMMXI||<@dLB(&&XJJJHHHYYy֭߿9[[[F) bU؆---k׮>|}yy9}i?qKvttuSFcK\\w#pu^ µkװv n`&E$i֬Y]LLLm0~uwwTO|||O39?ZÇB666X[[#9F31*صHMM[hѢ [pBjjj~]vuW\ !y\Ue\6EpA\P\H,){3Ld3ըMj-&i[n j("\3ݹ!!9Y._hxuUMD|W8]g}v߾}/))Ζ >Hںjժh>zGbbbF]+W?~oy͛7,--=rʚ&}RSS/^\)ݻ7??_:*!!رcGԩSvvҥKkt^u;,+PV_vNƯ[[[KTLMM^^^Bk>*6yzz !֯_=n:!Ԍddd!ZlGOB-"Xf044T1rڝf1???!ǵ/]$ѣGgO?PwڥaǎB={*kuM;f 筚Wh"峽V\ٺu vrGmݺuСw~zYYfѣG.\BH32*x&z i.kz. мݥK!D~~~MR?B͛7K7AQӬ#" R(FFFFFFݺuBh/RP>h !DfffΫ»MݻwWڨ]E9s?HV4̻ E(EEEijjZYꃏ1M6.]kӦ7RT*Uyy&iBCTw (///CC3gTvr&nҵ'T@>|X{ggg!Ν;n={4B؈Ͽ8qDD{N=R*SLBl޼Y2uTR)mij#&Se?B]⼴H-:uꔞ^a:qppB,[ݻ={[f͵k׊ƌS&N:~{۷ooذa޼yMBWWWiWݻ_)kN0~I}w}кHT4U_*9ru._\TTT\\|…߿FW^y%22رcBZ˦M@G.^XqvKŰPXR;-[VgiӴȖ 2Uoz!...\a2к&^?+ j ڛn-.qww=<<ǫs`W^yM5:#jJj|4o ]GhK 筚Wh"峽^y啀 vN͛'q…aaa;۷O>455 @=+,,TT.B[[[AjBiRUNNBh߾ @]iv/v֭ @]?ٹ @]QԿ^vGƩ]$%%EVWv'Onݺk׮Ʃ]$11W^Ʃ]ޏSރΞ=ۧO7Qމ'˃DP{Ǐwqqi߾㛨]j t @-=x !!ܱcԩS' @ l޼{.եV?쳩Sw_P]W v={lٲ3Z {{cVsj'{u̙PT!j'۲eKQQٳj'Pա&Lh۶mŒOO?5z]R^^kĉ...5z"]k׮s΅vCjzѢEƍٳgMl?gϞ3glڴe @j FV3r;}tXXXl-Z($$ۻvOg @%W#0_}~~~>]*ZvmVVrfN^^ނ ^}.]8.xb##w}"#tʕ+/_޲e:.㥗^_[nݹsBQ ]BVC 2@!oW_d +2{}1]Ă T*Ւ%K\dӧO]ںlЬK3f̨#3o !_TTR˅BG !ڷocdd$ol4֭KJJJLL400#Sq"##SRR켑ј1c\ u;3w\wwz?8l#@C B*OJ8qb-JUVV&w ԛb#@6733[p.^.۶mБ/ޒ; 66^&-(!!aܹaaaUimm=hР?ǷHV/[l̙ڵ;<'K7w\@ׯ[lw[$ мQcr ?aƍ&&&N{Q*AAA7o@غuk]~|-?1фT^ZZڻwAj?С?/YA%%%3&;;JRR3fѝ;w%![fܹs /^А5^{B:t(88XDhh ]]]u.]YnnnǎjTTqϘ1㧟~agiiy\GG#FL0! ׮d///!ȑ#7nhii)w(T˗G}YJP(x7xCwE-_.++300ҥ܉UVI?JU*ԩS7l m-//ٳgώ;.\ώ1bذar4j://OT*۶mcǎ}ʝ Ou3fJb#w(4R??? HNUc>oU*BPd```k8pJT3F000?>ٳggΜ0rH[[iӦ?|PF`/l%%%7o \vܹPٳgO<ѣGRT*~isNOO_n/d޺pႳ[JJ@F999ǏONN}i{fffڵ+<<<660a¨QZj`iMg}oj.V:uڵkbҾH{ȑ#AAArBKOOݻCEo-Z?,--Ռn h;jԨjcǎ;vĘ>!!!#Fh߾Ns^zUT(;w֭,PO?u޽{!!!|Zй! "K0bϞ=W^vmt.E_mٲETVrʩSdH{ 7x###c۷ Yvmnn -((ȨMj:&&Ν;  n߾}?ZRR2x9]~}̙=r*//~zqR9wO?TTnݺo߾={۷W^#F ;!ڱcxAAA-JV'Nx=zȒEQ@c/ݻСC]>|p___Vn#Gܷo߰aܽׯOMMСYРy+VDDDhf5>>rT͛O~Q#wy/<{Yp+_] [n;v,""b׮]ׯ_ڵСC4~G>w\RRY'OKrgAùqㆿ>lnn.wj}Gz$::Z )99"((hذa lذY*A]hB2440`@?|7΢JKK,,,"## }W/_˝r.zP .>|ǎs玵uPPPpp!CN>Xdə3gz!w}t҅ >}Y,hRdɒ~[,EQ)##Cswkn,o߾G500;HKKݻ… oG/~w⋗^zI,Uvs.RTIIIR]TT(]4dȐ֭[}ZjΜ9rg**00PRƲnNsx{/={YEQꢠ 22R)%%ظC 366; 4Uo_~ҩS'+VHLLٳYs+W7oާ~M,OF]͛G=|#G.]daaP(MIqqq^t/ȝɻx񢷷{=@?pժUsΕ;KP9j.ܺu+...::Zi޽T_lll```mSQ^^>`⸸8fΜ?5J8E]vƍǏGDDDEE8PTsnٲ%--F,MUhhyN<%wPQQѤI;??Srǩj=GhHQQQ|Ex{ehh(w@h\>|ٻwp4I.]7oޢE?~|llΝ;S3.z ZzѣGFFF޽{C0`Aܸg*H9>n841jzȐ!o>u+XNNï_gvȮ<99رcǎ;qݻw۶m8hРzyyqO܌3ߟںuk4%k׮;wn|||^]IHH ݳgqjEQhB$333i9ހAYZZZ^^[pp d\r/,w޽{'O<`m۶hB8D]YNNt;Ceff* ///i9`kkk@ٽ{ȑ#7l04jzСׯ_OHHyjs;v 2v4iiiǏ511ӧϠA@M81>>ٳ-[;Kc~YfEGGɝOR_b?E5] ):622rww0`@@@ۮ'N ;K /|rgAuָq۱cP9j@Sw訨SNGZo߾ܽؼyӏ=:p@4^#FpBRRYP.^8jԨ{ر_~rǩ.zO  &&&ݻR3pVZɝdΝKJJ233;KcwM6ĉrgA=駟fΜٵkם;wvI8EQJJOO*茌 ###ggg rpp;#XNNٳ.]*wFSLYzYP^zM6˖-k >EQ HRsrGGGHM}A>W_}/G,˨QRSSH\tiĉ|͘1cS] *((*BЄ꧟~ƍNƶm&O|ᠠ l߾}̙]t wrr;NP9j@3WVVvyr0 ;#T[orgin޼6nܸ/R,oիg͚gʝHW] **55ĉ111'Nzi>}[S"/cNBBY7eʔKKKL<911?6mqtw+iTU㷦С4W@c„ ?)Є?><<\MITTԔ)SJm|||䎣sLJn9TSllʕ+N}!!!!!!Bظ?-[߿yJ~ɝMҊ+Д_OBBB֯_Lv9t s&^rG=ԪUaÆ 6Lz -|G" g Ԏc>%rhO~޽-[L0A8 &”&''GEE%$$/\Ys9RϞ= Ou BY/Im;XչW]30xzw~iNW=x PݻOG憆MWII駟>3֭;Qvi*|l&tzC x"ccci077ԩS\_e˖Rӿm=qOOyؽM6[8ZIFx{ֳF̙3ӦMtҚ5kf͚e`` wFP:tЬSVVvyiQݻw/[LVwA`|||T* ɹp*̅tvZG󎄣G !9zϸ}N_}.MŚW밐J`__dGGGɃ ٳyyy'O.GZp{,,,5+tU_zo{;:)qIFIʯ_N?H$mڴB>}{u_uNZhoP4y6Y2-**ʺ}pJj5AO 3)g\zBa<Խ_7[VB7~8RJ ×f}y ܪ[OYKwR4Zj,(..NLLWZe``ׯ_????ccc#rq =sѯ_7g'Үk¤e }vhaҬoj7|okرc;̚o-,L\]{|MD,!+=fv aUlR(0'5D5yz``mg (~TzTfWg]yԂI55sEi~..؟RWx!L99/NCݤ\1>/~HKK~,%%ԩSN:y?PZZڲe޽{׷[nrGܙ)k !M*\+*HucUpzqqiN !??w{i5ͷv9x ePglDsG7.C祑Kn~Q&~݄[L @IDATL~_ןHK&(**aKwt{UW@R(zի׬Y**===!!!!!!..?/**jѢf.k3&D>&%j}㏯ZIIIXX؟g~fikKf=`ll$!QTdcJ|w$]BZ4ŋ5o߹/05zO<]3226mH ꫯ---=<<4-ܩ迢-L-,L<( ha"(*,.^iO +W{5k֒%KZ5eDH/y,wLLOowV a` 4t++RN_ϺURHR SZZz^fJܣFЍy-:8־UEǎ7n<.^i>VQhѢΝ;=ztРAr'j  [ !xƍ<KO;_/Vƍ;uT^^މ',XСCѣGم,X`wܑ;2=+C{Pzx|sx"zz:p諭sGJo8˻w'i$*^k՗c'ނG_U 4-[J7Hfgg'|2|SSSww޽{Kzzzɛ@#!?nI~c'ooh<߿/pqq9p@pp܉fZ:xVA.XܻWsq3ڿT埭8ϿMr˷8[2OuƦP7.E:[VV.lJ' h2/({jQ@%ȨZx^u98MHǎ;v8zhafffBBBbbӧ7obeU4};g<!^s@JVRRbŊe˖)Й3g*ʹLzG۶m4iҋ3@ll?Z<]͚KII#)==Us={ &(;yMRaљpPxxロϟ?(VZi_TPP\]]cbb"ol\x#GL0a޽ݻj}R XhkѢv }ꄄuC3wެF%z|~z''} 6LDM {jfB'$$Ha-Zt]###gggi"O۶m+wp@}qŋvڭYe\*EPH-4--Ӊ+((^˫M6KJ` 6ۯ]vʔ)r.xccc/////^\f͚ !DV5azeaa!gn@5ܼy> \z?T*?*]^TPM fee%''9saaa*JTy˫]v*U/AO}Ludɒ5k֘Gfb zWIۧkPM'@E;wܹ)++rt$i:LffZݢE yBG)/$wZzu-[7wܷz EuP ٳg5޼ysaaCm.gϞf=wΝYf͟?VP:gee}jJ%U0iii-C t_/޺ukv}ݙ3glRP Ȩt3g$%%Һ0&(>>?_ɓ'hnv5z ,)--mƍΝSJ{aܺvj`` crhv޽t~ÇZFƨ͒nܸ"-r!:t.ѳgOKJ?\2***00pÇ `Ƚ{RSSez7n!]]]_޽{֭[fMvvQլQI^WIHHزeK~~C%t"66vŊ;w|̙-jNjIKKKHH {Bԩ###@nٲeժU _ILLL΅vޒ\TRR.}O>UV={tqqqpp79w\Ϟ=,Y2mڴmʝ UvGP8:::::j]-~FLFFΝ;5wMҴ0ҿ]ta?UVVcǎk9r_ wss;x k&edd]633SVtMH 7NP;999ׯo222|||֬Y3uTKKKs]IΝ;w\ZZZzz_~)͈177wvvqvvvrrrvv655/;.??˖-k׮MHHh߾3fΜ$w. P1C ьܽ{W*bSSS²jaN899q$j:::zƍC ټyرc1פQ:Ѻu뀀͈^oFFtiRZZڞ={222b:t _| '))i?cNNN@@e&L`mm-w.jhK4ELttu oKjw1o߾}ƍQQQڵ2eʴiz-w.'j@NVVVM'Lb6m$-٫P(:u]<h_uXssS.^ߟ%jёVys.\p…}}eeeB{{{''=zѣ{NNNݺu311);ڵk۶mRaaaFb"F4۷o߾A4#%%%]ݻsssҪݻwk׮JR3)ܖ8331cƄs`3A4IJE{իZ"##/\ ͋y|gg-Zt>qݻw)m:t4JhhP.066~|ǻ={,]ѣG.F{={t@Ӗk׮]vǛW- *b G^vMV8:::99u]jo.]F Tqq8pM{{q-[_~ t7w|͑`QQvs1;;[Rvر[n׶m[Nh .^hooΝN:MNOxG lU ӝÂ*G?G? /a-5Xrגy$:n;[!&oZ77NXN|i5){^TéU~9,HQ;ǝgww{;_p’ǯD?6ԣ| oD#O/Â}z_g~ԿZKS0k:c*'UE9+_*v%\cѿIUuQ>}꧄ϵaw/\ގ-~33BW2r<r@Q*+K9^,Uv^SNfaÙgSdso鬭 !#7w~߾#b:cJ%PPzWU%= !?x!#g7J"P]2'訨'Pae5L\Y $Ý9t.1(@w@i3=BFX GWG=? `6_๺YZ v2IO'_m9n帱S6 سj -X ON\x=: inGWGgBg!#AQѠ'x{5%5_~5zYuXeGԲUYZ, TGm?<Ĉ3kS_ө7AaJ4 ~0 -<[c4Dp!#W_7gG)+$p F<{Oqme?%=.oD??@U%1% wK~,yoo _|J+쓄kIkg!}zd4p^O#B 3=B!d0#B 3=B!d0#B 9Q77Cwb$ z:J5XC!2E#Ç(#zdRBp͚58Vh>=B )B#G!fzd֭[r.]͜@&ݻwΜ9</<<ٳVIҝ;w ABBB/6EQjOW)$/r;<<<,--Oʜ9emܸ@ (J(2 E d1HKK wppܺuk{{{ D(@zz(ԩSV*.]Ϗ KD" SZ`T*e.Ψ|U um{(f#C,#P(bccի|||۵XYzz:Ch𽅌h#""ڤRiCCCbb"ݻw޽{%I~~+ث!%%k.fzwwg϶߾};::ϟ,샣U377r'%\.655&ˈ߿\]]BassX, {Ofz4𽅌ŋQQQ...]jeeEj 99Y}F*___HMMe8pU 3}vv6]"ήYXxTT۷LJJhds@NN|&hH{  :ӫӗ_~߉m25]X,UVVV,ljjkkkfnDP9MǏ@pp0y'N-Lxp8^\&hHyD)M6UTT9ErޥKx{{ӧes1 322`P[[KOQlGҳL&aYXX] 66‚j&ˈkoP!=bа-{zz|* I?}%%%544H$rKTСCͩTm…}펎 wwwcOBggg8r8|mJ|>U`kjjbD"HNN=>TY[[ܹDJJʴiӸ\#pLmmm7o4iL@wޭR5`RxU\]]\ϻ[RR4 ѐ' #aOAFp^B3!1LB3??6&80#4ZaFAi|IB\\\,!B!cttvvZZZN81$$%P(}]ii)yLkHH;K\.ZZZ788f!w]^Br۶m>ttt gNGMcbb{͛7BkC,#ōcƌ1cFXX<*XZp{aaG"hڵӦM\~᪪*zڸ8*L_0^?_΂:s?y򤅅y$4.Ο?_ZZ@ҡaR<~xYY]ʗ_~R,@U[ 3=2]|>?((ݝvuuݺu+++ʕ+$%VUUYXX,^xƌ<ݻyyyUVVVoYYY555ת>D",///,,$(..czi/;;J,Nj⪪*C,#v2{{ɓ'ssݹs'''gɒ%=gi Z"dp )((8yX,V(޽{ 1{lRNRE"""Cpԩ2m3+N 卍 Ymll|~EE͛7{1rilllteĊ`Փ&Mbbb>s'Kk0j$|!L yj;{߼yرcfgA TN^*3a{zzmD+**JKKI'چ2b$ce9KkVK-B˗/+y3\*رcMQ01z*͙s{{{xVRY__ȁ@|3rm|fl[fzdl4| Xkk+,YdZ$1'prrjll,//jXu޽J@@VVV*ƦёiƠP(sY3k,r9R___R;Mdb1gg;wꫮW ס({{{H$n:s sΟ?wuttdGW ֲJJJbX,qFVV]dӧOV֭[ꇦ/--U9t2bÇz{{e2YKKKqqKkVM跷#5gΜ윜R"JJJ Ν[YY)233333TWVV՝8qY>y`HHH$*/////'%/4uƍcǎ%:A^O8qsSS?~„ t&a2ݻw YDڀU-B# fzd͛GQTQQQ{{XX3%p8uΛ7/(((++kii(jر~~~[+W涶Ξ=;,,LLr+++>|8D'ku7IL,#˖-6m___eff驲PMZpjBE( Oj"4Qc 5Pf!B!c!2fB!c!2fx=BO_ Dr…*?O!Lhg|a6?|_Aȝ}׭[7uTrQ2qBHUgg'xzzB ?ttvvZZZN81$$ӓ@PGܿ_TgT~<חy>۶m-..~𡣣chh(}y26un?oll9s&͛7BkC,#ōcƌ1cFXXug'|\1裏o޼ݽm6?\nhhhpppwwwfffeeٜ9s-Z!аLLE"ڵkɃrÇijkkkkkXd2_|Q[[KW566666ׯN,Μ9C%'O)\-ϟ/--efR97چ2bJ!:ă*++_~eKKKe2Ǐ,駟'++1''<#~~~Z- !aGsܮ[neee]rdª* ŋϘ1ݽ{7//*""<ͽ"++`ZgH$充$ jpb1ybX,p8Quچ2bׯ_/++𰰰`.\vډ'%=fz4lt\voۧ~{n{Z dooonnvZ*oocccccAWieŊޖcƌ!466+|mll|\.y&)r9Ϸd1rի}||x<uww'/sw5;]|J[ZZ> HTJș#crܼyرcɶ߫>L:l'MD===6[*6L#_RSS?mmm}\/㯗賜~¯m%bXe!3D]|YTΛ7o̘1RtǎðhJR99s.vqoooW__ڪT*y<9> z=Z[[7NRC 3=26nC[[[`ɒ%"9ScccyyٳUew^ee%iNNN䥍Mggg[[#)0:&AP|Ù5k /}E&a2;w^}UWWW 6Gyzd //["ܺu̙3 H8w}!=^E.X*))bƍ RdӧOV֭["KKKU݃fa29;pⶶ^LR\\>;6G>=2Qs!%`ܹ"(333339/KUpppeee]]݉''O&DrR@Ks^^^7n8v]u㺙8qsSS?> 2bw-,,d !SyQUTTngggݺu%%%---fffc277 ZZZ(;v-|>ʕg !GDDpʇ)m~ݍi&ˈe˦Mwuu9::zzz,TִB Cq(*==: E(YhyzBȘaG!fzBȘaG!fzEQ!]J 3=B7&?|k8OC#B 3=2]yyy֭sssrNNNK.fN ;g[[[~RΝ;x !!>yY.رrѮ)ُk۸@ (J(2 E6LpKKKOOϭ[B5HOOvE:uJҥKaH$aaaU ,J/J;Bγ|m|Ϟ=l$22ue EllzO{{Vin :,A2}jkkJ Hjw {H$+V`ڵf\~۷oGGG}pj޽{\.rܦ&d1:WWWP,*"mC3=RBFBLŨ(s.M"~~~>#Ky4jjj* j鳳HvvvBƣ`߾}eRRDGG&ˈ͝;rrrHu[L3=^&X[[مUReeeBWkkkfnDP7:4~q&/ĉ2b<8133333 *"mC3=RxE2QqqqJrӦM$%tvvϢIRQ2tww9%I̹H''HT]]]XX|r~\. BGy}X"4aGFlC^^^$%\t97>}Z}^*///8w0## Ə{${d2ڵk ----- bcc-,,H&a2\v}e؅"d0:8a===>hiiimm $> 쒒$IAA\xСT`\pB}vGGGFFϏJ39ryѶqRYTT^^^|>*Mp0SRRe555bX"TTT$''hu[hH{  m3]T_ RErR%HBCCի{zzH*=3 ;:4N̜9Ϛ5YIRܲez`"$:0ӣ!-d$@L/|)SoNJ VVV666gΜJ"Ǔ*kkk__ߝ;wHII6muww߾};}΀m͓&Mrɋ, |*U`*ʋ/ZՕZ[[%%%Ea NѐxR cbb & ׬Y[c4D<BȘaG!fzBȘ2~1B$ɾ曛7ovtt(!d0#4Za"+W fzA ̯ eee3x{{s8Cwမ!dB`zB#fzdꊊjjj:;;---'NBK(bs IDAT<5$$J.---LAy]ܶm[nnnqqÇCCC0QoS?ƘypMPk&ˈ@iiiqqqcccoo1cf̘|DM}G7o޶m{ŚO֬,HdnnOyfzdeĊ`>>><< ՘ާ˗{yyLS`rc˖- ܧGFJ%~cǎ!TN^*L4<GFtk߿_7}ZZ0YF|JMM"ᡯ42b}*}CȀ0#ueR8o޼1cƘK;v â)~RB?FO9s.vqoooW__ڪT*y<9P? z\.gzMՁLkmm%KxD"NNNgV޽{.$U䥍Mggg[[#)0:& Oq8Yfɐҗi&ˈ9;;߹sW_uuudukJ/uuuqG&򺻻%ɭ[Μ9Üw2ѣU䂵X,oܸA ULioo马<}!knݺEq/rT=h&ˈ...nkkd---תú5ANLdffJҚsiBC鑉3gNvvvNNNNN)%%%sέDyY+++T.""VVV>|pSںםr&a2,[lڴi}}}}WWB5CSzY<oƍ9gƌ˖- IMF8O]B5kpkG!fzBȘaG!fzBȘaGQ5t3BB0#4Za*Qpu O3Ϸ7cOB3t[͍:99-]4;;9L&ۻw9slmmy<^xxٳgJ;w< <̫RH^;vxxxXZZN><9%cmE Bf#B(fsچ2b`iiuV[ɒƻl2~xS؛"3W_}uܸqvvv+Wd>FøT@h)2 ,*(:uJK.#R%Hԫ,X J3*/_|EvBag8k={ **Hdd$$&&&ˈ)X*v~im"%O=s77RY,5==] 3}DDDVVV[[T*mhhHLL@R{nݻwo}}D"_b{U||<888455555;;.D`gg,,th<** G^&%%@ttnaܹs ''YX]] |>='Nh̙3'Oӧkʢ-d$L_~egR@vaUTYYY@ss3<ښ.Q(}N_lL^1ӛapr#zM\<_iʢW!T*7mTQQAr^gg,%KIwwwSÜKtrr*((DՅNNN˗/oqr\P( zdQ_MiKed鑱Q2dn||y.]bN OV Ν;,Ƞz\b=e2bڵV0XF]羈V}ּ)_r&OC\LC:}@Ȱ@ Bg}vvvIII \DRE.rttt&aj8)))X,H***sFDhk,f&aj8Jr˖-UZEDӰ+W2*.V&0ӣ!-d$@L/|)SoNʥJ VVV6665,U$>>TY[[ܹDJJʴiӸ\Cp7*mmm7o4i*/j8@wޭR5`RxU\]]\ϻ[RRaDL4ٹqFGGG'|R$ա hHQJ752 E#hDSr$ k֬iBFC!fzBȘaG!>!dB\82AO3CTɭL9ri #d r#4B>=B!d0#ӕn:777.tl2l޽s̱xgϞJ*ܹS x<'RȳJ!y)waii9}T)ُfk۸@ (J(2 E d1HKK wppܺuk{{;K8ξNWZEQ̙3*3gRf͚Шg!?p}[(ԩSV*.]Ϗ KD" SZ`T*e.ΨO um{(f#C,#P(bccի|||5\q;31iccرc/ %gggg3.RBFYYYmmmR!11Iݻv޽$??<~< !%%0LgϞmoo}vtt4̟?YGݻgnnr߿OJ\.mjj!Lۿ? fXשR'OJR<ĉ L!_x1**KSH$''R @ aΦKD"7 }III[,#6w\aVWW0":%/_NB#Aa )|o!#Ag_wbL`mm dVK njjkkkfzSBƏ?c݃m,#p8333333zp\ #"\DCCØ1c\\\\\\&L@WkXѐ+򐉊S*6m 9sx8>$ 97TPP  ް~qr\P( zdiBu:vXggg.+y[[GH/MF{yyw%piyYܹsŒ z.?~<jL?^,,,֮] iiiiiikaaAj5 e|||ڵk}׽>cp?cǎ?d/@h===>hiiimm $> 쒒$IAA\xСT`\pB}vGGGFFϏJ39ryѶqRYTT^^^|>*Mp0SRRe555bX"TTT$''TT^pN.www?fff/_fGѐ2f]v|]~=3+HE嘥J"WUv7f8& 6wyuh9s&)5k\05LRe*}uWWW8{,)9|0<裭JL-3L&KHHSLپ};}VF*&$$ZYY؄9sf*DO}}}wI?"%%eڴi\.}Qhf8&㶶͛7O4E'HݻU ST*/^\j+˵ywKJJ[Ny Q(}k׮Ufz4(%ޛcbb & ׬Y[c4D<BȘaG!fzBȘ(((0t~Ν;?ŋ ;/^1cĉ ݑߌ.22xE2zB h{d$ #Zwww|| rCddfΝwh3fzCT=ztʔ)|AWW믿nfUO:ѣhãAff;SUUrwurr2tP߿?qDr_r311q(აBRVVpHHDr111Gqƭ^_5Hp²2C!=L׭['@.UẆk1KV_^^@ Xn]ccẆ{ӓm۶nf'<==+** 79//*m ñ޶mۖ-["4z>=BZ۴iN4E+7|K&ryggM6 ;ܧGHk====܉' zuccw i}„ UfffO?KKBiR(~U\.^4?*3fÆ so>tL8>=Bqw3n޼9c iϏ~IQEQWfzttyM4H&) 0]r9p8gϮwww7t/R}}M&NO;#4Emذa߾} nJ_>zK TVV̟?p82\O_z;i3=Blݻ|%kkkCwX,^xqSSիWǏo +<9vةS dZfϞ}iP7/AG/))iǎiii .4t_)ZpaJJʺuy7xA&3=2r'O|?XCg}vuu]rL ^{CK/}A|\xq޼y 2!Ѫ yǏ;ff Ó111W\z*Q 8)ʹs*˗/Oٹ`33B d BhQΪU BfժUOyo6>!o'((w|;!HLLT)Q[WBTߌ۷ߞ:7B,]qceJ!Yo~2O(!2fB!cfw ;36-xNi?$HνKO`nf~+:N5J/"{}hT婃%e>H`vFoks^{f3<y~:Ɯz}"Oɏtro*Zxd_WO*Jz(}Ȝq?Q qyq  d WY΁ ~g{'>-G?RJSn[Pydwn%(5 :6Ȋ#/B%2 ="g}˳puaA# nvɚ>nȨʠ #=#HO_F,Cp(Nlx|=:vru>W.\s3GCiΝ:#Ƀʂcdc^Џ9IDAT>}GםYGk5 /fF ԄE ^qfUn2%.IAҿ>$]2i̤W^]੯v2g|bSreDMU=KaЏ;ߘx?R׫[OT`.auߐ_|xfc?=}OKiS)y`ʿhG+W03i+$Ze|4 Mm`t[gQ7;*NIVd}\<&i Tقؿnֺo|Sy ~ Ӝڧy;RKSvޕʥr'c?{AO>E mRNiλqq0i^b'.wv<o~ffULr[)OnƜO]WoWm`ŠM*֍^VwJ;^wS)!5 z;XWʚʜ<)o3JeF/t|[y$?D#/]~@_gMBb-3ѣȿtڒcI![E_;`750 /\ ;8LѶ~FOXhH 2_z;yѕG EQw{ݝ 4\eѺ*|gX*o>[P_%/~B\j&œ^I^ _KYƇ@lfߢ:|1:;A W _o$k{ELv+=SyVz$;+O{фQD3/MV=߂cNp|4ֻظXp,]%W8 ~Zk5vU=L`;f] w9ŵs!bmCj"<37{7+s+ dk}֞XϷm:׆k-th*Yv\VuVUk16|㗃39LyUW{9NWYGoG=tSxͫ?}fOO{~P!=3s.B)\wf>)Qf ~ǣ^5wSn ۶wCɌ_&L#vWv3C \==N}.•+$IEI ќU2}gtfd$j/A&6^ p 45f CMTJ=B!d֭[r.]id{Ι3֖ㅇ={v*TsN@x<@ HHH6gEQT K\cKKӧ2'`Ng:7.( hPHQ@ -LpKKKOOϭ['/!7;AZP2@zzRKmRu)R+J.BU$,,LjR>;_TiG(y6un|Ϟ=l$22ue EllzO{{!|^4jժUVi;[7;d ϔ2}DDDVVV[[T*mhh ݻw޽{%I~~+ث!%%k.~Ϟ=~h?~ж{sf.rteBY,{ァItiˀ7;ALŨ(s߮ﳲ"~~~>#K/2 8lD$]С(طoyѺ2bsfauu5|MCH[ai7;&NL__~eg@K`ffaSSX[[3{2cnDP9MǏ@pp0y'N-Lxp8^\&!-gzҖzyqqqJrӦM965DRJ֡);97TPP  /_> rBP(0/Af7;H+fzMF{yy7ߥKx{{ӧes~ˌ z.?~<Os"),,,֮] iiiiiikaaAj5 e|||ڵk}~oP nv*yzOOOZZZZ[[B!$-g`gg H ,UGGC577777:88㚑 @tt۷;::222URQ9< ժqRYTT^^^|>*Mp0SRRe555bX"TTT$''/C4J6_k.ׯgKRER%HBCCի{zzH*IfD~*6lؠށ>աqb̙|֬YrMp0J-[ԫBzaL ,gzLۻ(8ߵ&]PԬPKmI* R2%:T. $HF1KFt퐁y-CvqW73|gΌ3+V3OϘArRRR<Ͻ{ Ft\ uժUՙ¬M9vX^^i-zƐ)ˌ~c]QQi,..y|ncr薳)땕UUUY?Ca߾}"rƍ8 Oq{TF2%UZ(@$=Q:; O= ~E (,Iŋh}.~:u.\x:::  n;99999v{^&=h?}eڬX?tnp|> >piD񤥥%&&֎F*ǏǏ̴>~(cƱ#G,^855u~DYWȢcXo' wcΝ;Fo0ܾ}˵ 7oڲeK0.wҕ yiޘ"|ܹs"RVVfTDQ*..h޽eٳ:ANN`Lu,:|Vp؈}u]߱cÇGFF`SS"p)|xM񴥴ց˗//ZH"gϞ ."2.㒮+W~֖&Kn*"}}}_~ )*%KիWmB.u]捈۷oͮhʌrcHVVVKKǏsSVdf(cF+*݁Y{`ygΜ pAx0ܶmۤ0lM»<ϟ?C~2ެMƇ _IիWk֬GSfSǏwT)ʡvm]LuYEHz`$&&Xj7ni]{2c:8C ~2@e$=*#PIHzTF2@e$=*#PIHzTF2@e$=*#PIHzTF2@e$=*#PIHzTF2@e$=*#PIHzTF2@e$=*#PIHzTF2@e$=*#PIHzTF2@e$=*#PIHzTF2@e$=*#PIHzTF2y3+~Igb+TSQQa}9xyzTF2?m]i]jVqnݬkuK]e]BHIWr9s@`2 Л4 u9f[-_<))rdΝ5;w,))q̶tc+>p/h"wmˡA7<-:RS}*:{ }p;l[P h`A+Pdnׯ ?GpSaKi*<]~1NJ젘W=S|W1h<LDY+{?SXt3ׯ$"U\8|'_χ/[\vZLD'Qgwokk|K{>k~\u,ބ3/آ^M_nƒIs |-*6dwꙗ{O#-,y֬u1ʺL ;-ZtcޓqO9EI?Xp34 h`A+$Ov~6QrhоIc-B_1Gm  V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4Fh4:{vÇ9r٣dܘ  @drfJKK>`0 yf̡gcҤIjrU .t̶}t:"LMMup5eddd2LKOBp8ھk0ǭ{qxCi#Bffs.9쨪*((ŇBn6mI[4M..E'.K.]*߾-F+ 2mڈ#)[oܨ9Ĺd괨I??/77VWXPu˗&&iBBrY}8ܬrH=YbCgvŧ빏vLtSF54,wъ?5/928:::{=)S~6AF =&-@OLaիj}H[s7Y9&}@zP5|~zmXONx];# KXXX%Pqϧ̈~㭴ؚK-HDnj(B3WOqwWxhƣ_ZmU?8JD3[sϏI7yyDID{~y\*>裓O=,Np-nlV&zi6g ף!otc/ fn(AǮ|sB]Mmc nʎa~x_l:6VdčH " DRi?f̿'HL3Ѓ><ܹbqTT&"O"ڵ;}A^k4 n$"??ϷW-LDeaaozQ5٤'kÕH$FrzccA>>QNpc/ k%I P(QQN$sg=`eeM|+LL .H<=cƆ.HLD/x(_T&2(yjԟ^MrH=\v3n:U%ݕJy||ȲǓ  ]|瘟W='aqVNvA$y#۟rs _'̘95?_:iN1Ox|A Ox'O^?hvc/ l>xI1CRKe۷[ dFx7d~e}kԨс77oV.8y"O٧9gܘ2""bfix,';!p> q!*m=q~^n5<}c/{۷u` 2?@p<ٿΎ} Ў֗}yl݄34ZjYN$gHVVVUU\r+XA V4 h`A+XA V4 h`^A?~㽾ZqgA?M!@343s|ԩŕ---!!!?K:^"EDDtyZ||ڵ*0uTq͛7+++Zm[[R ~_T^^~ͦ&V}||Wwhy555FqJã+[JsιL0A"i4O:?xNm}qʟ񠩩)//O*Qkkٳg[ZZ\KЈʕ+տ/_\UUe~\RRRWW7j(Tj2Ο?___o^ឮg3Zð\F|\SSscNJ1us =<=ߊCX^^Vs+**ZZZ<=={fr|*`0֖ETFEEyzz޾}ʕ+Ygccc\\ܠAģ_QQQUUP("""T*T*jo߾qưaRixx\.j%%%D4mڴv?v9 Z6!!aÆu =9+tСϟ/..  X\\LD]DJ BVӗ:FJF6KijHtt\.H$^^^#G$"DxM"V(111#]aXJĕw =9+ӳ/nnnt̻gAWmmmSNnJ՝u[L<Ν#"ڼ&"r]ìJl[ý^يCzŒ Awz&mɠ?XjXhW=Fwj׭߹sL*zBoڬCJeSSӭ[|||) 6ۣGܴZmMMM``Vb2Rx/rfW"njӊ.СC/]TRR"Wbڬ݇xAAACCC[[[CCCAA " .TTTt:h4+**Μ9CDC !xEr}-ڽau\1!%"???777rSI3tHHHUUNWtOZmYYիW;] 88>????TVV^|<;]ayxxWBD BqZaBCC,jW>t剉hLm۪4M||BA"1c qqqÆ swwH$gXAAAJ0F ɤRobbSA7qZa\]]H*?줓oZpyϛJZ*mh"ˆtq]=54peooo777n A#H$Je !hg WώM\C+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+Fht(>r䈳G1 &'ġƲa۷;|0 tA6o,ɺ~/… 5ۧ355av!d2YFF/566z"R(555g8C޽{ uϞ={ھ233Atx%UUUHJ___'7hn k.gg @v7F: rƍWbZQ3ر d2F~p .tz;wSFH h4gEnnnBB]CCT*T*E3# V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XAurrrzw4\9sFIII˗/JNNNnnĉm^/77'/їΊ[ד5KGA/ ډPF$h|( ׯp٣p h`$۵q.MR'j7xyD,nٯ~18B"H9>hyVototJhʣ57Mׅ##Gm~ Aw؍c[m=]y;[3^~|ܵ_~U\gJOH4o~ )%?8=0949@r7s3LD B| ^}뎯r֑#S0հ{SǥD~+g.a>泛o.=s6I A D?RtɖW9`l}]:h(^UP$v3Y=rk ׈hV2M83lUDTTCDj/yqAl1q/!.kˉϪ*/͜N&L˲e.RQ6h}޼DZkGVz{+Ӣ2/D^qu2.3f3vfkf/G,XP_W]ػk+^ʮ#$}-ε2m;QnYmmk%)~d97Rx{}h\nVwڙ".GxWyرE;3t6.j(ҷ{#+,0 {}JK[KSkSnY?i}M۽]Z3^Y\3q/ m+GإOy!vٽpgh!h`A+XAJ~{s[Ca9]Egzݶ#"oS QwZEvݨc] ;SvF\C+ɓ>Z>>>f:xw;v[JJW^斘f͚VjAbZj7Q|o)[Z.`dy剉 deeY$++KDv#̔JP(4/|-+s} k4)F166VEms3*-----^_ڳg8WϚ[ZNqzv;LOkժUDR>ê-[xyy;~v_ͅ߿֭[#)S%Vo޼)ryMM8Z.*v۴igeeUWW755egg7VX=2LDz}xx8޽ӭ: yTTshƍ_heV||<mݺr͛(11Q|w\k׮%6h4G#fwt=MMM~~~=ߋNBНw)ڪU"##ŷÖ DEE޽{;ʬH"_;0 BDׯ_7/p wb]Reɒ%DI4IDATDK.uqqvg7-Y9b111DtԩNOrw^{*ZbE~~o+nÏFCDjmmm}}}VVؙy߈sÆ :.''Gceiiquuuuu֭[U*Y|Z1c͛7!!!@D"O-/:eM&ODÇ'~<;̓Q@@Mt:]^^ƍ<u]&oܵ577J$Gv_Cw;<-[fz~̙WgeNKNN8+%%E\sYAXt:} +Ɗ,wg7y0M&Ӌ/q=t߻|OꂃhO>BCCM Ú5kP( 믛5k+Jww}u9KӭZJzj,G!CBB^uNi:t\.۰rњ5kk׮m74LJKK 宮111˗/?{w.^&Ol^ho.YA'Aî V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA V4 h`A+XA'/޹s 5QZZͯL&m)))ywdkm54 h`t~(IENDB`python-oerplib-0.8.4/doc/source/_static/logo_oerp_64x64.png000066400000000000000000000122441245703354300235500ustar00rootroot00000000000000PNG  IHDR@@iqsRGBbKGD pHYs  tIME 70 $IDATx[ip^U~}%VEޝZ(K:[KBڄN-[Y;L 0ЁдLR`:?  S0Ҥ%79w˖,{~ɟdv;;'Y=<ī\+wG3K+͐6h{ɫ =7:΁[yP hؚYVB Z-P)Ą@j$(PNb\m~>2(@h F,ЖC׀wxK3(7& [!43Pb*SyBOܸc%ƊY tV_^ 4qWei?[YŕT d@ .`BKFC@D82W(`F"%#I*E];YY;U+`J` `3`[EoZL%ϖUQ(Wr3 DtCP8 S&˓5v#N(:D%$#`bEI=O}m|<}q ^|jnh[!RJBjvc&5dE] e#ƣƂшqML{f#Q,Aytٓ&|B@U@%?xn 䮵sӊrɴ4ApQPu9Hxo Rch5{ccG7.4tr-gܴolhkkePq @3J>\ <Ґp E5cn^~$y(JwV%N=E53Aj h)/FH.Εc}rXW؛AB]nqzes-IxХU~}$)!-5vo~_5Ի >S 52.GHc %'AIswݼr V,whR7X`qg,> jT`;}%uWtm̿qip5ؾgxdRx}F">1 "{4;#wuۜSoؼbIaVld ($azz, .{ xjDGk"k;s!Vx'8ض<Ո1^u[4"k)TG| }醁wmbr~0gK/rrgהA ?ysAp8.O,WOg[G) Z A_h< d1ՄV@L}{Ԁ tnx`O,纄0P*}d@"{՛}.7]ajT9pq'(xTdlv&`o=R]J*:ɳ?gX+NuPaq mUW:B]TC#RrƼzy!l^cKd(e`W="idJ"Z64b@5+5JRk lDF3787$}&Ɔ}*>?^nsRZSP˕3? ;ҙ ^;)bAXqiz%Bv1 @ xzpk'Z.z%2@u4f;%Q3Ҡ}x ~՟-ut}N{s[k.Yc@dZ/ol0K1ׄ QNy޵4S[Xp3`mbB^$J=)llPrw` @uf,K`hfvn\ຩ^⏌tZ8X(7<.sJ$ $#oIŌ"(<)̣ =o,W(?YS}u\(NZՇrR%3|d#8Ip NF j='N͈Ba|}%#2\ފD.51 kE G׊9P]0m+Jg@s ?ݵxHrKy)Y'35 „CzsW^SHk%`W1/0*%8S sI$H v]keM5u6:C`X!O3 p9k Ea](;UuF`^_t^Qܝ4+KESiC{ 8 @PIP}.nYd@sUȔKkv H|1ɧlWٵXP (/#9q(??_4^o I%n64 ,ЮnM(/ EOtA%\U] Ie}ܷG-G྘zS>ȳ`  ckٮlJCG+@j2L8y^)Fe\$9XxAOs̶ kss/3;悳6]2eJIga9_eS3~To?^u[ D 7BR==Z]] o;>CM `[ӗYH?Xq2x,,RLчmZCPri~PZ5d 7ЧrKV_cFZɘDu\ͤbmDMwMi<;r(GsX z /-Ng=wϰ` |𫋀ǁU&gvwi,#Mᔼ g| p}sh2`@jҙX/MNCwJGP@pSZȤr!O|wA3M6{椫˖M^_Qє\XK,X"cWp?qzb_>J.`r|xj=VV'K7 J1pC>+lQ>OJgg<:T=so^*,$%}Qޔ3B\6f\UtEΞ_c%[Ċϼ=)'ۃ]77㙴(|VJ崘f *{ `嗔Ub3|-vz/5;%*R,oHaoUxT9pFVo>u?00lT+~o6dc5zP~V$f&#Ȼ ), *2 UQpnd(ɉd? xk0ґp>2] 1-6Yǀnns~F{R9O@IId.`$`TKR^Y̍d'= u:a0y%EPW #R;^\Y2kT>fF1*P~.zt!H:Cu/?d9[Nyp8;na^x(o :-[rBàI;pwM9|pCx~z{{t`H#[![![tpmKFzjɒ% ?-0 gΜtꫯ>}:UaO}+fLW cjKÃ|pLW?Dy~< LQ?wHBC-$ ]K&M-oy{|H$.Bvv;.Y:)?b4jc͛[ZK(_yןyеCνzOgP=`;6mDNܰ@su&!Ex{ Uk断*Io 4kLҮYjI ܗ~߰ic+LHnfe <嚘ܓS9{q?zH -[|J^I2.(d<؝ o0!o~= WX0I7cLS5}: pY,ڵ{G׳~=/-M}̘1+Wſnܷu> r/?XKeS]/nŻ;l;ZAq֭G^;qRAŪ'o<0!ʪ9xu ٴqwGWLÌc_;AΝ7~\--MϿ/BS'=pl 9ݎO'7ʦ sG?hS?~g~.'XXd8hԝ=H}_;/#m 9WWϝ7^~ûV5ϾleggǏgO_5?:tVH/ϝ7~\:د)5U:񋏬h=zC< ;d `9r-us8 5 dex o #iыrH yyfA-/&[ڿWn#$Ϛ=n֬qls󳲄7~oA7Օr/fՕHk4/-MI==%kkpIΞMGW;A7?f\ap5'07aBA:; 8fAH$;m,W{zz݊]&k{7[4}CE]e+:\&'GSP }\oսK&llY_vJ}LZNZJAtJ7$BeIg4j.=:K " ,\t5k[sg7|{}[CmX5jOR8yJau# <}8u߾7Wa?}5]s\ǖ,t=8pfɏ=2ǽksiWԸ{,dW/0-a5=yi'-DoGٺGA?YbE6));v۶Hk>~vɂ4Mˡ/! ކBiv 8[߿XwϞSl5I~Kݻ>zM[o MY!++m].d1G_zx Ӆ_* NN y>%`8oHI7LW cz!al t0ğg0l1CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC`5;I/ Vh2LFh4X&h4z6ӕ!lBVvN7~P( GB]~XF#,Fd0VIQo2 `6$FLFd1 [zxH4.XW$AY bH,.+u3Gcge%wE1Y zeGVkVɜr[ZD^iiu:U6I$ ]fk z cx$bH4.źe%(+Z'q^i/ܾTH.t);;릟p# zA bф˛%\H'R%%qۍ5Kwu p4a0 GBh V!O(P0p$ _~ܜIo&A4LFlFuS4Q! `̢`2 z@< 'd JfdsP0 G`X H(U PXϴv`0EP N$z\*GEJ0 hMl2h0 (M&A4&Y4ZFdM`=n0Y&tc^s*P{OV?ţѸs)NkZ:! kFs7l@:76APB4Sh~@‘+&(Q/ (YLfQ4LFE4JY4g2E"E#`#[0zi4VI~`(p "PD p( #PX :P$` N$z\h[.d,flJnEE4Z-h2$7͢I \7mnj[-7pnrvC,5d:!.jUJ0+Pooo$7X-^ ?V&:@FT70AHI$`,DD3Jĩ=_'5`zE4赺 ;6IF;'0p󉄔N  LM$xeE:^k%$s f5씐h4z0n cx$>e%up~dC4#/9Y=a Nբĺd Q^߸}GZot9 ٠C` .S!% "?`(Ñhh4$YLEMhMfh1,fMhFA7K0̐-0TDc7?ҸG>Ns`X ?99b6IQAPEE<G,BFHB@]{|thS۲N,7kkI?|*0\i4VIJbOLpg@x4O&'8@, zN͵J\uX"Z݅f ` -0xz{{;|=̳-=Ϭq̘5vht@?!bH,vyAVX<JĩVuWǻ\$GZD^i/#X%ѠtZ$:6&{/n{ߑ== k͚ԯ!`2*#@,.#=R;]*zm=N-̷gee lƛ64kln:xëdϞ>t?{pͼQ p5BNINjX8S^;sI)=)&J# \[gÞCiбH$YJLJL-3adF0wRGX,ƯGD]hLV^9sV#8M2u]7ad ܈D@ˇ {RN;w椒9SJL}|Qk#H$Fhj#.On费n` -pNi۱Gg==S'1$#C B@ ! CJ ?>u@C r4h,&"Z&I4Z̦ $MVI,dV1FXkρ567<봥%3V͛9y)Ly c A5@PU>qM`y&?ZWɌ~ '-bk zU׳H4.W"JĩVF4`ӯE_EȳKixhMGw8ح?5sgL47ˠ"Dch4M=,^Ex+-:Z0c6F~e)+)!%smɝJ sIA#YDl,d1Y̦(YLVɬeDlJ"A`|~ϡm^A?|}OKL5Lb6Y̦byaTԟGHdSJ vJP>ḾdQ$hMt V 0-F#O>c96M{5Z8wt̢,3NicWǻSzVզhddCʀ|&^-h,^\\xhWP82nsă+ΟP\ѯ iX<JĩVu3A^QWwIKKf>vՒ \AV[MSg.3cbVVV`+ \)Bjb>_g*yv)7?g%!oWco}cяΙ1t%%3,UotPVVK/d`Vzآk*JJ괹 ;P!AJPe%o&otu_2Ac̒dD"7d1Y%f;3LCکwol޼cmܜ.Yt~ɜfј 橯KVTԯݹ}&dfeY>0/0B#Q@́*d5J etIŦRJG,j4ٙZ&mCKoocݱ~ws[נ׺,_:U2cF>5fyye}յ oo &pUJ`bh,:}N N,a }۶۱'ϴڒ9SK\<htFղlmUY4olix?{qcJ*]01A`k zGq_3pTz\u"٥1ѷȘVgKݾ-;u,KXh̞֒Gz[ܼy=n-zyU5JyA l +NҝH{A/xd Au{Ӷ$\:|KO(.tuCu7nm|zk=݉ĢӪ*JVNX|BPRJ ?e%DShQ\ HnجfEIfdIj+;yy'NEE\txz=7^GHls]چ߄rWUkkNFsJC\[_' A|J  c$Um7$3.0t-n Y nޱv^5O.Y8|K͚~(9rꭇ[-w\TUZUVb[*ad4CQ_m.C] 0@f۞=^xkW-)?`e@qMY͚ݿ۪k|?hKKfV/c 2]& zA(r:A7;}ߗH'$jA|{VV-[.#} kwݼ`ˇ==sfLZt^Mc@opn+mx{c] 6i|eU`AV7M;wu& BId ~wuuv,2 IDATƺΜr3ϟY2]Dyukm~xsUYIeb6V<"@\\H$OvENU3@n5uM;DoWXl~K*ȷelpur仵;k;KUU֬*uJ0 #QY O |?'$jVZ-bMd3Gad t;|wUkX%qesWΝ>6:~љs[kjN$͝VUQZtA4|)S @Wš fh6nHfUplVIIfNlW$۾@:zR--YlK͙>Qteu65moM(.,wUU=0zsV7Psˇ2:ԉ VI̳K99\`#[ ]uػyGӁYBrלW.7<@yCuHh9^]FN٭W.*w^P42X2P)fQഫvE^mTcoCHxsUYI+̵[-. DnNn__s[Miݳ|La^K6;nͧR!)ȓ, 0-65#>:ժX|'fgge@\PL+mx{c] 6i|eU`FVMp'|JЧ>WOO ?cYYYYVhmOlniZV3(> Z>ܸu-t'&pU,/YhѠt0z٭Uekʢx}csuMj+j/rW&d4㼞B8u}cfWN$zg]B,ac S-Hv-k6#EN=˗/+1`8߷:j9zڝ5 {[z흥*\kV:l. H'r+=>AvWBg zb6ɮa::sνqkcuMC=݉ĢӪ*JVNXp]V慾nT7B(t Vɜ08d 0B:ƺ;DbcZܵthtuY3Q`HH$i}m;ݝ Ņ宪 rל&`JbL=]ݩg Y OdȳK9 BXOOTԿeCdz^pu0q$Cy%=ZW6Ζ#OۭW.*w^P4&R!\!B[WVg6B(,p/6ELAda\ ?`f۞w7gZp8lLW[b|& dЈ|8V]Sv綆99Ғ,Sti0S uǧ/r 'Oʲ[6lYVj[-6n5%bYV&is`![ᡷw_چ;:,[<|qUELW[nD~& :-ukXM5w$ =BŠ6Bh{bԳ.Ő:AYoQ# ipwػiks|[UEiw.g̙g0ߋitRw=h -H5 6l?Y<ƹܵu'>9Ii~鑽W;=>WBgY̦|UmuvAp-n&w2@C֎uٰ9;sRerלF/<Ї;w5&_&7w0,]tܸqb=n-nEܪ ךU<[KvAPnOjo[wuw'O)VI,t Vɜ?0b6ep]@CяμzS}s4f߳r=+qۘLQgϽ?KOL`9޸vDbiUkVN8ӥ`JF;no 6푕`WN$ : C,UNu zzzw5uG?:V/(-_:w1h"׾WSO3"溦 lww&V*\]sr4LWFVWz+'=SR#|Ŏi/p6al2[E1U/>LQY'N;e!ԲzS6]w.*w^P4lp<>+:O{|X+yJnNf,vnf[-ɝf[R-:ڱn7wX]µjyIXgK(aC_&a/~0qL Nn__s[MiU)pd4` Eoۣ_P m)C_Ow .DE[g12(ln=6i8|lZܵr|$f:j?{׬^?~oc-ukXM5w$F`jǧtxdWU%r$My ;jI<&9l670 j:xt݆o]m7]YZUzyti"7_^^WSO!TXa݆:8WTV0!@^B[7$ VI,t VlEN{Ӟgrs}`6=onql{ٚKJh3] WS翺E.r仵;ks*\kV:l. ҢO VG\H!xd%)`JbQpasLAdad 00BȺ ;6lޱW\󧯭*(P\ҀKj:UL066ΜsoX]P}Ow"h5KQa,wx[ݞ+@[\[g;y?PENBp:l|pd pSۛߨ~ޮrלk1L\_Y˻W.S42]a)mkZ_ΦzwoBqaeµ5'Gtu``{؅>#;}sBS3_Fw^{;vX^;9lL\Y"?y7x/yDΦ ՛?YsQUkANX;rWIn=ǧD"M,yv)aͳKřgSo٥|;.e==]sgٚUK 9!-<ny~ڪL`:q~}m sr4%3+WY6<>ӫtx5s;=rOxN :.9do˳[.t%67+\[(ygS[7n-BUUҀk;љ;ҳ3cʄL`-ukXM5w椬LW"x{푕nυ) AB"ѣ$UQoWg.՝͢1+\?z-/q׾:,[[Yr|^Ҁ뵭a~O~fvLXa݆:8WTV0d%d% "ux/%w봩E,r: vE,r |5Gk"[[ިZ}OoooϮ]u.ӥ7~?_[Uy2wkwV7m1u+JVU]$atinX+++<B:m^oJBiZDd.rvz P8nÎ7nޱ7YUVlMEd1e4zzz̳f_}sq{Ꚇ{EsUUY]:L`Eq++5|Tokwu3|G`K$zޯk￵q-7SkVWY6Ҁy7?fE+Db6P\XYp-w͡*$5-#:Jw"Rd F=>xy]k?kec ):mn~׋Ot9p DΦ ՛?YsQUkɐ uHS:|U:v},S麀؃_h|P\rNn__s[Mi4pp2guwʝ^^ #:m.ٜy8ݭ>Jb^oJ"@ݓo'6 0-‘F{5ݳ|meY:mnKXWw7o3}fUZ|vguM}^t̀yH4mu{3|`BOۃ"i/ȳ(!lH߾ޫom*O+?<麀["|_~OYr`0>޴vDbiUkVN8ӥU%To\-| j|l_^Wk>фϮ]*n_麀[ϵuoO/;5`Db6P\XYp-w]6Kӫ;흾Nuwʝ^'6tN ߖgy6g-!96g-.)M `"[0#їվƝM-6|?vռY3]p<k]zWWtdȤDggSچMGv,wݹܵzBP0b\@Vm޶vi"ɃS;:vd.r ͢1  K5Ʀ7E"ݽVU,_f4`0luOͬO3hR8V]Sv綆99Ғ,S@ ktT |P} |k~WgUg;+eF# .o +. H՝HtxN[=N_[s{vO8UhHHºu[o o)Y(r:v[ݞo=?psô9v*mjǏu} Ꚇ߽{k+Sw-[40W>O/<_h3] Xa݆:8WTVhs-o\?"5 | |[»  ;w:(\hs{^uBWN$z#:8(,p0۶`D _mpwgrѠt]`3>u?O<6iW.r-GO[/7u+J /dV+̚%0vp@ z ŅW;m;yԶzj؁jA1(/7/1CT${i]Ttm O>U>H<6o 4}m'?}ֽi[cuM׿$N(]třRݶFcc?qo=lJ׹Oi3n߫iV=ǮyyrÓmkVCbWNNPvz۳֞m[to-w\|ԞjZwo?<=}@<ӻz=eiKѓF}]zpwOs{r1oi/|y\H`JUOFqhw{[Զl9ѝH$SoO.1y4kf" 9G>z|-H޻<i+z^r`DDb6P\XYp-wh2]S?nܰ=sLaގ7i¥UVVo{/G=,_6uAg f {ﭭ*o+o9}{E:>sm{O5/  9qH,C~lizkɝ,=n'!^o}W/Ba/ϸ?5އ}ym^_鞈gs$MțDuʼn 70 _qٻ& 'J) aDQT`eUTkYˮu{]EDP-XNy뽒3d4 EE9(+#+O)fd2r2r4MTr, 6SGKSGa3Yz-t oACQQ)ý~Iu.e,/yrC 45 {wG[O Z]{vi :f]qbٳ1S&v܊y!:vP{+C)#+Eko{l{}NmTZ0H/] xܕ{n w[-dۂ2:n}O_ۼt~|0޷aīNH`2]Ea{c9p 2.9/yC =6BgvQc sYmF\b%ctW]CV[ sF0#%51xcJ~Ad+8uI* Yc* du#[n&h %cTW ]RHīmL .p,m&a39L wԓ5*(-->v y IDAT6Th҄?OX_PxK34|o@_`ðtkAn.:,}i}ݻ;J\2귕pȽ.?}.+;_֭TYI1啊3ؽt@/g@5>]NJ>^U"!FDжxS#rꜬ4R{x+2iXUy˾%We-.ٟ^O{Mpw{̲2erѢ3eK%!7dIKŖ +9q◫XiYSXz6ZMWîfL/;iik׈S栨$=#[-LYŒTa(#'Uӗg uU.a8ښ\-ahiqLEj_&(# =|FP3}.{!n쿲C?_@Ǧ:@3bL1mGvn{~a_fYU"$B.޼?an?TҡMر t!>/]"!%OSҿ%IU'^\ \dv%$&BD%!e_HÈ}1c`Y<塚Z&$TXã.ԅ۱A*" dzc׏zEMCYш1q[RZWw ӳ3s?e(+)rL6Sf鰙\6SGRkRj (!,NrVNGx;e\+ZXL 5/^=?> ;~'y.n=\̍/vH$>g}baQ1Gjhfe%E5 !D,.Uees::!Oǂ5tC|RB$SqcnLWPAu}'L߲4R7gy7/o{Gqw=&<-Qxj__߬[|zN.lNȍdrHOQel]U{?Oe'Q0#[(~|/YZzVa_<鲛byP;MwŽgG޸f:!YThpŸE+4[Rb7gnt+07 Y?R'[i3J.~BWF$BsnֹY9i4D")5 cifULmjUֈ>]<&qA^v-nFW7?H~ [b ()baLˎ#^]I]nrzO^*.8vk|yrtg}gg}gBH|N.SYe ux{ IEYISAUKJW[E+R犕FB{w'BW;>)m3&>z:ojΩTʽ/] iҨ/:b%Ǻ:k4h4ѣ'.>'=??hҰKsjֺ2o[!+^)+/qn-&j7r䶓vrrsG'U-P Z ȁdrm__m!7@ Je4*Ga8lAVy]"lhh) u(95}K.)-+>2:@u?7xpݹf6oB"n e8n.ԪO !z9_,ۨ GSCm<\qGw !y|+ {*-'/ޮE:6_w*ejUٹ&̔л5Ghh-lϙ 榐M̯8~l_'HJ* {ZƊOϾ>;HDV?YEYvRSd{[ltp%NoۿO>F҈).&]j@ܼD@ ʖv>laF0=+=3Gv#Gd4;@F} ~D\♣G % U:y%~5`)Prq~awCTյc.ڜ6[zUG%%e-,]g43)i"kϽ6\Z[MLQVVgZVƥxFij茬<:]Έ905NmZfH%E}:mX2oYf]X".iskL4B;@r^޸6w^,-aI$QF- XTZ^ei^b? wJCl!B=;ފj'.9l@-"o{<4Uڕ^(g 3ӄ9Œ!YyTZ0Z\-].fr6K^Ձu{]|jʘtVT@ 7`7M@"_0.`aH_]}&C"2s33TA@*Hf/(SVRhqL}]6Wt9Z:Z!%P[Pg8zw+YCu:v9աG.,pxgO: Ա["\ڬP:}F,+'/] DYŒlR |AJZFX,&[gᰙ\6KGe3M u?AmAx߲̥jSهFu(FGv:q_G : @KBD]0-=š~<5ڂ?NnV}E4:WD |@(JNK(+)rL#frLcWZvfrٔ5hP[]?{P)@u(dSkwش KtLe~ϣbJ]l=]vwRWyGn^@_Qv"_(eUZgfrfr,#Gbao@mAmwJ䫘nSӭyޛ\3{TgjSEw>  xD\^ҽSߞNLxTG$=#S 2ܼ%EMp,.[^^Sh P[P3E=v%U1t@yZQ QZkBu^aQqPswCE9F<}kTp -;E5>4fN:n[ 2աre;N_>:P|;|(43pwqwu6Ѱb4eeyVXȂ"4qxex#W@S}_ڂ%nsF߼FKydgxwTFcI(߀gtձ@T%eI+([t꧚mYGh3QX j *{_ꭇ~ڿ+J5~d낁}:S蘤~ßG28zBu4j)/gfSEiaFJZF #U*Hȑaiqz:<=6u5  j 'U̺'͌3Og99Tԍ7u⑭ ?Ut߀Om-ݻ;Xh4M*LI2R4?5=95ey\O`y!P[@!ob7{߈cʰჺ]:h׎n_4w'@SVXT/0Ɲ\#Ѿ5jF(X"e'[^A$K!m&2qy\ \6@O? @5ڂ'^ 47֟;m]ФϾG,pw: 4bqyxd_`;,M^]ۻ:NUAu:,MD /L R2bBWerL)-;0quT$$ sꭇVFkٞDM潧l?kQ{QD7 /0a yǶn갨PKR4QJ(9U$[[A-s ]^@_@OPOG@OGG[Ѐ4aF'N^tF a+c}@r"\ڌF÷AhvJJR4Qrj:?MDI),i>jYفS@U[WT[,9zԐ^ t:ա/,ph)yP)X$7 ꭇiYz:;usu⠨/$7 /H ,aFv"_%/($(z\-62qdk+tXx k. 7;}u:]~ί&ЂLu(&ܵ69u_Rkc.= PVdҷ \&JNIONO$]j01q t 9:z:z:)ihK%' 6CC]PM٭{~[O: @uSEw>  xD\^ҽS^͍DYى|a_ s@hpL uZ1iw?^@|R=N˦:@wci+&Ba4VEA!/= +0q\=][ѨNhH$aFvrJzRJz⧂A_(HϒH$rr4e1q 9Ol&&[[d[tzV&<4}!O~dh;Ɨ?hhwBYjwwu٥ tXn^AeI)ҋVF7@OEjYm8tjCK:@2:n]$//Gu? { ͵ ͢:@&e4aFyҐ11q u 9F<NupI9ziˁ3J+M>;JGLBymZ޷\Q sqaQqfn]mгGE#MPө- [` cdY`(SHI9վtd DuP$Io@[ҳ twnev?ZVN~"_,OJKH$$ 3r{9LcPW؀P63@cj c/jSy_PwST@tLe~ϣbJ]l=]vwRWS:@3RZV*Ȕ69Á0&>Ca!DQђv5̐HupơqlE=5~ݵ=Չ2_9e=T݇O|>tԯWGsc}4See):$%KJ !J:|p`jeQiĵ{Wfv♣'@GYz%*"L uЀr'+kfXP}'{5{,Y%O|®zeӽs[7W. ҳZw#./'obayBBB|>'PGNr c&'mlRQq !DC]S{3C#D./OudhA!LY:g쯣Y EM5yPc.= PVdҷ%,%KtyU3FxgΜ6l)^QqI\bJL|Jlb8~lBJLBJ"BJ )XXHP:25ڂ%s쒣}g2QYK9؀{j͔P}GF+)*25x&A,A4ڰVLWi xzzBFIfsgN i|atlқDm\rQGY{#)@pk GN99Uz#ij}(,5\Uq켫WRZ^WĮmZJk f=& 'g%q 1 )>?BZ0͌̍M[̍Mx eTKJyBNG[D͝X\ˬu9綡~h1u%*)CgZ0@RVR00R{XXZXZref&N)mšN3HN |/}On鱩Ќ)K˪[@bB=jI1P&FJS4.A.u5;s;s"461]lrtl8~'/ܖހ˶032347շ2305PWS4>4A YGOWs S8)v>ذaThr cd D03֗6ɜͤ:,4\H$ϯzwaiQ*z_Ps 5ի+)*P͐9 ސ~-h\B9e4rpyQowfƚ%/Ga@rro~hYBđF# YCUv֋gF.\c8ƒrC'EY*[͘068 V;[c;T Z/`5!$7C!OąEl瀶[WZ/%-2K +޲ݮX̜$-9~PW[AE ʒ56x6̐H]pe} ŸEVN޹ڌ %K7:tƝ[-߀͐|e`9C LQp`\k:~fi&{.QP؄o%hk-n].͏޺ !b$V{;%-&r]\ +9e?-n=Bx JS39~`o@TZj:&$~`(YX247047aQ 5} N^߻ڴ:m&%Kwz@nϿNGvzҍ;3$*:];<˱ECB>[ v;ה~ajBH5B~jK3g)/njF-RfkxHtu)sTPDYocc"b?;pjyDk(ض6mP69TS=>z:f[kck1P#x|6hO=ʹi5cU`8]*7K?nypp,.^4+'et܋qϣbk(()*XdnocnS+ SVThꣶWe^ <|Ʃ=,LyTgZz<+̦N͒ %?GtH[sK޿la2fzUw?wIv.!͖D"1}ܭ% ͧRsc},p$ɏ\hW-,Ф|@ 'g6 @1(+UldTLЧQy tt6 ;Tsu9ZT6D"_f {L  D"Y=,yzzBΝ;GuڠhgΜ6lAOR7 /0Q qyyv6CvEXh?ꭇ}8ҍX3 ?c]>&!ѭt4?yIKO]P52f/9id.N?4ԉ;=}?9L@TU:r4~3}.vG'h8۷` hEE\۽ݨߠqƹqO^a-xe)}.-PDu@-x̕0~t ~}L@ 0ݻ;wwe\{pzд,Xw@N^?ކFQoK;Ч[=;C No},&Y]ת-*ɩnm:|D[[zWCOᨫl|_9'&p٫'.:ڸ k"x5~4Jf-h;fho4M99==kCh 4]x]eϚ4.pqݼuױ_Q^.:4#[PXTf_O {s ڗGn/--KII߳txG.;9t5nsU@ {s{~ps~.36N̐>j*T} /(W,Xļ߼LQ ((Ѝ,HO:4/-c}fK=ݜn?a2z%R -(*.~1\@fFz'{R`0*>q=(&&Ta** ^ҥxp7Nv+݂_i +wX,.v-TQAAzKA\ڵ4 bcWLsu ;D``YYS\j11W>}&';OEѪ/m*[ޥҲsg !>/vY(+/\-"l@dȷ5mfDz7{ӬܠAA?[]>!k oB;WSMV8?QO^fF_9n%e-X~X)+/ l-9Y~aCk{R!PVRpw9ѳΎ(' K_-()-v!}YjC z9: @UyB֏q;Zښ3{ٷRVVMޱԛ7 'N\2… _ҥk; %iq6αLF?ޣ.jů^^pojjWt|^s OU LII17'?^Į>ǯIk C%?VU^m !"wy,9//wQ϶mCѶ'=zvmimARFuܶ{O^ݺ[=ȭ3z@]Azoxg5ۏBV͟HuB]]uҋ׃!=k{uuU%%EkkO#zB0}א5f̨qgF"z{߭[޿l??S2qp門2c 9ꧪTÇt9`E{M2];}!)5zsrYCF˾\~>BKf ζ** 69c!$./Urhcqb[2?gt߀0CAž٣n}Zzx֕3Z: @S&i>++ɛ^`VX俫fn "!H$D"BLBN;3[V|`U+GDŋ'ij}Zw"C<~N!w)T?UM _bt@E%%_?n%|lWqcN' +nlӦ1E),,>[ 0Yn[᫶nVTƭSigkm6zHo4 ,̙#;vpFeO$ٕ276'xqǒ*GlR񩍍) ,:mXn ;w;ڪFEŦe^Gũ:uSSD"4u;)**W Y!493[G9yަ,CA#Vu߂W5[qu4PSW> p~?OVQx<7,4r<π|WSS-;;O(r[^xW$r66f66f e7W@Ż{rRPS7Oj:Uj2e Cɓוh4D"qi? yǭlcHsBπSKV~>/>Xp>kƄ!_K\?_7 wr@TXTh/6Tgh^x"QvaaqHV'ƌC22rA"WpsL};mey Wөl&!ԩ Flo1- !#rv={:Bv(/ /]>]Qӷhv;8g'xBu(h|["|:nIjds Ou_  zM{b.^ic5 }m[Olz#ܞ>~٣GϤ[sy73xz;i^BB*!DU+CٮoBt{ץ[u'6WZ A+O9wH/ޭ]{v;{ˡC{U3|MRƂ87;Mc&Pi@/0\]MŹMKL~_sƱ4iii ɛ۲eެY:wz%UT\nNv6}t"7qm[|PNNx>ҩ,\8ԩii"&S.cNm޽]~茌lqsԯed;sީҮBhhkW不gikku=f@ڂ3G**)-+WyPΝs'h4}}^z-MxKK3;nvɺBYWޠ IDATZKCA@H٬`Ȥ*cSiuQ7afӓr94]'UP>'uF9sfذaTh^E7sZKK-\bqy(Wg{J@5=y'u2^z|6&>]VasB"@ڂĂE,)I!Hf@/, T?w`eGۑ ~8U^r*-fy7wm:@S.u@PVչ^)Yũo+oZ~6{'͞頌" QA"@@p/@psz(x]A.!2DB)MٻM-12R~IN{缦+ycs;}j EP  ^|$݁\F! k^߾},Xpq,YҧOSN;4WTF~—tމFctSZE)%_ _fڤQ*9|ToToOw@x88w…'Nv\gNwh4?Yt'pbφ5K_m<79݁zǏO~:f>79r$]!H#u쩇ǿWFfvΩ[`4cB*LW4 p{|SgqYW=lӁ`04OoORr5ZQ <+?iy{ogo-hLuy%y{w6Wx)FK9lw<;;$)= ~KgB = C>xl֎Y$".CWR%C4};OTHEKVL~66|kܚߍϧg-zjS@bK£Eb%ǖ r#]cɽN p8|ӧOgXbx!Cq8y8x)Sd2@x#Gܹsg1;CRH$RQQʕ={p8X  B2 ޽m=z@hp8PT*ZRR`Ff͚ClK/y<.\LϋO=Fv?(=]s3OY " Ԟ :5ݱ8q"Bh: [s-;M4JDY,(i?}60yqÎXz{<M9Hܵد1؁SY(jkG ilO_h :K ^C P!ujG<g\@p8Є 6nܘhܼy{DիL|H 7n뮻;̘1/}/}maZpܹs~3g߿yVX裏N6mժUXK(9rSz6lǎ#zhڵ):tb[nҤImp͜dEݾ ]\Ω[BT 9MiZjs JkXa?IF}9 #n_{-ּx|oSF8VZ~[ ؂w߲W"}֙Om޵ʯiX9ĺeo̾գ@}z3RbS=S[{. #>CpH(?>BW_m^yyܹstoh\hl_~iZV+L޽{-ZHVXvZTwl@ pС{].1#qt;-صؽn,@R?]|͖).n9>< rGwtWU;H4!#wvNVm_`=Z*_$ܴa:4̽oNQOlH)Gz} ٟfkڎ||^rr1?vcm_으sDE| 2/{[hiQ~3NMU&|7@G:كߣ'-[y9 H L&D"i",֭Iv޽xnG"L&'8ܹsgKMNNz[K?v_~ethAQQQyyyJ]#FL˗?裉>}:ujʕ?p+oPRRrĉD2`~mC I R__Vkjjnt6|RSX@$lFQP/YwCb.,e09]7j0XB7)-CnZ;:%S+Bή`FM vT#@!?WXUlB)ԑ5.M(oRmŝ+&o~gb;rꮯ4AOwַo_H$jfaO׭[5ܜҒZRBMM?~+Z /DUV?><쳋-j;B~~>Bh۶mɍ?#Ba#=oO+r >_CK$ 5/2 xS/BhԭBR1!h.Oߴ?ȀN7Zl-Ưi}s:Xڴ@]:h v@8if>_LwSzMA9?4];U`\8s?=a/Y;f]n5ek ^C$lW|j\.G}Nv_PsOQQ2L|>?y˃ L>=̚5O?mpٳW^mvU̙x#~iиq V CPuu Եy %"qgߩۉ5iĉBׯQQ;G ڲPQ":e Xo|"i7B(6B?^z{I+zîvdRXO;x3F=#ok-QkG֤e=½K䓟{JBϙ3C nW$->a7|N[w0cƌ+WΝ;w…鎥8@u @n׏;5mOOߔHwVݺu+BhƌW.\~ԭrUtvϬx<>Ͼ=,f?i@ tu2U +p>P{wW/*&poѣG#tS?sz+ .V,6}? =^_?4@`?r{߸mb4!JptJRTrK4y38L:tn[@7US0th6P x`s |@l6|@9NJ!i*f1hB(,M"Ѩ:bҨ՛N"gǐkneX71lPO>^gWݍW]i @ <tG]>_b)X~@0r{|nB`(4aOCPa-͡pss{<c1id"͢t2D&>rdͤY H:N!H$ɠ;ycG)`OCeե嵋>_t¥>tH{.5 Xi W.Q ocAǕ]vUUU---roWryC@tGX5~Ԑtt_`s/|Zަ}/m{6S! tAtʠQtXc1iL:AiT:¤SYL:֓F!37H4jFks(O-HchF>n=!Z)2۷oӺ%K|7K޽\8|4r@@nݎ\/Wlܾ:+^xj) |9itjIOGvI[9Peؠ= ;xvg5&^`SZ[lWX*xc>tv;2ĕl0_}󽿞 G yǺt e۰aJ}Wf͚t:gΜu֌'xwI| 33[5_~bs["ؘ\6S,|T  d\"PȄ">jZ.m>  NiVZB#G߿?av؁e$xZvm Ň;l 'Nn>C|?e7Nv߲xbw,n0ٌl7ZuFFc!"!S*%D R!X*k+GZ7;н_t/_0O. mN p@Rs-'@$drk@ׄ-zU3-l7Z &bnp,\*7%eL̗YK ?xZ %՛"BGm+//;w.N7-ڿ?=vXЖ-[{.Z_H$+VXvT*㏇ FJKKg͚UUrYb=;N<m7U1B쁈}{SD"&Ch:`?c0ٰxoS' xMiT Ic3,Ťc *͠ʤcLVGՙ `0k Bt%q '`e =`oX܈%@-:Uhd_6u|9]Y;M#Aw>]^Y2G$WܼErn:{}ܿ!d9 OnkCO}dUJ1'g\/cwԑ6u.]զe7r&i2h)n :ٛ >< rGӥa`V;lp s[i.2vpXHx^vƜu R[pu-8|B&I"m;d݋/v{$ҺLcܹsĈ֯VF`0^/ҿcǎ͟?_FgE(//1bdZ|>hӧϩSV\'%%%'NH$ ?dȐ ju~uASMm å HtálEE\)_``sbsj&ެћVMF mD$<\b\sT !vgt#: ?pj]?\\]6.z@Q.X՛zVor{|X}&U%bG"9B(l~u/Qo-zUh5ۜX -1?]-- B#gnOQwK$,aD\#Cһ'GX@ʰ+o=3nڍ}m r;P%dB,N7U r F~0a.O\g\&ǗT]S$"VKŤtߣ (f%8[xvU154juf z ٲۆSDrR.jFczc뚖0[X[B2Q)<\4ABW,&'@`%ǯDEb2h'Bؾ)Za]:x`3'|26LhtgvQMۗ2N!H؃?mI| [x'| Ltz;?o{qz}ԩŋ3 ѨP(|>[n4iR 5kֻ] x[1nzmBź \,buhas lkeV]\&bUލ xLa1,6S,⪔<$ϭc:hZRuDj3@!ʖ2L,HಂϬ.􇎝ްt/@5c:(&OB)EX-40,&-y\nNk괍u Z]B&b/)xP [u9`0[ f``\ R1_&zu#:X>/[VVQuiS7UI˫4;Ɵ0.^a隽[|]NxL0=q/JmCN'8{BH)^p8\<D ˗S2QuŨC͟?ba-w88ǤI>#l^ ˲jժ{,<쳋-j;B~~Sm6uD?*((`ǎ;zh۾JRy!<$POKzJ Dzeib$bbDd%,#YzYqZUjmlu5{II$ *M8rRpnԃ)5KT8M˪7lۧ՛AD|nZP(R+H8po7했@ƬW6#hA~ϧ6i`*jM *k4:vO?JQ 4w`GQޠ7k |O`Bx0KgE}{Ur\"K">;_Z^VQ{-cn0O[w_/ycҝYyB#oio9BhîLxG'}Տyԗ8Ѳ5۹kݿ0R1h=r7^zڷgމ !4y,.po~`AJTeTN\ \^[[Ν;ڵPsOQQQEEB'oy`^z !4k֬v B>SfϞ=z4B3gRcxg̘1nܸ~n xXۉfkx>."Q$mЛk.ikF=p8䪤*:[*eBBfe#Ѩhn`՜U7=>SH[wΖ/_';~HK$r#;z߼z{(2;M7nQXī҅C^z{nX#\xj),7>\ÿٴa_:hmYa1.tO7$Ŏr=c49r𖝿vdmo z뭿|#t7mpKb?aԩWN>ݹ۸f͚)S yytnfBB#F8p СCwA$;̙3?nݺu&Mjnwz4?}WY`EQBVHB>͢("13`jwɆAnPMN튶x݆FgCj5> Bf<Ĝ3I$T K,bJ$6۟ArEp Pު՛t`!fUg.)y2: +ԪEN}s -[DEv[\i&uNwt{n`.OSD c3vkrL"Yf϶08,E0B!kv/54UZ]eM5*_K}蛱%yقF5:^gǓ K&1D)b\a123_mv:m#wFM[jw[iq8N7'`Ẇ?5涁+ ÓWo8SKq8ܪu FX{pˆO݇-_;p82lp_{{\{L&lD:O.cp̟.xe*% n'E;T=͙3}:N*N6^r @V# 3䓻>;|jɹۧNZRRr,.uol8H4h5,uI39\^PF^*(%KB!fvlsNˡ&{⫉L"Uv X}L-->k2;G}A6mK  1Hx9;;~FfJ$#Е3H^Dumlj9ʂ\E~6TЭ@^#[x, W&3X$1]:Su.Υ; 59`Lg"-@owΞ"DdsbT?lQ?.#HpeRUqP + vA/nضw; p0Yۘ0eWlc6.D\"U%XKB2xRQPUmhTJBrȍ4$Fv4%l!jc\XZ~dZv֭3f;ˋHRC\`<[rƸe!8G)QJ+QJrU c9fduNi4+kGH "OJ}E,Nw8lgАֽD@=9i x,3bYt\fѱ݋϶L  `L (d:G#sĠYA2G)&dP?X15k 5CQF  ( IBޭTk?dP!q=BD3YT+Jr2 K"aE :=$al8݁AX؝pzlwcntyOt*q\S.);g6# :5]݁l//Ҝ?]Q_3F1 XY=y\B*-H!66'B(# sUp[bS"bڝ}x< .Uӫ0GJ,;L.e tL:tH7WDJh蠾nO{Fojm/T(S)UrQvpKXNi95z=r9y,ÀA[@TE}Z]MyIOC,S̻KݑhPUց*k*tɛ)S0qP\TIDT5ZE5:Vo޴8F-.T!:9<%T L:r5sC53 ,z K]SܘH8iOWlX\EZ-c1))dX x%"،XZdum5?~ol]bQb\R+bDZ\ ]4Fe yVG$b^L#Sg rl:GFQD, say Xa:VgeN3z~esD=DC9"Ja!pUUZDz)ҔēLaTa0 KBE3q ZU]>d6c?td2aIļXĔHWQ*Hte#- Ͷ|_|@WH$9)yǭmNk fq,:I y%=c:^V}tzՁrssœ^9j; 7,5 XCeN7a_4*E#+S3lF%Y:G,̌PK|}2 geB$2l:[l74ik4:-jL"ʱ(ݑ[{)V@7WZf`9T࣡ff8!rnG"Ҩ"INŷ+)흯Ј^9#h2yKZGr8L%$mpvq.6nr HX,}oOM+݁.?h.9܎;Xn IDq,R.qX,vkotne'j(6!Tطc{f*̹oVVWUUt !D&9 *ɵs,UԴ&R`kExBf,F(_T%O IDATܣYn6<&[LnC, B!S,bKL-rT–X2)5p@ncQ׳0PfwmNm,66[6p@9љb,.K&/>e xlE8Bn8 W^S=%=ս s+L`0Z+juUʚZ]u"lfab@iF)sd2 s%]`s!SV)nүD&_%d233} }~ 0B!ۻGn%֚zCupn{=">P,S) Ej%ɤ<D6gn:0ۜ&lu&7%f_W{zՂ YX.y`$= ͦD[Z,1܆FOchZANgel%rrD–Ir9K"aA tڵnwZK+c,6r{}flu=>)n:gfd$|f1Dnqa6Vi@(b(dbzh GOT;Yut%q}{Ox{ߞ=ryʫ5 4ڊ׏dq%ES W^ؗ;+,UuF,E@))*ɖ`Ic3.R1_s5~ykSH p~pKNۘiˡ5C4c2h9JIa(Oe(e.P 2e<^rA]NS^kdTѸqǶB(ψ0XD!f ]/fl-kN$4=FdM^^ٳc2thFpx9,rYb1G ѯF=AnicUztKp{4XOl'sEnI<nn9ȓ?ʏ9 :_ybRIOB**MEMCiymem#D|nIOtQ"/[vej$\IpUmCEFWY`4-<f<9`ݨS,² iV^.|4 @>x<3#cWxp͸>*Fwc;pKp7VdqX.n9ܱXk:"f _(K\~Gy\o} лmgV{yms?N:vjl !W TU@~>!~C}3@0h)TnnSF|Y\mEl*10dqv).e_du"ǥ sj y8/;NJ2bAUjCn oܰDcX9Lh|`JKMW7(orXޢ2uCkqL)ǖy{(D+V.@d7ީkiS4jzGĢ?Id&2rBiɇvAP Ij\"m"wuEC2p&5:ڌB6y@$0h2O)"9CacdX2$ؼ~i%'朴d/7_bs2M4KžkLB= gዄ<2O%H3 1= fP(ܩDMcq.6h+JE izw񞒪ruFg BXlb gau\/.k}h!Ij@%eRgF 2)W߭ob3(BPS ȡ31 %!rH$gjiW4}yᕷ?GKW-,Zv O vҮT9U*Bi>|c0 VŴbxm?tMvmwzNJcޡɽqir&If#8L*F&Ɍbw+|C6{V;9d&wՠ kLnv&RsLXRQi u\2`ni>-(%0A_oٜcs.d; hl-ڒT=ْJcMcM҆SQ:8?ܿN%N㎆GƤ2uLݥViP(& m}0G`υ' b]/3x\Z]eQ]eQP(5Z*]J+SkÍ 13@- yn#g(P|+Ǻ';>?&aʋ *JjsMpbdġF lrEztH$IN‚B10%++8NyhӣR,ᑱ{&b(L9Fɢ *FΤ34GLnv&wgZ[ZmC6[VmhZmv{$A$!))FrөTfrqT*JLK9k?ÂD"{Cwmu- !lsb7[6pOR jVJt JΌ6$ ?P(=~t33* TI* s8˄? Siۺzڻzۻ{:s8!d s9yWK$b|+Nlڴ A;vĺ(j_} SuLNPǣѼlD̗x>HҦk.`CkwS])$%&TU/x \jBa+ ].w(UH$)=QBfi E$dHR~l6y9<pnG@rG3h$*9FbЈ4rŠdI8YmZ<3&3YgFQqWIp+{V͏u!`vl1Ы5Ч/ǺpMVoV4vLħ(Y *N%R:H%,miޡѣ G;;&;.=eQMɢLw+RZ3BaR W"UŹiWl.ѩEϤ֚ L)y<0LIZ~wA0A` Bj9z;0 "A"K<_\ Ӳ;VqXSwhä,)Y\[R'6CC.. [6p NREBP,-D0!)d;bs.nwMV/&)C!8L*DL*H,muZlvVۯ Y^ͣ7DaALRϞш|^Rz7-Sl1oWӓ VFF}8\37NA4:I#Qɋ_&0tFd(/{qM񢚒y?} [MmrLY!#X/is׀W5(zy^1aH-_j}kքǥ-+]lQM1)낮VH$ Jennt̗M*/T 0VhJA*Sk V B r>-r8tL 9 WH$b0;NE_CvQ(M+K r /.#'h3d"--;Ph`v.Wڜ=P(v5`0KKɂM;\fszo.`.occ"aʜZ[ZmC6[V;`0C!VY\䄅Lnvg: [bh7[[b].2[&lu-i}GPR&Nf1 *E'QəLN!^C=No=r}hxTY욅B4BaE]ڡliWv4:.ˌ^/)s1 x 3ltץF*Uq; H'X1_cqٴ4C [ [p*6ESCp ;@Tc: mhBW.(~9|iYiZ::JU8L#QRLH.'v3ZCqqȀ=zÀ1=z`΀@$1-f8D8T"\% [fίe{8ֵY;43Z&.dכllp4r%bl mb ƘjIEuEKm*[;-ӝ=cL|zEDX^,(#~ڠ'vA,6HgX>;Ǻ004\=lNTRTBäT$"D̗6:j;|}GZlXTYVUR{Q!p(T \ajk7\",-66`K0)ӓ}؁᎞,!IҾ92Ytwcc&Z[uku^գ7A'$deE;dq'd09[`JKѭFPy.Ā57 fdt&Ƽ1,:I'IDkW3id&Ħ)^*J$5{6=ܬ2W.xKjJKx@0xG*S7+O.&! rIpnM魁`g3$b<Kg3hx \L̕+ B=c{=`4qX\[R[&fQ/MkmZ( 5)mٵon3W.\0!B/š"J6ZMm?^[C$JvB ,L MD 9g2ܮ;܎ILB=9HRp49?o9 0Ӟ{u'w|;օh|v ړ@g|`Yt2Ia3(là0$&#g.P(p{{7[,:yͲK/agp].o>-VF|QUTX$sch htQ b3cKDi} s-B^I*HejL-k')Xq^vfS"oXQnEEdhxxxsW >LLȘp}JA*ר4>exdlrD̓>A#ƣѱ.@0A\@0(W0 IDAT튆n{0!>^]Y"*Ք\H7WwxSg|9OL$pY99,.wr&%K10%0Z;nxȻl%ֵAˣRhF|vIt?.U/(z| ׯ0/wgpxSƎɞ_Z[9OXMxgO8Vi*!z9%[-|v%#-i-,4SRUKT֙P"9Lʍ|'ٌ ywhcGsWkBP[nQuq$?%y:;N;kGcTj RZk8T.)UX1Jʌu+.%s9]A)CxJ`.Y]&)+-/r!#M+n۴bhx͟|qxOS֯qwfgrn݂ zzǡ]^./TUR0D )aRGoiuMvLm8|5T @!E[p+K$I3F~v2v`jkؾF_ǟ6v; dsfCNз^Uo.7huI34Fά֚62plF>0 F)7v|sX\qӆKJ? 5JOt&^[QXWQXWQtb7=RmԪ>cAK'X1_c;l[ӠoBpQ&o<-%e E"ݟViw%޸~\Ȝ'44 `nmMvM5:@2ˠwL׷fPtM.äRg: X^Mb)A_/y`Ioڰ'dRUZц 'ZFF}\6p>D fRm 8,P( X/:`. d sd 4ɛGڽCYܼRjI p/ZZ /߬skMmf]CyfC'$0KKksjk8Ui$Tr9DcgZhrp@L (NO<ƺEA1>{`AD /2r?f@C q݇?uUSwݴfڅtͲ5YOm8S[T]\[QEX" j͊G'W=!-e򲅹Tx03HM+_T]\Q*W?C'}ݾXqÒoX5tʭMچF[, r59drfd96|7y]"!<`1oЈ|!M(BAud [en?-?/ֵ%!EA6(h`; &\ y1+d sd @OOwl:uZ33/-]\SrθnO}ѱʷ޸jImiL'FF -&mCqbd$J͙_S[3*fm|`szo,<}zXtM4vaR9d|V\(=KE<c@w !A$B6翁.'“ 0%0sd*m͵W^3ֵ^z=:@14F:4g Ftg?b7'Y[_yu˾QwhɶxleKJk I3\LgJۭv+Zَ .= +|vR"&֕؀l`Nl 'm]ǚ:lo #%%kK*KEgYcoSRM海\,NZ]ϡړ f3&9Q,dT̯fWía;bso[8=zt,o"&JS1 ,: JzyB$`2|><*0%0sj?:&3&e^6E^1NA2p;M!b],`n&t۾-,,u+%S>Rg_ߺBU-_XbQ?1em7ީRF*Sk V IeXs@0A6>DKhcG}SCrVKV,(FO,}Շ;K*ߺX۲áEwdØǃIN/]Q,Cc[ ~luZ.b7Yk;]M°)D.!D#}dXSy "Lld 9Ko}۰-օL$Qiў}'`x38nE}+-;g̈́?pdߜ8|dHkV\\QQ"akBaNtDeϙ $4?xJv@}ħU-.^6Jxt߿}msWݳU+g H8lGx,/^D8scqe,~i8MVo'#VZ+5N׸-S+fPb]Ō #:[Rje*mp#¢9wl^]()pK9\ 껟c26Wyj*)#CO=|ԀwXgoxͪ%y37 jMўRZgPf>ƕ_"s`摲W֭_Y dow?Єä,.^T]=|-A%ׯbcKG+,,xp" nGF}S*B4m|4n‡ *>>F' E\R.h3~LL1+Uĺi}m]=]ݽ% 'cDyn[_(rcip:[;?-|?Y=? /7DiYRbqi3S۔3HY+ž*1۸f\"HfS'S麕}]JlGF*޲qyA>@ff]V{?*EI`&;6c s|ߑ9LLoui\Ou[0`g4r< p4ZќA2Jg:9=GgVZ݄gP?<>B$FY)lEr+.M%@ 0@2<262ꛋd#JcnnWU@0ǥU-m:~h'Fd;_7N`YTC[-(|T`[WOƨT2G72K& 1j2Rc\=7mtYw @)MNR 6 1x`$%bV/.~{}G[9:4H(]S-ZJf5E(usϙV>nΤ) wh:_|A\f"ɡL#e3fà pBE"/v=gbXW*^#;_*-̋u-7?sJlm9htUZ[,!9 d|vŊE2I?G;ZU /(b}pD7w5>BӥxF1"O" sy٘b_ljjwKIX &=yb(% wP8r\1Drv&n>#UUUg0n{; P¿><3֥_`|\{A՞_y T"Q,OJ]=$.d[[L(O\`A@\&ٗditRn. ;I&L m0p+{V͏u!`vl3S၎osٴX2~]xM)ׄa.VR[R[\yv|o|xyo)/8t݇mAPuԮ_Q8)q:|RF*SGht`(D(-̓yZȨo4ҮwkWk6M~~Mc] 3.EHwn^~~!com`GPMk7#épxMk{-҄DW]J$ƺ*{.B+V #@' ' bR `9-I% T@L ̐Ǝu[ןKu-gÑ.Mܮh>-;CV]{Mm&~nBf鎝;>25%6fTPp:6FnWڡqz6O4MȜ X[$8x`KcI,I'dĦ4"!cnG `J-`OԎu$Q47J#dlEz0wz&3oUE5c6Yzj o^D"_:ںzšrg1_S@P'c] kWh$!>Gn{讍\'~rޒ ,ӯfQcU MijouzT%9>:+6oXziɱ.Mwujf# 7/xk^iKH8 Ξ\Tl2C3>4BT $d %9XXOIMd̤&LL#<0] [f o|C] GNm谻<)ąO;eF|0o,Kz]6@@=W+O޿i&:iwM~)e7y#Dn8lOH"l\Gn"bTƎ;e^B鎝vbVi#&uyVBiW( E4p0 Ǔ O1ƒl;=vGo-p2"Jʌr8tssd [ y6׮ww#-o7JeJm0V,XT]\^,8`kn&3A%o߰rIe"&/4퍧RZ*S+ԆѤDL'A9/?駏M޺_H%wL/1 Òr ?+ rn^Cf*tFCOrM+ݩ)XűsO_U %\|㚅ski鎝;vzJ7߀gALmr5Z].wz"0:!!ɠHH32Vd9vGoM6eR)Dsp \j0%0C< K7 Bd_jP&a ז,)| &}&Cd~ec?:rM[ݫ3P(TVR[,-)-p!SmiXbKWldЈbRdwCgoD!(?bUB[/O&õ=}m~cH]E7\jh#OwHw|6h6.(mKO6$ 9c&WJLf+l2ۍ 6#*QEBXL hT"Z6MvvN )C!2@%ed nd [ |ө)M IDAT|o{5}su;aRV-\j8)3- k{u۾#MI#}w{`C)2T4<:tS[\[rq/NzDg0#c]}krme0Vu^>o}Cͱ FٖnBʅ6`4 Ln+l2C!ө"!@L bE(nˊALLt-18 x%B$PgW2hx4: \ [fuw=I!^{ cv= '^ZE̸Lue ?{74F">tGrT5RZ3Ba2_5O\Y"\Nz\) ?{OT'MHW6Sbކm H$!!>o^}7>zM. Cݿ|^*WuCMnWؑáߺ7sclOwC,-y7oƤbl`.WJ\njktuO #Sfi "QŢ)́鶻͑P(tʺU+q0C_ퟟh֥u4kj'RZCL|zD $"Ḍ2c]5WH$RE5%}/~Dj+ g$cͣc>glڻ}n b]L__`S=|k lRW޻9w:Doxw%V}])`Хd2Kg1IJO EEBXt|d`2p7ᑱ肳3 l.d [ Y{or8/[>G(sㆥ.s 7oW=XVۺz"$dR+KD&`wK rc] P(̯i;m2A"ò~W1\ǚhw#'N~: `;~#(YNrgp`wǻqŸy(3eN۱}ljW_vˢm ʶܜWRkLmmvBiqIR"᷽ JIyq0  ۝ni0MV0Z&luNģTr&Nb&͠dL%gAO&-Sl3d-w7߾Iej6yÒV/2gHpD6vCP(>>. K{neµf;U_=ޏvRޣO-x|mVĢ4p.OZ 6%|?@d};?,E5%߷,o>miWH޺܉w$V8[=_S(0Z9\ajksz"0!rHH/*L%/=#1Fdu-N29$3h$6f9 Ia3,:fAL ̐U(ys{ ;w>ks* yͪUY;4zSu]A$-5(#MݫkƺRy,y9_l}M?2mxώ6es15Xz˷޺x9. ̨1,vu[;;-]v"81FsbQ,aKR2/Ϡwt;\ɮ3٢ZAx:5äRH }f (@L)>p<(gz9e5߰äĪ<XPHk8%U9ݩ՚#H&GVK#skVgV=T'ۏέ^دEkVÝݫD2xQE/CcH;uFbH}K 迍D3mr I$Pə&ä|8pٴ;t̐p8;Ӎ@6_~)+_mU)0;0E"(kmr 2eUs̯4L"xwTm}< ħڻ{`E օf[rDݿzخ$%bb]rl\>%1[̻fٗ_o]YZ_ M\|»>~o\ 7&'fP^Ӄn׶/Kϛj *.`댧?w6@EC pefdpkk5/C~K1I,ǓrRHH *S.mwSfL6ӣ9b0;'3g92iBMYqݥ7Ytߺ~E ?lzB0%bDī*M wI"ػb {;b" H/"=@rЃ>}C6ߍ{fLm=BG^V~X*+HCz#>Ga.жL -~SWe J~QI甄䬄䬯i9 FQ@SZR*=vf-i=AJfk<~o0oݫzN Ƥr BNh@%9k#=gx;;Č-{}Kqf3_]EBԴwJ5Fml:}aBg|OH(PKBS]W@L\LFW a P [a!$'#%lsȨ) = h?.P! \ٍu"@J/RJ*xyL 4!z QOټpu :N]*? @czJw^e q۲,NHJ=k[Ӌ6vMƭ/wvi+OT^4=#|g@ܭۊ&W:3`PkESES8>]ز砸GNGXAF~ԕx@5޲čo9ؚ]9܍؄tCFƷYYEtzުF`r#/^>۳ͫ%ec7*K j‚X`@^Vrj-{λ9kaQ:jwzl\`޵Jk愑ԭ!o.4Ѵ=DZ[Q[7?ٺZ#34:Hrr$99}1:gzFa\A½ jc#`llghj*s}cIdc}rTg%ABrv^aqҖw@VQN ><~жbڽ_OY;mBH` 0WV&ħ|NI̫k  CLݜmR-3$< +,AHVSQUVC<~Rf-}ਦ"s"Az\yeؤ)IiU%Y3#m7g[3#mC]u~>ޮLѶp#a\)%!fpU)l45SZQ:{EgDXt(}z0kcgrtSvlœ]DN _1ÅKaRݻs^f_m*^_T_T_TWXCBя2*#H(J*IHH(I)HH1f7_zaȩ=k[w.m z%3B:ps![y-]3[^\)٤ 3d@b|()"Kr!ߥ]8+^E=WMYk cZcs6m۶폋ر#s ؄m^<>BVf^ vk_W߀I0@[99:69SRᏟxxך|9.9l !ln,Zxwɲ}Ʀf3g?| #}1:gzFa\\NTtWObeelլMM-xlq+Iqee6?cR--ŚW\$-!*%!*!F#HIHHD!>^x p=k MHsq3zxN oD$V4^ ABBu|XG 1HOt^VQUQY]ge꒲ʄ*:5?^$,D&ID!0$L! D!H HD!>^QW/ $8.h[dM]L}~뀞QD}/)R[;*1>MT,n.t?@G'TU-XMTLmV靐שy:eyS0ko>Y1o"YG/"yyutg̬ o=9Gv,n=h#/!*1'Ongp=Lf~0 (C 0Gk"yxp!0(L`KhՕ_QS[hz!3 C?&?/{{0@+"&}.5eHlr2%e-箇~:[!bjsp P3vRo!Ud/?% 8%_`nk~7w譫=X$>p&yTS3u 䥙3|KtiDL*a!Ac}yۘ3KL{r̝+TQ]9Ҭ1%E_] f?J+B:ĸߤMoE%>gD}ss 8?o eujuQWt|6 WODorPu8xXEDUda䙸3oޔ֗CY2Gm}b5QC5_*rR\{pGb4*wu9/x&N>8`i-g}׷* A +EET: <>{UD yYI &[ j}s NKJ>m<[Ayg_re%:Z{/h۾ a+j*ӞimjV®:^}dnX eETS㏝H/UnAquM(q1g롺UGvv*v*lT/߅DHBF},3&`ɅIs'To)[w3SQNQcG٘i[fC~fNɮ',-~/ 1p6{ 73 z*KȨo#G\`0 llmm \\\""R]LmjRjufjUm]ccscSsum} !LijF5446SںzK B5Fx~>^QG"D!A~~>!A< B! 3;` a@U#>:.E19p=A/ضm)hSlfzw IDATufXS3uMQm4 yuUh No?pv ֔3`"֡Qd$COѿ1qqq85#sIׂ[v&7g[㴵SwnZ\ Y@YZ«GI9[VV Ԇ3e<-&+$UY6A{ƒ Dbx1 ZqO 5u ƞ.Ļu'yKE n\ք[\Ra|ָ-+1fϺ|WzzsDVQu&OZS{%__a1իHK,MԡJ?Қ43n>Y`ʖU=}К¶>pHagDp OS?&e|I&23PV0N{FMm=#c=LDa7kE77<Q]S[@B&*? !D)u fjum=Lkjj46755S)Uum  "$a8 @Et|gh;ڔu'.=غ7xudC9iַ8,בA5:ZS:}aTl2HY4!OS*$$:Ko<^t)n?e={cJHzr} }/IY 7,{~<B#yxտ5Si">xsyeQ6;yZa2郧?h}jϚY }2 v[^9iݛZ Ҿmy 𶟷' i6gWkg~JS_"N埱S|,hKG`H>[Y.;YS$Iug>À6^u|hRkyR0 0 vs˅7;2GH(IX|e؀Ȁ=# )y2_Hz.~>ރ\fy9vM7K9}֚ǓҾ]>Iw`:cswi ib<U45Q45Aֵs"r絛Rlljl1]\"Х v-2٨RU][USWUS_USuUuU5ueU+~Wk HHHIKH*ɪ)ɪ(tL /Sn'[MvaL$;ImS8֔b"Ix:{"ڵ> xK$;ecFm6&Ll<ځooh" .Aڃu%_?,Bm'!`k^T]~IԴ1de*CVO8ܜ!gƌ "l8ij3X4ڏ̰19~cJjMct){ 8vfq)%?+;S^-)w~K~r+s@|{[w^#,oFٹE'36[ovsP~Q)B0~͔!J`ZWOaMYd%;?k cvZj.s_Tdd{PPnU.BNʝ3o?TDTB͵mx%9'+ic` f GY'u+H~JNx<U5K9!+cpGw=٩KQhª~")y1Bz珂e6঵JS_٢j)=t,c(Nr"`}#YC8ؙ]+).ҧw뢎퟈v_/߅ۘInJw cIaW!/ G^U^BUW"كC..(9H=b*GAsa(.0'{x'qgnc;/6Л^L-9e6p**#֭eHJ>"P͔*5kN:,㷔܂ŹŹy%3 J4B TRV!kB{@:g } |z}fHN` ͔oΞ] +)@i5zZ)<"V(IMvVu[g̘[c>fv=jvD` "E-V+xB{pV-̸ПXpג-5[,V VYK>Ugߞ=l&깕Z a>|Rvf85z6[ 8p}*.-g02=cPub!D^e3K;^EW-j09 8~>ނh\\\ѬG&_^^K̈O}7 xx#0A퟈{\G=mra…@ &53O~ R[5ܵhuۯDdeѣ7 ڼ]޼=HANjDoϜ8=奞:8m+R,4|}a=:7272[!TUWWTWeelF89*` jRT38'GRznJF'jBz*zڪZ*d5vtԿtBVny[yyp/VQr2ť[ՇHVWO ;P|d.FNfRVF# oF't|..K^p}>>Њyvr S{࿒&Hn!!N7-M# "\5]RQE}3bU!t{. [FSWYZ!nJJ !+G:͡*2.4<*uLMm榕nN6z p݁go>>\ |V:N[CKr֛G;(Mk]j=Q3tSa3vJp.&[bDJBWReλ>|Nn3gaka+,11ه0 "v]Xz.4fK򾫫`f$g/LKJW"6yϟ}23z;+C5q#6[`O_Eĵʫ;DHB.<Oȕ:}y/'@\#voߔ3ܕ!jjW =!1̗77j9r9bI [YAFYAjkbUu]Zv~BrVzv+)<8$8 WC~s^XH]hXmĞ3@m 8{FҊfjlB%;BS\!6 Uů_57ش,BhnwOI ȏ_7 D9DJ" U][]S0,bc][Q6ZUu%eb$w SW ^gĞ.7,@-]溷oW>_b/S$)"E﫤TV5VK.e[np_}Á2Vs]tB+k( J Bġ_kNJ]jSݗlX:3W7W5,hjXud|؍mXCU@N~8cB^{)g#igv6 6VD.!FB= x ];;ǂ~C[CIYAqx4ABh3ߋ)~ZHgr^^YYBӿ.b BtIDx 3&UW6>zzv-A3yUyޔYy.%.#D45',m- bR0o s~@o˦ԯb**M|w0ZC 7hjaݒ̽{K{CחL"p>wGn:o! 1[<~hF&/p`KEkN?}y~ OI '8UXHk萕 FZV{uBoc}H;wW=)szlcax[7k2k=룜ĪBs:ow`탁_MwqaWZhڂuBg?n?Y]~h .vm,&&WY 6 ,Xr}@d@@d+C_vyEt&o`ƺh-;[z7oWOОPP]! 7frޯٛO_DBX`%-ك=vGcS3/Oqi۳u V[͚5ۙk!+6whg`cZOCoXe셉yC2CB2CS<=/&ovRxw9_ax%};v ݿ>Fff/C&گq[pCRS$%<~؉G7FEϺz( IƓ'!*2^|:g~qqQ.:3?&&]BUu?%E|vyS3UK]pc;~ -<}?~&בV.r ^ 53: ^zZ|x ہ!~>އ=_Ty_Hg Y~I08c8k8_t-ͫHTwv3Y/'L$XIXњ0C+V t0qhljOd{x<[htUih..+/ +M8ha-"Dt/ٷ,[樭kKKo̙#.[0t㦌Zh4c6"$!;M+nzu8'۔\;U̧o8r^rz1_b0OYoZп]tEvm'67Ӄp$Mgf×{Nܹsmu _~xcDLbůM5N,̍<2'9+">} >s7Ćdm$MЫ{ubob`z?sq8 vzc`SzkMsg:+*nJ {vp˰@5g~IgUEE 0{1\!& 7_)hlk1Hw8HΗmuлgGd}zZYyl) |/}Bh$G1ƦA㜆 6U~#NZ+,j߃F}q;3P`okƘTK]Jʷ0: =;-zq }-okk[pscm7gZ G=lEn v'K0G 軎w*IIM~$[^ --Fzc\]( [5hoo5jڹ_EE^}$;n YK c7Cl_ƱEu4_Eı4Zqjǯ׾P.![|ۏ1ԬĈĨ_յ"6{,e?׎w>߃=~7,@i+8 ٶpShW q@m,{Yk=wvp0.$#)&D@ۂ.;Ʀ+ۈq)0Օq/imKzx_LEDtÆu5%%O={w3?im&N76:#ebib3>)aXĝׇR:n1r2Xg>--5'{vo|ARFLb8~> cV}V:cbԧja!A:HWSO'i+m-}ݤ1*zSųݼM̃W:dċK˱N@Q[pTGWgˆ_?Ү>};u;[1O:`>dΝÇa6 XzαBDF%?~{Ɠ&OhjuF؉PPoݼO_>pmLu5f5pMzƲM9))Բ~ks} 莴䬘Ԕ\*&B4j EG+n_9݅%/9.6*䤔DGtz% A׉ezʁK" rvlD>m2GWƚkfxbq%)_% ̌A IDAT*IM#HH:;O/(~高F~$;묉#sЋ4/ac2iB@l/_FRLLm FKJˉMH[PRS35Ԛ6n?/1C\|+Mb{m?O2 ^0qߺxwCͱ A_@:ZZ_&|cFGmsy ^WaDanyzefg3u컭"'79q½scuG;O4 ۯNoh ҳ xp#WgOdxEy)@m *z靖;mZtJ=T8TUץege'd'd765KL 9jjJJ*p}ܞ(_/pzZvmg l-ۘ4Ƅ:||MTS(.ֵyǃO\|~ øG)\Qq*/u(zЕˇ\=!1!~BkIF܌'MT↗ @t`_|Zv+M4U:Fж45S=5+T@_[KrVϕ\WMYEUů꒲Eeť?~BHii(i-pkfO;h_S3u2̌.0,bߋTe uՍt t ue$zhk|;姯<\9,lpqqNз:TÓuIKz/ԭy3ƈϦn㖽~qnG :=HHeʼG "N‹O`>K :c1O=diŖU:jXGBMTe;b_bף23ҾrY3 .b05Oh Oo46wU3A׷og[jLllnnllnjR(TZJShtz]=FS-_5̖ߘuW80kEy)M5E eP>g =8.ף+h=!TsbƧ>?u,%!jnlm,7 +t5U/(jX`AA'I|2M["qg>:pM 9RSCd1, {"I:=NLEٓAqr7nގ>{^FOt4UU{p]Mv.[enζV̂ka`bz 9vWgaΣ {QO.;ﻩFzp94PkjjkjjkkjkjS)!sstP}=J0jBJӀ& rqqEID1I\$.J#3c~?|Ŗ6$FKJˉMHMHO-" u tԘ t4;*a4:~ 00hD0r3#~W2'pr'/%) QN*+ύP>?$Xjq(w{)~ 7hD>|Y>`E:r5}ҨmkF0А4/? w`kias;{p(l>sXJQqyů_5?~WVW.YQUS[_]SW]S_[@,(,$H& BD!A&q89:B" Bx~~>^߿}:/\#htzo} 46SۺF!qD|||/ `ˮHLvvՂ>gaT-;{BJvBrVzvoU5!I1c}P߅/1rꚣ+'9bp\'ݡ Ǯu@'uX]S1? PtܗntN.OIq95Y̅Ka S-y*b ׃wJѶqc A00m8ڜY)9>)-T܂܂%ť?J+J~V|/)gv0D"EEb$ue9Q@d$ 9?$ k> Kf`xyxt:dn#SV$$g'dg< {&&+kk(듍45Uiej9e -$HXpF+Y$'u@'\_S[^οdϞtU &Y9o:37`?>]sRTYy#C g /ps:Om ]g/yvw.ֹ31[zɭXgpNN~1ޢ$u@G<\i4Ƙ?yÍM;_XׯO.^Eiφ>zmMl,}WzQ`TN3elֹ \>gkgiU蓏j4 }=< KJV|RfFvAZV~Ʒšz(IXS]QUIDOUIVMYNUI}‹^]R\,C" 1c~9Ԁ(L0Q3Q7U7VPf=p.|2o qǫul㬉/𙇂Ps7B}qWj;D`<̞<̾D=y4yņn㸸{KE;y䉀UV7sC/4Ƅk71Ф[XBIa09E?'f&etQôԕ5%aw'UU-tdxaXg.0f`G*U5[RZNRz5< aAL  5~z:&ײ*1`kZj+qw{֏(< ^! CJe~OHNO*zP&+kk(M 4W;{ i;q=GiEƷX HH {S֍sᰎ?\GpFB6`ܞ_XxD[KK;nbYĩϞZq=QVhttfFZsVΕa @ж 4/m8yn&11м [IYӗB_DG|河4Zp*77)JӚ'fN8,!+Dϊ살aNliaՕdc= M5Ehi@i+0)K=LpLhx ^ QՇe rRFYϞ⬥K*6n᫯"`m췝N^6FмFm4{M̅K/v>wz 넥@UջD9}iNHL|{MxY7DHBXgW(#M95sFqXEde;nf]=%;(- !9+!9 F>^5e9f;rf$d/m %zʝG:ZB`F'|1>udZC]uI.#c/e%-MtPm BcvҚquz^ۥ-=D}g_v˖8lHXG+xp8峔dVm=p#пA0Pd|+֬ @nb`0Ra%O755fq[3 o.ۺ-CgvW0ms cJ >Yl]//Iȝuu@[a 0TKZL|j\bFQR\d FT:`'Lcu߅J ^,5ƚ,~T8:xV ?j߁}Y-wA4cYUaR)oޮ"u")\ !9vWgaPik]{6>z©XG#c1sIcEc0S[;kSҳ ҳR4UJdc] <`rrS#'B~ ~'_EŇ_S zTS35-3ձVjV~Mm? 1Zf&: ѩԋ~Y!I:WCe'OKۿWe4։3y>8tI,nm{OfLSaMۏ^=WXϹ;Q< 9izYl]LdK=zqYU8:x!dx7|/K̈OʌOJHΪh)hMw&+뒕UeHb50 H dٴXںdk< 2tXsxpqq9+Xb~݇ ;/lu?PQOʄ5wM}-t46 v`wbvba " *(`b(a;p>mggg?{ܼ-'`׾ٹyQQRPiPٞ%/1E4 QwPHJRnմ4Z-Lk \h X:dn ڠXv"y)l;pn1r }|>?.1fDl{q?efJY uhki5`h 8_kahmojh--]ƍ^J`߽00kךQ ̳~j ~zI+zΙ8vX'N!|=~6jbstyډHn^'iUiՒҔgoJJ}tLBHycKTX_KK "*`1Q#[5es\|e;uv@E܍?{VI9ښNޣ0zGkiKFƶkaG; 5f]:wYʦ&~flaΫWnudM-F_US XVN;`x@}=P|=zS|`Ɗf/x<ӗB;{BPSW NI;.C+VsvnR؄ԫ!v,*.4jY V<{0}gx0юC< 9f @?f9z_Y&zjhika$"k`cnp-!BZLJh_Ӣ#1<@-u{oo-mGuqD92dNыW'7|>\/{ mk3oҵԕigD9y/Mx>ִ⒭M MNVrc⒒i )S_a:jFZF\MKS}svZ3z^yb'a w2Vm9{6XFZrp/]ZiH8m=pv㒉 ϭ>sJ-I0ԊAپӸ+^D\Lŭ<'U(Lç7z#*uU%Yjfn'/cVF\Mqщ3'A;NNyc][/y!#6!|Wn[VWVlԀ9}Ai;CC'W[n쬌ig1Q#}͘n1޿ۏWT@ukLEIvTTZ)xvfnj =LzYXTc%rꭾM2Ew9sWi/[҈vPiCiϰM+/Κeהv` ;}OӢ#%aH`m`8? POZ~Y? +,^Wh |Z{JӎSXrU#P[@aX]ci{ba~'i`FzkJh!!ֶS\NfEi-{VΌ DbQ[@ p򝓆ѡ9,@5hZZ]kٹI/cRbSD>HZJ@H_ˈiӷ0ѓ+C+i1N PTUfo/TD<5jѭEO`XZ;\ 9,AB^~C[[ؽiqDl<{YX8E;@h5ټk׊NnN-h3ya|}Ԓva͌b&ֆv޼0kEv@;lKh'Bu輜/Nm"C,n`5DdNnN\|:][Cvvޜ^SUh`ݚOtvT5 smGzLݥG ؉8-f+.)8qҪyigyfwcNAYAAaыTR]q'K@϶@=!T`SE2Ғ&Z ,MjXu=/p=>o]-UډNfܠ塡~~Kn]9fӎS955S 9~h7YEs1jJ-!A5>?л߬c 8lL%7Ĵ5-a|;W[MVFY[e;Ӿ5F+$<~olp-cgXfB3.:rښOG \؄k?>1vU ^bL;w33sU+.bPqvګf5^ Mڻ=G.ʹtd89vi&3q, HKgk1Qѫ!wrE8CgI͛5.Ϊfb6,iܱTVgzbYpcⓇm_r_h˛;i ,k;W1Q俍 }x*)elb+n?^ZZ&+#iղkr5-M1Na7{{jޙ|"BHn^A o~زQ=iǬtu6o J~^XXLqq5D 55uQl];;]B 1n=ULLXۤUkPk !G},QWUyn8 lǴ1QX𗉋if{)6j *cXwm_mn}eђΫWҎ0,J y߀iёԖWvuwVP[t-ޡSWZOzxZݺO;3/g }g/%.6v()()p*ZT\? o~\os(A3yTO S!W$$߾HCMiM3!>ܚۚx_W\uu5$^wXŽ\e9=n Ѵ'^={`a~=dqz԰\8ew.*"L;e{O_{ C@Aw{thN;`q;:Lbi`E}n N`SʒvRT?gHNvgEkw1)--cC1sQ:v1]ա#ǘ~ 6!qӗB2r462WVlD76vZW|z_ЅBHiI髴KX5o4xL_X*!mf B;紎"3Suؽ+ EX,#"Uk 󣽨kH(,*b>Kf 3/ݾtpP$#-bo~)8UX~2nCY3h`Ǒc߾C3tjmfik7^}?Zi !+V2{)WPK3h [B6p^8qwXԾ88qym&4, ]'7}-,׹*s͟{u2Kti`޻wY^_9ԖؘA]64TwJUFעI|ZXF;gge vŐp-RFZSC@NEIQ=w~v7ƒ8wnþ^+Vn9zƝh1kη>UYb sv.T͐$BȄn K 9v& gD9#W^^RHHHZZV",f9fBuhHRLL\c+TH]C6d76QTD86B]dL@; nN_ތx@; j=sqh`FZZK.[ea̠.liC+h#쬌Wm9}fy=!,Lr5$0!(bpÏ}l]9/n'kj*jm2hwW7P7x *2ž} !#﫸)00XRR|IxxAm6Xc>$+)-]w:ڸdD] 3r;zA; BRcy[ #!f-[D i1yRߙ#FMe J`i)};:rqpOq1t `g?3` 5%Y;+//uig; )!6k LWq7~ |Jշѹy$MHMH9_ iZZ\MsjN%ʙ43EX,} !%eGups㾑^6oaDZFSNbBZdD !dK U% ާgGDz%Ŀ9xLvvA'k `a~!gZ%@ \ 7pVE; >J9lJasSgxHJҎ$,!!-688;t ce3~7z:,>_q!Wf'_G!3Dú<0p֩KڵI.G <%⸂ ,PAlBJee|eFFZF\&:"¨)khdܙ# s%Wb$=VN>\=}tx޶ɎUS5՚l.~ba<;4߲|ro,^rpt,(?+akV?kyhg\(źEr*@gF-jۼe^x_]-Um||eKxM6E ;ruig1vy?ƔvBm0@AA᜕tiegeL;˟ege|})}^=c s- vS0%Oߒ߷k% bS:~ړ564j٘o`mLmLOzzԅ'%% gω~QYj)0N (R_u6]0uts:z&hQ.Q#83k\UYFB^EgLs6TQߎMl- d-شKnҙiZo=p6Kt=K/;'oVߑ;h0^Նvz忍 <*)eEcajyHq#Y=eӷ+!w_?Y 1 Kg^ 6%>?ۜDEh'Y9}F/Vipg@X[;.,%$g&/uB;8y{Ks ;I; @m ~ꢭsɡw)(A*ϞS Vj,O;gge\VƿIKgkY~;OBs<7#HZPIKI7_rc'ߧ4; [~?惎6PKn{*.]-UډH~^gyzÊ|!0?h9&ڳi`!6φmۦ hN;@h|沝gFH;  ԔG L;ߠHCU)W[#N")!N; E975%_VAΗ ZPPX<-e%6<̴/лo۸d^BB%¢^ >AQAvA'Dpu۰$l֖Ӧ0Z`0YjE\\MBm>UuMhǁuli\m5YZs+ Tn]:?cYjųc^J{քvh(P s7woiO;cgeb2~}x7(>ivN^sG A_l!m em 9yR56łBI q}]u#fycs=LlЎ@GQ;BHΗ5ێ:ryJW|7˶\<,P+ƍdEzEp͙`n0*m7u6銉܍M7Y9G.:S Myz` )S_EKuB")Iqη<}sc]I q@Ľ[:idcHI#.GYk٬=;}kTVlD;r9F+ =B; Ô.X׹K':9:um'UƴTPףbS/loZݎ7g˝vFQQ P?Aʳ7OR_%ocKטeDcV1R# @p݉y4bqMփvV.6bWG Ӟv|١~[ϚfaN;ô_xs-ztf.bapPj @yo<԰!Ϊܔw )~⒒}z|`mJ;~6X_X_+q-_9;7)eyc>HZJBO[448:O_ĭY#hg!.&Z$j g٫g9F_D; (vp}:#4w@-A>shùꭾo~PWUWE|9y2.dNOn5safmތvka7w垯Eb" j @|kB0'^bΌ-8xjW[ $ژs!AlbՐFZF\M46إEW;aw=;E6o0ᤰYFAM-Fhj;y;1;Z/sG/vN;5⢖uunG~& ܀HHֆ׹Sڳh`aC,Zrȱ^igiע؄{kY /Ү܈:i. IKIEK`nm+4TZ`RѴ~'%%NVW46813VWU@[Cnl s_f\h<,utsy҉l6k{j X3VPaPrrl4oy/ PP[d &ݜiMyqSkݜWGnG?ph%KZ:A-v 29yʶ?YDF2j~>H;hQQ@϶ ddhgsdhg (ݥ}#/+mՊڂ16< *dm e&v./dd報vI)/GN[3o}Gk>}F; /Z$_5ΣQX'I} ,y\M#}- @|?~)w1`c"+#vXOצolc юⲲf]:G:pMⓞj @ ?~@Ud;t|i) YUmLh`d=i3%V r RIJ}UŐ9%Oߘeմ63h(O7v͆V\ ;BBBR&&z:ؕwDply ?珞.?Ms98UqVa B; 3[0o1}z$9ŋFڴT@;s)d@_עWy Dv)݌D;K܏{aL1I<{!,KGxR k^0(++ʹ}AddUi'jN(ݧ 8Fp rkf;cBqqQY@Qk3pr^=ѺںI)*Ɯ8fLYcb})/ZׯE:DJY܎aVmAL|)BB,KS=A~j& /؀(h|yqqɛ7o;p`yA-h#yr"'z^4}1,mtnligav 2;{ҳ,!fwj @jxv ZA^vA)&S+l kb 4%%i46(.)I~:'&=SnOٴCX6kݧ<{vz+#+gؔU'N; =ʊL uCM-w ^aѣ;Ţ1{sǔTE}.,?9;WNVvBP[e~ιvk򭮖*,uMi` )I1515ԩXSb!BUJДWEEŊJ78ZҲsF|lI~Av8r7;;7ҲKoEM/++qtiickRe Ç-LM}xؖ-*"$$z\ƾKk T?)/ibie4qb]j(?qB_+kc11W7>~Qѣ{9|nȑ=5NN~qJ]3$>zz 7! ڵcfr2_RϞ;9q2N|NN!$;;7::a9fj'6e`٣)+**9+'ݭ[9| 4w[;TqTP\)) ::YB"nnzӧXĕ&[XldDK־ Ajt6[HS >fՑ=ʳ|JYhLMeeDEELM%'0aBnSSu}^+bPRR $$8q50/yNNOeKJJo\._rƝR{{s99BSĦznwuգԭ[B={S'S„}=ڻII:~B*̚=BRR\II} !OS_T3vh8fPYaVCADҤG崃0\Av oзh|-JB\̭- ͌;>vR?)) s$iTAI|/x֬y+(ȑ{MZM!O '"~j,*_5{زMml2KؠȮZB" !ָ OUMݸt*+**qx:dYy!^JܠⲲrcBH~uzJ++㏞l[92;]/N08(ب, uK :a,?(CߠotFTf"´4rJL%Ey)-6!̠I9:Zs|ax72~HyIIi,?<^XX5%+_Wo>o^-Z؊F߉߲utJLL}۴H98Z.TIUM:xw豕v Y#"JA ^)n>؃Hּ6_M?fthux) Lb=fhQ^ ?(C&b&7VTϲI0@Kgy f?d6i$$wh@;`>AwPQ}Ů trjʫ;~gF0yyO**K>Le6[x\B۷Yq}œ6mϞQ>AXۯw꺩czJJ/QuX,/--cX"ȸv ##!ʵ_~G6Z0u!,@Xxt*,LxF'!Z &=Jo:uDm -jnFƖmޔvhl !%+ 5iHv {:sv> P sҭkkBȜٛ?~)t儐vm!۶?wY%Ο|kڪ-522n1qʠȏJK>z.v*wBHppTpp.7ո WM))5"^Ϳrw횃U֑& [޵i@ٲxٹٹWozڨS K眡Wmk',@0comrn< `Ъe಺hȤ5mm\}rToPrеɊÕv|9aPŃaXvِ!]#BC?y£Kr˳g]=DE=ƣ>VO۷LX;^Ucӏ74Q{<n`UqSP{uT-v8u#Kڹ;^]y+W—.gа٫mddÇ+V쭼ӳ3O)߂珙[O2S 4X.vfO`-ea A$aԮh!eF%qR-:|~hv0 7$ 5/(S tDEh5n,ó ߵ4!˫3Zm(EEEttznoRB^vq4DDD-V*ߔ55U8RaúΘ16vߡC3E-++qOK' @X,ɓ'{y{'T_'={$>}<ߗy^X0e0, @iXعfziga'׶uSץj!WIalyŞ۶ſ!Dog<ڄvhXd <־ Xз ߵץK$AjPXT,*"L;SS]m΋%L/nfW,|, ^{b$%зxTy w?-/y.vf0LG7Wi<:l6 ?`?JKEҲ%|vdgef|=s]="Xj˱!0|{ n܄^ݻO;H}.D#*^O.IKI䠶j MvQMQxt XY%a7JRf]i?O|RXHIv,^GQ,1!# T8PC(bؙ3gHIIrǿ~v.zb򝥥L1wNSCa'." NUx7֮&~1 WzE;HÂ^Ho :ؘ ܺbyv&е2R9 !<ٿBiTi> jڵ111yyyEEEO>ݾ}ÇiG`Wn?g$yY@pr5cgXv yjmoN4'.&k!HmAIicmzMsؤsߌx@*]~Nx"'uqGw#/T,yݦ~AwOnS9fMD/xnjuBnj϶L<@͝,ee$Eڍv|>vc䌵n_qi4Ϩk/l0L~zR]=\I]>ϴ"LO'yI:b"ԠרEãã㳓w:݄?Kx,8޹œuRp!Kn廴ԕ8m IDATq[nlj{~/6Q8i;'^> )庝'|4Ԕy{6 )|s,XvFpܚ5ie3rh=bS*Jo?>]~S?&޳mϟj>ݸob< =\~ogW?b33HZ 7&I&Sl 7c$%WvC:q$8xYm+<:4S$nEm?xF^IM̙;BF\WT\\yɁ<%yחi_~%Ew"LӻOYya5ՏALbX,VnnСCeeeUTT6mD0`|ƍ͛WMxxՅڶmmz{{kkk8p7nX,VX?: G F TJDAD,Xqc="1 vb0QKc8gl7}Ykhh6266Fgff&&&&&&fff.4;{ʕ+[ 8}Q:tS+ǟZkBGS-Sqc\u^w|E$$F۳);}W]ՒF 5vssϻj6dܳf?gn󧘜qGx"iuiA﬩XBڪ 6G~rZ ,]4$;Ƨ{>Ap<`ka¼D<}w%3^}Rή_ ٱ3զGw g8!;vi?GY"~v#=lw Pv_fYN#?άf+/ޔf9 !lάObBms2v7U'A?D"5A`p3)+묆̒fNKm9Wf@/g8Uijz-n91=+_j>hFGҶ,'Ws/0ضp~&cVPX;HN;ůߞ֮8vsAX[F4OΗ|ۑkvڑs״-P /n#NʭݰyRtד~;c-' 'Ew~!m~aqStm5oNvN16B)#Ao9/g,#?xz@B]tgjk9qﻛ/-l5c߷ϮnI lO*.-77'" ӑ#ӧӧOZ%pƍV L>bAA_vvv;wL~u9BSNmRWWm444t0 Ś0a·a0^^^^^^]Sf䪘X;OkO-a&EwMHKy %<WEBG۾%> 9:l:BHoD) <r4_$nf40EB1F nHՉS-2/3l׮NHo?tl[ H(fpܨ7||ː31qi+jd~ 6OVHrN;`9_,ph۵K_/JN}tfz7@H]~;9<ѷŚʏC:ycڽf<ۚ_**mbWUW*ؤ UrFCe e e {;OCe|joe L8XٍTSc=)zNda{O_6uⲆF~h _ӟڣm=j+#EkوFyy}EI!6 ?jsԌ\뀯r:紜hj{lS>MOvN1 N낀+5u }6~ 킧Oz?ϛ7!4eʔS9Ҽݻwrrrfnj> .//DqBׯGرo߾!/66vٲe7nlwϟ?~000PJJɓ'{`ϫԄ[[[n۶?Px ɚjMKm)} m @ȆWAyh]I^#åC\coUZok1\.&,&,&7#J2297kPQR2BýDOPabRˉzN[}5ꫪ8M \V n?)ۨWT+$=!q>Z=ik!++9N[7>𘜶VƒO0g 7Ng1'j Zx+g wuE[t7F1,&b99pi掏qa@kz,b JpFHKԖEE_~k;TUѾ}hPTR֮ŝ'= @R+%"/CE6ꭦۺM?%;rwa蔔U>Kw 5Ԕ9u!z B\RZV:[B!O^V"Jcntb_Ny[Q:N+y}zQ돫G;|ԺuB学$'W~lٲCihh,Y!jRRRT*aǏ9\]] ѣyyyFFF#FhAٳG[[yB{6mw<#I8.\@Plmm/^vctp~(2lښUܝ.46fMY[_o8gd1lY. ^LVaj;h@y^]_Aaֻ01釥$u8izϤٮONXsv3"_[Sv>xL׆B=2/Y;BYM˧is>*xjmhzru_}Z*w1SB߶~' T\Z_ԛ} ~ӣ큋B(;w.:v6Kѵ׮kHH9~yѦ7Y޶x~Ew-#>~Ӵwha!Nl Э_M>v 9_9K. 4(88cu߼ܯ_95 !)Y4-XjU˗k֬9sfYIII!9m ;S1`!!!D"D.B(##㛟=RFv'WofBǝucGkMUimev{q1%}P}>QkRVpN=9 cв7/[frT{΍iaͫ}UOGu9p;ukBOv4-|)I3U\:SR=vz(^XLAh?\vysByͳ{(e#ÚҊ| EEۙ;1pqʳUU)O]Bt=\;Hacyv.cowgϕ䰚"\E5p W˅iyoXρ/>wጥ~ͳʓ6Ý|FH-уRv߯'_4+(IvnI+9S 1~:!%#WRBLMYwM"6'x @iX23k>Bj* }B|Oץص׮5mt z{p=[߮g̞g/#o:|ӡ67brC_ӹS[wYqJB$!4w( Э:~̧ҬOn"߼Oka.<>POCL,z._.m`_hsq۷KKKstO ֭[7f̘]vq>_|pN<9k֬ p2hE__?**Ν;S7~[B`0޽{kkoz{{ԃ CkH$Yzrby-4Օ>~;Eaan:j?6N f8.wnR+ |,m9g͕npv)ߞ>rcw'O0y>vciSs>|xfS#](Np 9-}'E٩_9wZ@$^B-tfĮ/9_,ə3YYZCVҧ4,̛! U+PiȊ?<-pXMpO<|g- a!!yY޸3w3& +K`ڨ-fj*k|'kIͶ"Bh7O5AMY^XH"J6k-6*M.'#u_ETVZrc+~ @R3riZjS1^͖td-8s!2bѡ.JK]{ץkpᚦ|}~k7ܳ={i#'#;H ] ܲeLY"?B"d'mZ>3"JflZ1d_-gS[a$)&F<@W!zO,XO㏎AJ"vQRRRVVvemjFFF-<6mZSSӢE6W49Xxӧ?'O.]yVG̛7!Q[[[__t1;;=.xG^u~413tck$w ~iç q$,<~jEEzNgݼtp?Ern崴Z :0zyVW' S(* K@I*)>N9쯭B""}'0fy*|WB"wFo>b9,..&'g;kƤ3';H(ڂgN̘&&+K5199ԱC{wvDwᱷe͌i:$aaq;۱G{:Ut}?>TUMMOl8YZ۶["yn@g}@tcwc{^}p?Wn!.]3fO;[N5̕奕ƺZ37rr {yl ߎa+W”)SN>rogϞ|}vSSSQQQ bjj\XA著0Ba0K.HfPX:MNFɬo95e AQѩ/B***_ v=Bl.xIFf!ӺdKب$#wGAW|KoZ,;=+j .'/қ;W̌lvLb%wA״7RVp`| om6/4z_U pg=g4Ji Yz<%}ys`1n EZwjE邞y;Bn*Fmj-E}}6U0Ɲgc-ܓWPTWߠ;06<tw֨JRcSp\RtW`;DE.V.HFF۷BӧOǝ~Ds6~8!TS[;pݰd؉;}R$2YAbقI?¢FCK&h:sj* O2"lqGF'6Uw1aԠ/n[=B+ 4):o.|@XUum؛[Ӹs$8mNz"pw]IW^:QRB wгBu 1YYU+n0GJGwˠw,t\W7@+зpOAȁ3~smmafcx;=ML uuu=v :u: q; DEEBuuGJJw6lp IDAT^$O)IV[8[SQtA))Ⱥ8Z`|xE%*rn= 45PR@=sU5YX!TvaQQ /l) *@+ 4ƾ{ K6~K؛O|D|>wxZm]a!&k[EKpfe=eZ/kKϑ^n{ Jml 's"o$')!fD~>wxZHhDMmHwGA2,{V&FBH6fW"Hއ$= rwxvIf'{>}.UVڂ.F6䥻u |eoqݸƂC~rҝ]栫0,mjKW4QWk-+EP[SQ)C~ _*15kw5 y5­ t?/=; MM!!a!ApuOv"*EP[sq |HEQ&-Abke"!Ny ǡuu.Csw?RYUI*+}ÿJ<_H"px)ǝ?L6tȘd3:,_mz?|n.\dge; fccabRA\\~tLA\|^ttqZ:$ w@?:⿏"JƝZ8w°#Go8qY brr¢P[j 0xMҧQO\gj Bnoh! o/,ae6 559?U#D$${0͟0V16VS#t1'ЮƦ ql; 7I[=)w=vt~@RS16AmZ6-!݂@ ̙2|LU!N}l8*",C$$(CqϋɋΏ͋XWG" 2 z$aJlr0F 0cH!qOZMS Vp n|r-O2 jE~a<oϞ<᜻pg31YSj pDO\1a( t1SRd ˮywA'܏s?F}ȹGJUU0 s3޽t 465|ƹ%,oed[NBB9Pwq"+[SR;pIccB, \3e{; BH.?8 5whOCuu~LlǨȼܨJ"HS73a[ݬ20;|߬«[p,)-{)c>M Hh@\MI)/zl@ѵƛjiAV=NHJLɊOɌII.(*ږW+]rz(w>ƦH*"@Cǎ3wuL8`@FՖW@m cG>z1 w5W#cSM踳ͳəV 559"޼z.;"(9fSdd֓&P--4^xz[1>5:>5*.5*>-'!$/+Hwh<%lǜ̇F<"c{ ޿o]?jY,.j9P[ӷy-z3 ^n? vSC b2 b޾|&XVSEZjekpN1.Pm]CdlrdLrdlJdLrZf^CcYX@POsf&tx*cDx <ڝ_w>$C +( LˉXB ո<j |nLpi';sYBs[6 7G}yv fr>DeGώ(LJf55y# sP\ZC|dlrBJVBrfJzn)F559ٙ/cQ&3֯ 8j΂V]S1wAg.:my8p瘇["bY [K͚{3l#AƝnc[6X_w@W>;}Ǩ*!M+KgU+JD oȌȘȘ䂢~f <ސ$,S o8tzeUKw,wAD &9gjqmђqǎW@m!TS[/FŝEP_2s޵;ϼ%H$Өf&>Ft-=M(&D >ɽ|Ô`Ou';\E% Z6g ,xш TR{Pцy{I$[| j P(zAρw@OtťR=,{#ZzW3^N.,.iei6zM_Mk+iuuj"c#c#cSRrkjEEf =3.z&:;Ѡ _bc&-4l&SȐ`Osu˶=#/+5(A@xCS)Z63[HsRUk@k?nBA@mw]4jȬAfNz?Wn==wЄݫpڴ0`$2Yڊjio\{;i55K+|OLɊOɌINImb2d9͙ٙ2܌Gi~"Fمb55~J K dyuqVk{;NE$z?pjQ2,WEǧ.;oOڋгSϽ@?&!$"ToeAm1QP]] ES]y¨A8q NyFbQ.݅ 8kjv䷱ڸxNUQQW-jc3SۦV>bed'gF&GƤD$ *zZ#1f&z0XTK"BCߞ>soSj2պ}͍G;½'ID@ AY\|3{8pq|q"b2qx.%#[n>s,hdYd!/қ; ܰ럠?N,V·Hia œ`?`,;15+26%2&92&9!5D"u4L}Ltmi)q,퐧tLF奇%> E  i4{;=ÂNȫQ!'JI34,,[z@c#q-KDEBuP[u*r+ߺ4-B}̍Z@dxe˪Ғa55e/-,' m[u9P,E$$LNˉMLOMO-."uƺÆ؛Hc4i553o/3o/P}ee7iaaoOmRU98픍5Y Mk:=w >{qo/nٔvloDpH$6߀%_Bm~yN޿w4k߁}ۺ:3'zϟ<~b55h r/$$&$'d74QL uzF;ѵ`l3Bؘ($-4,xRIeeMk+NK>$2TTi A{]S[:/ gw[_s$"-KDHiy% ,,iI󷄾v o1l?qCM@zǯOo"pgt;6).>KstaaNsYBގ˃TFŧE$'d%$g&eTV4q0a]@OSUITK j,LH\ ^,.a֛`co؏"-;/EU5;_=SMv. 2 ?2N?1н`LMP[G^Vw bkkXry/M[P[XmmG8q; eg'?}YʳS h`c? `؉Y)Y)1)1E%!9iކ6Vƹ1tԅ?H"0Uv>3ByyA[I {hzN%qb "b8A~ao֌ݜ,p j 6ۋ{eJʠ}^\?u+0swCL oNjKoss<|NM# i؏fo'&'Dž m "cSRRskD>jfB_ m $fe*N}i/θ 4{iuua6yibYu]](w' ۾-cƭx#");݅f8ox*)lLs _`'k6Yfu%cMB_G>m |(K O|"@BΚi۔8m "cSR2HXϙ2܌NΓR-'0!TSZeMrZZ4{m[ > fN+ "S'-\[t|1q.,&߂JN!.r^z]+&1aM6.(/7 ]vm&; 'u/ kj㓳b22b9T:F80 tuBV{2<2<"XLfaBbZXxZh㝻,% 3pWPt3Wض*d~ݫqߥHpԸs|p[, J}+ĝBISΞpу1&3lF>\1]nΓU;7ɏ<}YabLcm;sc?;[xw촉L̋K̈ILOʈILfEEztAMh (`A$T* c;bX4=gec#g;mD7w @NSl;hظ+ )8@c65G_G=r2Rť0&fC]`x!Q2Qуph+]{6-AꫪRH y($?:D&kc1vnG[aJ, 1%+>%32&%!%3;d Zzs 7ki•̼̼Bߤ_[VRYYڊSgim%$";/%o>{/?D(\t)ޮПn#?6eo_h@c3з\c"w[_[>w><~k[1*3 :w4֦'= I|!($rxgu];槢I/&HNkhl"TCָFzZzz:pO 4pq6pqFr?Fq<ݽ7xN8@\Jz;0 /ylA'v:Z,&;]dvkC?;{UK*jj(4 'mf] W GgnBmL&k­llLySO1djX8۾M֦;UV^R619ٙ/cIR҂jiBPqZzZhhvd0tB""mr56ʸqvfϛ|$8t%IopUM!WdKŝEe;^ۇ.ޘC.ȥW.씑Bdz]_U%e8x˪%`Y1əqI ə MD"A0ogaPUa @tLFUTdyeEK`bw @B_GLxru '(=5`o?{8t:x| j h*!r ;"vj\19ZkSU{\uO?lfB*)qƒIB z6fګw2l )Y əyM plw]KCMFfRRC'09C'<z YLL܌jiC'𔫷'g۵w X^dXޣF^Յz@`ٍd1 ρ=2├"ABi_;]ÒH$zU'*+b/*(*ipg@EEp~jhcm>vM_܉ %f%g$d%g$dUV d{͜aiHRG$, ia቏B?xA}CݧFubkHrzoO]; K2F^Lʹ8Ll IDAT:6-L5-\bΔ<[p^9~䠭^w2 &5on!!Ҿ?`Q{n⣐O z t}-YLi'}^3&xijKJĖ SRSCߝ=f9u:v:v cSAw /;,;_Td; 0>hYN1ϑK_I*+N/iEԋJ@@@m* 5<-$2 vkd(yD#o.-FŒN}ۑ7Ja66<{($&VA\Ƕ? \i:7iy UkeIRG!˩3..NyJc]v>"颥;  IA7ZE"AQOE~[#C=}?eAPSh PULÝGF[7wMb0k}._2cP,';|j>F ʲޏ Y}UdǨ}{hvwJqMy @„숈AmҀjiAs07](5]/ѵG9~ܜw؟2mZ8!$ 7pUMH)Wq}~ e%@ANzp'ON$BWy3~r_=?cXco߉w?]DaUFXOIi )Yi )YIieU!i C]MKoc݌Zt-E9> ?H"0UƜYoߥqZHJjcoG@wocO$ؤwܻ4W r~-I0&=P[SPW *BƝgvs};- g>}퐗``ENofzjNx0w+ 䴵\YM8@D2Ʀ5  4: 4 ZJ.#ҀؘvHea!DR2Ч9* cy{ *kC A6K*XO;QWQ1֠pMbgѵpggDz|2siqs *ޗWyͶ uspgdG {vnGd8xۦ \u9 dddD%' !5Ӑ@TlHҦ u52V' ]s$aaҢ(p˪N!yYih/Ϗ |+ӆj%C.Zz K?KCM wz$6CL𭨛(_a?,{SY "C^VPOƒ1u.UFPS$A.#H* cLPEAAw"/_iccoGuGo74.; ==ʽ?@w-أTe#C܉訚2 >~j u4}Lto9l,G8sy]6?|_?rya¼-w~J]ᓸWaHʣf)H))PШitPOݤTTLy @1B,,$HJ4{A&yVU2{p9C y>{b _thAÞig+/ZCp]GGSrwQK707irsB$ܩ#=zDl 6Gť:맫;d%e'dedg'$g~(_UU(X(,Ҥc>Joz0^@ZZP--/Z*3H pbIrMiZ[ -jb2̈́{R2r_h2U02} c1YY܉rQ-42EyG_[K]Mpc\;q㸹_b/XUD2q$$g +!%3sCc27h,3K7נmN߱:֖05'fe((x*-,<嫻65Sddmrik\=y8,@|HO[}QnPT>C= WPRYw Am^F4PT\s?KY@B?w=Wqsk qoc9DE\/mjhlYG70lLVv^aJzNJFnrzNJznJFnn~& Jz:m{+g#"+ʩI̽Tp葤TTLG09!T_."aGݸ@$*jձӶlhsK莝 fY #^E~B$,K䴴=NCC3* Sj JK*FǧAm/PSpxU~ί^83uS7n>=qs6&d=^OAQ]S]|: 9}ۮS{i XJrS& ?wqBB+@S%iwQï{T:30gY?s !5Tzjj!sStAf6{v:E t4Zѧυiir߽_)n%:.@!I%%#;[#;[,%'993>KtLI_6MVQѱб0W7103% trfxm]%P#4z'iɑVZ:"Zfʐ[r zFa%'KKgwY>bҚؤ# See/J#{)b<@I˷쬜NM9jeBnA$w>p|@G~E-ՠH]FED: &UUMCU f}FEQ+kl6FUVhZZ]SUMjk赕U55z-^ج4YQ^Z^VJYQn)V:*:*JBBmf1S} {PKj})P.ؿY:{flVjg1"%  OlSK}3.SmVZF]YuNtvK~z䵝R:!81283'|F:"QUT:#!!ևϙXϠ[gܭizm^;oۡ s #^aEm># ::!TQYGQjlv]}~yeU,D:d0մZPeU Ūkձ H#RdqE9q1QZRB\(F"J)(*Jl,f3b$^M^CX:QI {w9"GqBBwlb-zzKTR>d]_q+)cKJw~ۮmTQ,0/ibޞmoo] Uմe-[x*ֱD-Kb!TUX$ɣ@ `cЀ>a cزb󁳚sda}N:wFt:?ZL)}Ny+⌯C/5=Km@݊OzVX̦˅xU6wbh^[=gӚM(GSשf32mQaufsZa9i͙ƍ0:"Y AhD\Vv]}?`0]=jhgLnЌK31O&:BG#U@ni} w"N[UInNH0s&Mki_ jbx5#4Z-^PPUMcXjNYںzz]VW uu/1LΕu_RB &ň! q1O I!$ĈaBHBDd 10N'%!II#DQ*(װdH_{47wʆXG;vgNy $,l8fV_gd1~=.TU/*.c;Qi'Y2O]S]͘;^}g:Z`ҾMyXQtOJggxǫ?^~s7$%9d 6})k+l6}QR:jjxvzϰk#{`* $ h0Лd}375:nu#qL8u>'/sw_[i`0.z| eԱNq~^[SCtzVKVU4:V[G\_UMc2L&s+8gI 10(*)NdIq%Y!!!BHR\Ƴ$a^DDX(!q10 "B\4^a} {x8`ڔN9B:4ЍUUEx~|OՑf' ^DXxэPޥ}#Kkk4]$`U(\\0}TL$.F:{dHvNI轥 @{hhhh 5AꝻ{74hj L"ͨ7Yoc^ Ť>ဎ&& 'GY @VYX(3t-*rI!s!'Y0uț}_zΓn3UմAg.\0},i) kUմʪjFU5ZUSQY]%FUV赵[pRdq1YB(J$HKI) ^R\H!HJ dIqN4YBXb1Ꮣ\K{dExyѰW.>޳0q9&Bi C97,C=fΪ(! (-@,pɀm. Kͱ2cZ}&:"5+iP׉?_'|gAi G|[ܻ`Nm]MB#/ρ?TTtz7gHǨǪ}!LUa1Q̘ Q&)!zzɋp ]JfVf\\Nrʏ8Ŭ'ɪ}t,--̵G%~HJ7/g2~;ND=vr5LtS2\ r fL>{%(@[ɐC.;{\=zbh}n3)+zejwť}t578Y8c4YXҲ juEeuQqYA1s¹][o9//+%-%A$E$$5T$R$QQQߕ&KZVU\+{xLYMM%3+-3/qMQO {|{12\NGA+(?v/jD"Qy IDATv~GOITDUÆ_kG[  tr:r:ڜ55oSsSrS޾}(Bci4||OZ .&pᮣԕo{T:"Й/wԳ'xc#NmeeuI 3z /,UU:Tk3wWvBvPͿ^@gWWPVQY\RO~~#7(*2`)cGrϐbP*J˩Eeť%eEerjYEZBRʩ5pr2Rr2d94YH^Vs[N '-/+%+M A;1?&IFt#J ߹;Ro51zG|OS*0K}GAH\fJ- l^;mI:`[1C\~xt]떬^duDױб =5?g!2Z(yW__ sucueCl]͎}sJڗ,S8χi D%$^9H!F4C@(Y?B:p2-h z$-j^=};׾67m_L#z J)R+ze5F540jL&fU5򊪦g%%zipx~6;878$$'RQX\VZVQB`؜D rJ2 rҲ2^jr2dy9d΍>Kƫ IWW57|SD%;s `1/Nw@@stD"E%%$%HDQT'ԕBR!Lfb.-feeg旔S8+HՔz((+3$/#/+('/'(/-IhQz7@8E}ΦH\Go,aiҩ%~>_* nj2Oi { #[[].3uP[.~±Mʆʆꪪ޽όOȌ;W]R"+멛p z zf?gƼHMz񫨈 ۑ7\0'~**sP Fb.(Yda"@ r 4-Qvk~g<^hݪNG ;M˿^`G>K}&".FK@Oz =uyY)E99Y;r Fς/߳fee-kB$@# Jr vp?-쁰S{#(LKvuc`e44KYEezFfy->$WD2j^'vyjӱ13lXjdȔX9sҤMJJ3{¦ !j~~V|Bf\|NrJꝻ jq z&QUմrjUyEU ZZVQXTϟ,[Q^f3eG[KJa/Rp8rrvb(YYrZXGt\[d1ಊJS4A5[ԋ~OEw{^kbI^)r-8j 31˵P54zFfn߲f~]` Tԕԕi+kih+++# x克>}c5?𢡝-\T:(ϼ|dV?c=xÙ|@ p8NSntd)I=/0d ޝ>.#%uP) {ݒRfL0c:BPSKtLI_6͙=AB\XD3WB]SPYUSYEVP++iԪ*ZeU l\_TDXNVJQ^FK]yݨ޽4z0ӂ)WÝֈH__rPI%3KNG( r ,̌p8\›41C)ckhIΙȧ^. z߭= ?E%O\ lܩcy~"ded椦}K5߳+!-ue-u-us&RWQS&/ dlvF̓sCD%%.[,Rtv ta,&ũӏ':{t<8eF55i9XEIZE'EhKczC[~>K\YLZl5HfXs ^X3{r"M2Aϰ2!AQOy&J}sG&:SROQTD]UQZJ,).%)*-%!-)A'KHIHKHIHI!`t<řsn\jW̰ YRP_;>=t'Q+k#͚<]HI,~Եųl5T6}J+$M'"̳_’ĔW)Ҿ}I&zc;μ_:JrLTB@zV\d4Ɏ *u\#/]'/_Yro o`D>O,˷7CFz:ꃍSC]MNvOrvAD2@|f~:1A}?` s(kz%mFGK\ \t>~xlϸ.qx{X/YR~zW$*"w}o7oX-ue49>=c=rĪXG,-~r ,̌B o>Nu,p8>gQt㾋>[*6jb8oحǍ=vdOR?dR_r<|c0`1NEVLty-DaZ IW ,Z~?X>AȨ`?6mi$tUl6atYZYGH _DXTAn; {;Ԍ;W.raxg ])*ΛYBϊOȌINy@ㅅt{XZX( e-^eUn3Oi8:e>^|xЊ{:?3JfYfb~ ^HIr whJ>߼.{ry'Ÿ^|`=k xn^N ~{~tRgUe#_={1:=ʵssSphA3"'w8=x8`t=XhW9b;vh뫩* nBh‘\bߌa&yHBtQN%t{ \U T@f= @JUufLG/9ɜ ^_ ddվF@mȅӗۍ8s]ӳKg5(x%yW1L"'ز@4M/tb?[2R;>5wzm_tkϛ6{~QKaa¤q; _Hv@Uw˴rٳf=nYK|QWqc2QXa˷3I>%y-@ 0ҵ=xwᦝ{+~zK\YN^sb ñt)I?PXt}Bx3J{]\c0 htQ# `o$㱎غN[v֣}ClW>u}׺żmY9jt}=ܹO,vy_`ֿkG-̌$تywƯK7< /9RdrvM{l|;RjjXG$8<3FqMU%'QszZc-{ڌJEEo_e9ڭ^Ҿxm6:(Eğ9@EpqBQ9% rSbx z&&`ő_Bj]$υ/|t.C}mch!:/͞wlWgTKi*FFXG=߁QىʫQ^[:g"Bh_qq㚹jv1q+7z&U{WF& !!y3.E'w%'kFz֯Ghp6lZ3ͽzmڃFxxtpWt*7-Z`SLn9}4yԱ\/.F+(5Vlt3|SMO䤃8|n}۹v^:W>? @!!T*3MNrʛk7tqN5Oi5smF:{jC+B\.LnsZ#IǏ:W'_QXY_YN@ΙH"n񪨬>y:{j'OlI{U5{]\2{^4>%䲇"_c22BHM }u4tuׯϞՉ1YNrř ?=3ʠKW_y0[*/39k]/!V/7gŤ5^Cgggu4'$ԧRޜB| 9)w)-E5@A\LVӨ,堍Րk C@{RQOۺ´'pK `h:1M 㧉s쭰d^~3$&+ZY#'Cdll΄/uFio>z]qi#]eF[7[ũo< :5I,֯M&fUY~n9xi[ͰW9Vr2~^Lir2dNsV z|opd@0߼MM,4`2_ ${2駿v4? y\PO3'r\l/]~jgB#Hn3W//Vmw&Y"^dn +s6)|wu xl[Y>=<~;˴U\VL?mB(;d\"L7 Z_D 1b_LO<ח,%Ia0xX 3IW}f4Z)ʬrfqWɼ#]ST=RU.ؘG8ʂ>Wo㻝=u1XM:&Y!3.>OUduݛŨ8?ZP mq%BOZ#++&[&s IDATQW>5/oMt_fUs)Y?#L$1f/`9+WKg+*͇Zr',#xw_m$_XTƧr>Zu2;z6wi<.Ea}wiS7#0ɫ^[RURUm,4NjPW߰iC])QkHtɯveqs9`n:X.*' +^rFGwf}=̉D7ۻl툋!;\[VQyNzpm tb$Ÿ"7!T_Sw?\ B]P.PFxٻ;Y܎:"aW/J~1G= wKx-.k_ [:gWb[!d9PC37,`_Jo u4 !c:N>S7큊8s:c~_<ۥ&keԊ@6ǁAMiϰ~|u~θ07Z~ Zoڧ A:G!<^Oovnup44"ssoqt6D&c|-N?Z#9W7P3qn/'nizاW=dٞgn6ↁw#=UU5!QͿRu5ŇW?rǯ~=ŴkGf-L)I GAGnHqc**:a{y_`ȓq1>^)u혡3'}"z}uIJxM[v:7%h<[ʍ M\Yi_?=T K}DF(YYޥR,W!.\¨o$f$8=9RU4ZFC"77?WX px=mo)^lvf-|qJٓi4!)CwlC񬯩,aE:'9Y+p+ܼٽso4JC.0q@UQɑV1Y PW.&_!%JfiQ5mǫ+*o.9~vo7TQ/#$9-4؆Wn-(&: 6=Fx܉<X:%Na"r 3'M"CU㜝}]K}9jp|ʨiknhD>ZYP g19)M~lh!*Af߼ bp@V.-L[h`Xߤ~s~o~j0gy883&>'ZHn~Mtl0kmd~o?#d]?&ZK|luȺ֊FZptu|҇ Ⱥ.&ڦr"ѰA)ozE^A O/2՛'+ڝ)2mn܋kT0s$Z_CCmWuE6]xc$G]*'݂bJKzg_?f+8wyܸGNYZu%#8u tbyYx}ujO^5Uz[X/F~}2ֱpcgDH8C tFWKgD8F_coWJ rVvFuDf-|j\R$exvFCG8w8w;  [ (.:*~:暾-.l/rl3"|}tΈOwӦksT]=5+W6l{jLWX,֐6 -_wXJf3 Xӂ7[jcn_ S;u ]SE^އаGE^]EG| PoJ&8Юkezl? 8#ǰ~ k_P('F$-tf:o? (:: u>|<9zlF[?khd|`QSQG'ka&quGĥ4V\3:9m(v!6lym]}}e@.؄lfs#;o#[j:Y߿mD4{(^xkPngj* {miefo>#-n,e s!ę)KCYt!Nзܦ PiqIiY~<ڤ 5%Pu q9s]1s"]fJO8O:N@'J%3om} 5Z~X2μ}[[!dd;BI/MO.IeT4/s忷G Ge@ujOxӤY-MP]UUq۷O{rذ5E`c6mhûŀ͹p>pނϑQEzXw9@RQo <vґCO8"ŝ} 3MÎX뛪V"XL&dhي;{&E藇۲$f ]#e] 6 coމںzk=:=o?ϵ:B(l=tܑBBXN0'x ~`~-9^w ߓ!7^3z8Zt@zd^eloD%|AnXlF+(9 0x`cOlK;ja~a0M.@YYBj6[ܰw/M S2h8+%ظ$.6 %[鎛.7xp^ӗ(lkE1)+q2^{;p1}t57zi@=yC=Bϓ.|uVS;83ɀ N^i„(@@W?|rjucاRjjXE}=Їδ)"&ƭo?Ejk!Jg"Tddjú%wnnzja9ްnޥ x̮/ooET B_6n<3 ƖgmF>ZPVE`[ "LX>Zw9a)cz%OVS`ckG78ࢪ,ϷgVF 9'q`)߸IV|B3`xpaʇ#"-W(_oSY ց@GBy{(7ܱm~':8[:d"%b B22/BBѴVBrj~ '5"f}"zm}]}÷yo?;/n`5!a54z[PHwα3oހ轅`,s?dx!hZpHj\t1z"~G%μ [;-,RWߐy^LQn܏WR+Gm8a7=q¥I-{WP KejƊ_ DO-]W | Sg ^hE}QIIB׿=Ccxt窩`uIWjJKkJK__ 羾rr<۳A.!N`i#2zEEꝻ7VӦ*M+/,(PPŁ&ٵpGz"7`T B1T.ϙug<(AWUU=|tcʦ+t\:?iK?tF]]qח/xl9{%4g ܂ /TXDaTmtk7爢"rj㧯vԇ\>m[{L][N^\hj gӻהp6,q66mS|{(vl9oyypϫͯ ϲr|JjFݝG.4]8Σ%-nڸ+-F͇-gͲJIk KgeS;]tY1}%!촌$7!v񻅷B8l8hܼ CBq> ^Pm-JLDns,#m݋Swٽplh.vX_5rc#E.8aʍq n)o\GGMdec ݸiwf $^fWkW,FMܿwܶ-\-VC`ZlNy|q͘;,u =bSj+:p = 916dWw gؓ-t-cЅ3i?FMu?:.b%weF T@]nT(E1(D ?\D@@fdg3g9gsvf;Z[9uҍG߾Bo3<3Lq i.? 0ivogN^1^|; Ն.+g\6;/kfDdfDD֧/u8<^FKSTTDTLV!pN][VQHY[{Vz@ޜ1>T|ĢʫE"yرv0YJ29!D"={۪ oQ tԶ5sWO;XRBJbjkJT+Z=OLt)dLMi!.LxAP Bi~F$aa!sG>ww[$ iloO8igLvz *&$"b4c *|}2ZD2YJ]mۖI:--YL~Q'?@HI-}d1LHDJ̙5 V[Ϛ!J DlmƟ==ԉﱛ}7"<- \'] C $ܶm?٭>vdQ31%$BaZ&*6|1ųFurS Y:k"4X2ktZf.aBIA63G_{pޚ. k5+f_0e (,x"ild5s̉cC(ٞBB^;>l:Iv[\}w"⵽iJ=ȑG-GWQi@1Y٥o QT:va{v!4Z;a*]Tmǎ˖n ^v?*[Z``yr mu% /aԣ;u F;Xy as8:&/1jżqXg7t}.^K-[Nܺ&:]=͖\.>w-oq֝/ZKf޹nv7쮙.9R> /aùlL:Pxܜ9ݥ+Vqp7NpX'8ɨ.] bm_>}ǵՕ:s6K_%:s{aA*EE.YouMˋKvY\myyΗ輸aɲ*,DTP0W0W43:iO)%4;. Bυh@[ׅűQWQVRzƝ[&;[n L`5o@vp{ wgϚ8zam\!f5 K:5ӟp[Vտ4Ŧ=_}JJ^4.ϺIFv~ÓS 0[QQ9 /{8-=qBz 䔣vw䌻>PX~vܝۖtS}.4oGۈl`YÓJ .V­.PXc3sr|xᰡ%%v>j&a#n:؈ IDAT@0btܰmBvE?9f?'5$LldE}qJ0oN? cջR3rՔ΂o~ȣm٭ZHqr^cl~}w3oOcj*T51ȱOy/}aUU, pfQG QX'?4gk%i**o|/tUU3##'p ?~%<V2}zSݻ߿~/I (0ɾ& |6.voJzH$>@񭲜̈̈ȼQw}m zJJrz8!臏dan; N_n+‚/}͌>vX'-c ]E [ۚ$=Y6{ Y 7\mCkݝ;J(٭Nc\V"е9{ r rm^}+!h L2>#+HuN#ʤ) f1389f `D?|tkUE^VyNQEeYNs4CI~g>ܸb0Sk .<|XKS9+Zks?*}R|Rƾ7/Ұbk3Li8lh×51 )!gqřFJ rK"ܽ!=~؉/UEE4%EQ#m@sWnΜsbː];\zƠdFD*Yuw-D$9Y=zk VDE(NoY̔;v;mGEq7y] ~`mҞwQH{;}[jl{s┚4ex7tσV>6_3>m3n-@ys3{SlB*Y0x0bǢW9[` ts 'E:2Y9߷pC]f5x<^qjZ֧OQ>{_zmRWS43S43e]U;^>qfG?+SU^x2ig!X|d ~uҪ܂mdi9ٵz4!QK]͖$ElBiVϜy{Lk+*ޅ?{ ?+زmƵh**^!Aahө>}`.߸ ~/-w"gXfZow1={ڝgXl6։@IO,@_x~CQ;{-3uwaނ#o޺0b6UuG9ܞSA7<Ɗ`MGS{cYYDed$ #ٜ;~ߩ wXtf<w5׌[h(Dj80tCN4lyC:.}lϸΗ2Ht\L&ֹ`r w-2zПؾH/(<7eSl[=,D:_zZ`SQ);OՕ5:2A!*BtNEt\{¬_ssDG?G3FLc#H;EE߾ey =Ffku @@/';sYiK`yY'7||ԬWL_>wnJ~teϐkOZ(/-I麚m6y )- ܻLi7|NOUy_ =w85Mi ,zM.kG2dy!4q˞ן0l-YF3?j٩^nNRp8Y@py;ٙc$,hnhRĤ̈蘏oV 4C_aד70$pxmf7ʌ!΀(%8dX E$F;~rpi & ޷X_㯽cR\N+b"/rz̜OnK핞lf.ƴ"X55x Lyo< TJHc Ιe5c6֡ ry=w#ۗCN-@))[ܺh(wtˁl7cY0 3q$̺s\y|bXiNX\\\¼ϴ!.S5>/kNtt]_Vm-BHLVa/חՑՁƁ@39B {̦];R\:ow:saėKsL뮝Ȑǥ.~ޖ'71=п|Mvw5p#vf3Vq%<`Wה/6{ybtCճ6.uhv5R 10) 1)m+uu!aqq-M--M )uu)5U"D$ǟ=΂>Nu]L RCBXdj ;kvC"+h‹G>yK&pi53vb#2Z_bIެ8s{m1~϶̊l=s \&; |z-5,\NWmfqc(X*)-{%~WuN8oEXh~wAm()>u[xqxb*fI|)d=FjFɋ|ZOdEɔ`2WrK22   ?-NKo(8 H4e%)55)u5)55)5UMIQ:KL468fAKwnZ[atVJpX ܬnǮ|,ORoܼוֹ~D@GçoXЙ}xrTYư"8^Jpߺ a6gθCWU:}~҇+ fwwCI%om8l뽳͊.Đ9~+֜0iu)9*L)"~S9\zn+ISSSYckM9= UU骪:]^]RRZSWRq/?[}UUC*&&.//RWg34e%5`j:wطq,(-A-Aod<  mLUE [%)!5ӻiKvJm[=t!1JbR(d x4`)K􃕫0;fHyCUEEa.~z-/+mt^]=k~͟6|Y<CvgAC{k̔(hݒXgknNV!N^if,ף!UUS : )<Nj"KhT Z%9%eYYeYYY٩a֔6!Q(teIEEIEMIDP@',.>\DD:"54 pj6X 0fX/VTVRڌ :3qʱ fZS{=H `_qmNL '~ C_o?}7W?</sbDŤ S^n6@ħ]u #F;t,!j xHw{_CmA i!O^[rM;W>h)'C0BO+3 vބ $$![†Irrss SR^,-NKq ͨ4A!Q Y4]B&:4]"<2N&.+E~MҞ5MW/C %/GܼU<)Åűz{ޮWm9M)I 9ڂbL޺Q'/ݏO1U: #Lk<{~ዼ2%K0zpWcX$fuz ֹ@HLub (n?)d\Y]\\]Q_SW=!!1UE~>jhFP? I ED<>_7 gƍ:=)!vN-@L:ze-A}v뙫7[#twq#ۗ@yAL|ʂuJjjkjjjB&563ywq8@@8pâڐvYcs wȏdQ03։~ |~rl)W/Z[a Om{5#1L"L/K5e{@mA;Y5[ Ūu??I*2dhâ=>jJ=CPt*46j]VMMuIIyn^YN[Z[~lFRWk:Ձ"ԆF K_|q^s(AMx\.TVMMfDds-s5bs{ _tr>w3g2>!4k;}i/E5Q+)-AØ xlv8RV׷:/;M[(Y/x H pz?5%%NS]6F$zԌ.>zFzW@mh~} )ksD[ i^[N5v8b#;zuy[8}}8 Ɏ6!yY) UԬ5o<.)i}ìOQO6oo"}|z-S}N74:Q%:zz*OnD?sMЈvlbuPkma0c_dnÝrxd[gOmO.!a_uf@P}GMI~p))))Q gUVmmYvNYNNIFFyn^iVVYvNLj̇99\!D$*42]U,.!&}ӌF zwq̸n\o rMFR056$h*[qyyiM j 6v޻ͅAI%]q_{χ>K\Ls.6>=8%4uN.{l\^0y-/\[@$ۚH _tίg5k&)!6v؀ z\&\8aoYlUԖ7HIYL?3"|;{=ʊrGw,0YGtrR|[[`jx,j ~'m:6jesl^1@;yCT.0g[9i#pxX SoX405[PfDd? @U\Jpȇkף|))vv0$<9 RIYӠ}8\\$'Cy B.6'.u4q%;o8x<B|KJ?|ΚEc-3ۄp3R _0{P~t!< *ꑲ¤䂤߾xFQj: }}T"۹55.]SQ /26ř@7K c8,Oi IDAT@G $S 9y$t쥣\E6W^#nx߽mv@Ue99Qw}v=3"RBAf ˩SZ\ڊ򊪪ʪ@3bQEL").**JHĻ삢/qB"?%m[lMr8\>S!dm/#%0yP[AzRyE\w.Ff~{v0:W3'B)v#dJ0p K'NՖDYm-y##c#yCyCC*Ky,zd!N!.,*U0 1蕴t7ɀವ4T>wXg-Zwb7p/<hl=ݿ$1q*.&-y˼+G$q@fm\-㾥5}9%FwkJ o,,@qO={.sthoVޠ0w߼19bNgOT~MLOIININN/*(,g5!"""T YټDG DtIqi]RNK%t()NC]TR^TRWWPTmM.loe,*JINp ~'ws ]1oYա?~oZX,6B8m!Fz-/™G3dX# 1zM_,NOύɍs;?"2L& ; #F8fs9<6>z`Q‹N0iAP[ [4cʭ'6yy8]3|$!skѳ6Ɤ;epX3vXuV6|bӬMӃ}N$mL5%dIG DMϟ\OFM 'O7d6Uk??I"5xHKC1ɋzԾmy9ƒ!<wߤ9jˣQ&SGc(+8i4&%FT(5{1A~B!kP[$E x~OI8<ml] Bf8E$"XnX"PW={"&"%3Eg*HȞȌL y}8@"1*Y+YX0 ۳/^Eܞ|A z CEG}a/.viVǝ>a,1蕼,AA@̝2߻~+^vVvVς)q8_xhնڪڎ42uϙݓh(iIr8/`ٻ8.hnTar eeܟVla>^9dv f/tOF?|TWY`?11D֫UTVz !dprO7'6uaa5NvOp8]&*1xX97~p xEqś_8m a{zHI^> 'h@uУH$>@ϴ>f|$!U_}H?oᤅCUvb>~GDedp@`E)1~|JXg su(eS tOL_,!@5?%pXg*'7|YS[O"ճi=B"6`WWϚjӗ.Z1_e]+w%%Dn0`2)׷BOKaR;S8!|l"?@")7X[7@1>֍$2yȮKu`yР8-˃G_?H AD%5, s e~pcY=v#݀uлHh]>~$ł %ۏXFաC°N)g>_X6`=MB \X^;AZ8mx 'bacpy%UezOա P5RMUe%`Ƃ.Ǵ{b5m--&]f52vX, <|]97CrUMջ ayxB0lAmu)Hv}N_}),,,<<ںCtQ]*5\1Jk280kMC$/qɊaO8'JGCP(*+,T4]He99QwEM Gww{RJʯ}vA`|RF;0dX$Mu6 5{6G`c'qbi1URQ!-y!(,rE36sZm LVTT삍K&ר*ר*8r^>%7O^R\HT{;^wر|R[.M;cƊ=iyJ "slINu͐jb)h.ޞլSoĤ7n?E5vuގt; E8H;L :N+.:E-|(km߱Mf2 `#$9nީ×8p[  &tӱcB<6 !_<|!bŊ"n [|9AmTC*+\{SN0Vݔ&HD9#>5>yq_deFxn٤3Ѕ$,u42o=((7܁ՃXQ.)^TcpNy:󰁶Xgz<=O^FZ,МY]Ҍn-=z)WVUTJ.]|9uuplXG~@Pxy㽶;N)u`eόhC ݽSw@u:Y'an b2d!Odcd޼p#Z Vj$>..OxQaeCM-9GG]]]pM +=fߖys.;HHP ʟ> N@1Wr++Y6)"g8I_[V:㥆?ϋJss4dd{z|~`qIŐ6w8ژ %BťjXgiMS]q1P[ +U}f9^l|AÄErsWf?k=@=ALگ1AI`gΜ QcVTփȊ`d8w|ޝ<.kzh=*j `RN8sᒙEE`)e~-TЬ94_jddw_M kg?;;w%$-2%o^[Ø3˛H$X骭h|KIHȡKYUB iDŠ!CM~<>$/4,,~gq47qPff*۶ܯC]7Y__AOOOM0Ӯ0gղ>~L)7`gэu$iԴTOocc )mܠJ:6<7QJL#'ta t+:M !TR2 ~} ߾;,hWQrW}cwas3"@clN[jA5f0QQHHPBd2QWLL?\` @wK M8~ޥOu챣ձBv:VV%շ}sYJR&Zɸ1o_uУo~(&ih99뷸qg:""#**jUT#\hk3~sBDyk]]yϞBHZZtۖEEF?@ol‚8mV["}`"UU陳tQW7{B1Y/c׳ą--O2Ĥšc&&R(B&ۚ*;4\E-[1uulHps7A=y+mӣ<ʪaYNd8l):o~𲠨t w9X0QAoD *Й<&\+ۨ}f9n:=Z鋶yiSmfԛUVռ |t2z 8vlʘUmsu7V^:=HEUj,Ǧ[hpYC*eIMv^mLoy34Kf%BܹS++kY!Ӌ*Brj|8riDy"0$'?u"!dePQQ[QQpv8Bi}7BЬ_BBjx޺pq[pr[ϟEw۱k̩3N?!Ho+~QS*.RT&&&+-PL2Dž$57u@{wlwjZVf ܰnu_ܶnV07{~A}6vXg z-'*J:Hl, JS:veuo,gZ鋶yi?Ro1v3:aΆYi32B̄oyn5B˚V60o x?wσǻa-> 4Ъdz|RҢ˗o`8k\LLE75?oaea$B_>g6}sTB E )/nQ$&}CIK!U3wϏ&HVPTT&5?jbA/r܆2Ox?(]J{p;^8e׀qssmݬ3Ѕ")FXlN_ykݜ1C49/NLve%p8C0@{uU&*&iX p6lVƬsP-o3Xaa%!LM- :3\UBN6gg8&UW%tEIB" ''an0КCm4i4</&&la~=)\XXBhxN4 K IDAT*HPW]!.<!4am?qB/8F[A&tt'LF?}\qqBXDL YZM.""rsKT8,VO6m9de,))Kq{vDc@a/r𢡣L 9w> R+aMazV^N^!A@ڮl?ѷAYQks{]=ͺ*R!+ ^ΆH+cV9÷`$k-mLfLdt.*-=.<9} NPVrrե6CKg!n8#c%==6&4H࿱JZڌ[NEFz5C,)z!u}݇1ix+,4VxO5!"0Ui#6<.7SԷ~i\E3S!v%QපO9wBhܩڪX@LZ. }LuIDbxd(w.е]FoeOky-=:W dбl+jA`OF[YkJKY)]ZZqNhn琢/qCibX@Pݺ)ݱ0+(, MDg'O畅&=ػx?`6d(IhՍ}i=mZjߵkC ʒ$J,(3J}3ay{Vt/BHI gG,LL4o.O$k^%%5mz{n:||BBgᒞt7=/{aի- eB =a`qw<~%'ߪ]mZtP?r\}z~b]gO󺺗 Yw@T 4ۀ b>-G.n󲩓G O4ի^?uOv]jeE_Ϙi1bBDmΫ ͙+ } @5;ycfM6@]iTV__",(-)I|Go~Euu: β_[^ B|t96 hIu(UҬܼS[.goeeԾ9yzlbf~I ]@cBHHp|UPP[f!O:tƥSWOy0[Zgޱ ?|@oUUBux9rOP󦍗=v% 4YBSH%BiLuCoIIJՠ:CVF.IH{hk$8~ Rmь8 9>|9wA87'?OEyyu[w}X=5oNu@Rç.gnzcq-eTqb1E@BV-P[uU,A@(p۷Zc̜ޔ1Z LvڣuNN[AuW3f--MzO_4hcei6cZkr**T/8{ӷnbbLm!NЛZV ޾ۚՇO67߬iE6zܺmo z4PS?s\/?rvz!Zcԡ-ա@ "STAM{ϖr%$jw' :@@maz=wgS.:QEܴi&@RAr8)oc}f=IOnܸyދЀ&!AuFI)_y̓N=vʨ͕bK,(, t'iV:K-w6֤:Y?S25(o/bAu [[VL}M"V,:XRwO:h(Ϗ~$_қТ<ͻ>LE B-w>y= Mo_VMu(s,RXXf3":l#8샘p~݇̚"c>%BڶV>n@͞ӗT:-PgHvZ2K Wb8?p.ɨuw^[wSQ: ԉO_?~-0$JGK6{18"f2!yR[@&8ab%.!ɠ /8۴tjWyL3,gZZ|@`'!%iaaVT:"譓eo7zZ\R[@aYBTA;WTq9LW-q<޾JYTC/Q?|'!%@uw3 {)+S3]/G05{v{nT'շ U`ls½|"!.!u+L8 V{G[4,,'==ۇ7AJ[NqE6@_5 N5gO^0bHӤ:4\,&RPPDu*06hW\R.ִS{KHjKu ZfsJM-jQ}[Q| {U(6n2yRk*$$%qsOI{ܻա͉/R} Z*7kP>}2rhS2jkn:r6TϴXq/_$Yqjյ KN@uϽǮ%Ilqj-uC9DҩvpS/(Ҍ  $%%{Fu(-)2A_rrʭLZZajnܙ٨12N>N.cl{\>^ @L"nyQGZz&!Dy@`ҩ!o9ڷ{'6IuriQ_{5ciItƚf݌Fط6nܙ@uL^i)Cߝ.' pxm6 IhETcvOogB+5:@Am4[O獡: _ǿ JN&4n|V&][KTaaѫ)Y8E%z,h,&=Okh7zu: fMQP4ktmF޲9q)IzUpbbb\nfԌ;w6IW ҍqeǫ*{ZqvVgđ ?Y j4ԔC£Q["5=$Aj j 3ǫw<p5Y@ef'"ftT:aSzޣWc_4sD+UeCf\BH!oNb"-;&D1h@$%7-j7m 3#)),&W_Fp22FFP7flVFQDg/;}+:궦:@Ŷ"STY'}}ǮQDzwLbаX4{AH(779,%<<%,<)4,%,ׯF͛jd֩S֚T'񑚞7tڪ>̈Dd^BH!Tq#/+-F89'.Ϛ08 9 KHH˶of5_b}"£s;ZFܧ/ R'|.pҪFj; WnB#KN \v>y]V{qvVRTL@dk zr%$hTgіPMD7D*@VSa}g[TQ:!~& tGSS}5| k/_F$M LH [ B_J=:EuXgEGɠo^6upKA0Eoa쟹 m5U-g\#@] %0ALhusuNl: 4:-/..IJJs9|%((|ZN9l9Ԋ?J %"KDDj;NQMBF }}[00hAuRh@$$ |{b!!/;vL]HU^R"f&ϟoAѨo߷:xë}; s;zP>0"\[`lsԍ9ydbo "5j fnu2q IuhMfh3h?8y:X/…{3g߸U͜>}x^ /_zX Q;w-40hǫv%!ddAz*(DDܼ --Mmvv,!$++'((BJJҲ OATؕ~zLƏϼ_:x9wi qAVVz޼ !~]~杛65_Enh`?7oxN Iz8l6nZ p8.|ɠ0wp+II C&.閖rD|D1T[AQ!P0$`* "oG Xoߩ@egΰ_B!_?ˢwyyY&~lBkyFTRRӥl_utkjo׬YBHDD̕+<<~533ؽg**ʙꗔp> -yeI D_A OATȑo~:nJ+Ç/:t2!3m3\AB{̝7S dd͚5`4!$6&w/)o7C:}7]'Fa4("='Rc^:B̉H @]@Bٺb3̌T;gdx^˗;svK& '0 vBB\r !!{taC#7yӱW"t\jؘzzC tק xӠMV(v$&B .݈ӉS.ܔWYDWTtǞ `o}6eT'(پ:BSDXQq !^5@m 0Pp!8(@dv߂.:2>}Bua] P0T "-׆œYK6P455{r8'O KY=oE%}JoeF9|[O/..] oo-!\53/ lӂĘ?og@zwǏ>ޯH]m !^^^^^vW]5k֘r҃ܜ?g*yvB/++'++]% *^h͎#bk޷@}&Pm8AJ` .oJ]>`3WNiCuh&Mxnx촫ӹ :j ](wQ!!-0ǃ!<y|6G\BmP%Uȑi4]ﴴ %%Ek.wZ8ǟӥ[vTފߵﯪ*,*{->W7C&A)*.f1Tg^ o=:*41zAJ` Pz;q{VC P?jq ^| =!͍ "WɤZf> zb뱯߾q0m {"dؔ՟P|OX| Tg|N_£b{{ںU?h4իWGAu'uMMtF9뼊 аkۜٿr6HP@HIJؽc\CWɼ~r qC'9sCW#`'ڢBEETF,&pÜP(?{( nb{]},@=G 1ۉMv-QiքP‹ɠB R},&P0Hœ p@ŦjΊ~w7:<4( ̞-p8K9LZ6J uŤB EM/SCׂ)!nݺ ߴiS}rx)!?ѾM~ @#gn4aE'}ǧ‚K^ۦHc[ s"Bz'$~NN:-PL!PA?Riy˂i؀T!"4Bwmeʔ[篛OW@^tɓ'>|(..VSS߿+ԨP}NA;_RUQwnGS_!b2!Eݷ@G ogEu19\POuԥO-HCZ "(M@-!?j *3Ī쉶sVnc1$ xȨX˗/{{{S &^w\wc\™#`T'1"޷A2K@5@`@x` @/6/*ݤ۞\wW #6uZzhر}.))y?}STMn^mԱ;账:ԟ̕WyKRzoۧ bؓ3eb NNI˸{v{nc޽,kʕw...9s(++B\ -ZPZZYPP --֥K6mV*  | 0$T!nPA:gyaruZ78B'$$́*))R zzXɥwM@}s\{uO̻/z|z߳~> sځU˝ɧ_O^yuuo#Y*%m=fiJ7BH^~3~Ti)} !ݻtܸLL|FK(o)..:/%%#fAo_͊~XXTRAߤ gƲ;dK[pϥu.g-4ۘGtO.{/m37]>d{VvlMg?WWlv^JJK\]lO3VF}-=Jh43ydyyygggBHffq6mzW_x1~xUUU:ޤI+++OOpl٢d2uuuOVy)FSQQ/Uaa*F{9۷oOϥEEE;w444644trr*.ʓ]pBAAdjii-[,++/&@Y?g-=~n]PX0]CY9ܻEǿv^~yAVC?˞7NnN|}[rw_ ]ps֢yΫ^^M uuo>&>zɝO\OIֲ RBo?M5dIc&r:]RT,,E4{Y|uu~!G{!/'sx+Wm[HVy`˙U}~oېr#܀Uɇ)Op;RBo/3|z^~o¢}ǮBYaKCCϻ+)h޼yll,o .joBzdɒׯ\rرZZZ @`r ? ' 4 4U Bx @9-M /BkоzT=ӗ:{~8 $⣿|#"~eIUIͣ jRW7֘;UdUťŃ.''lf =Sܧ2du=LN"}}k*ūfޟIƻ7@߼ M0֭[lGGGMMͭ[ݶmy7{ݞ[n :>g̘q*O2EVV޾O>fff.../.]W^% @Oaa/IXXXXXÇ=<Nmwcի fFھx {Bș|hFmṔGCC#444!!Դݺurt:=!![_^^۷o͛7h׮]vj=Q0@ć"J`2!nP} kl'9l/."$4#OEoj?r凂_WGATt1-7ŝw?獄A<;|= ;+);|+ ?|6Y&|oW"B6ڐT(cI(wV~Dӧ9sB&LPTTTvɱc+<|ǏEEEIIIr[n-O///wwϟ?2rڵk !vuGaa;!+W=^ddŋeee7l_~___SN}ׯrrrϞ=ۿI\]]/]Բek׮w%22r۶mAO4~ &w0 C[6"YN>mm#7ӗiD" !/o;PQgv N&|NJ|%$Be .ԿL̉@o5 82+;SD͂\|=ɄrtsOIU}ne>xfyUܒO^/(,]q8coj_e KJJڴiURR[/oBLLL@@۷yTDQ0@D 4aOp!BmXLƱ]#?|u2YDOFͫ#<|k~StzrG[Vn;F*ڏW~(#_gkӣ]GqOUmֽMJTQڱz&!DGA-],體W^g{UO{8~r TTnnUvcʩɩ￟&Hw !jK!k¹ VooDυvXj?x;r䈾ܚ5k!YYY|ŋ]\\tttUUU.\H,WWAɩ~U~z!ޒǏږOddeVV־}n^ }&O4eʔ{$ɓ' !/_oڴ)֭ە+W!7oެ S[s{8q6Iu(Ioz^ٳiԡ2Ǿ˷d.r)'''###&&`jJ]]Nddd+**WWWwtt6m"0@Tv QMh# ϜRD5[;V\PCmvre*{e_BR!) wW+?_t.Iͣ (,]Έ&?.m44!?~ O?ۼW^Պ)\53ΓϺ١g41###.IOOݼzѣ++((lG;ީϟU֬Yckk4w\)))^-)4hPffUMVhBȠA.UA$9Mɿ700 y1aȘU ϝ2.i)IINM:''̟FqGJR_!LkkOorpCVo">>ܩvcRUK뒑?a#Ѣ /٠﫡TgHhDT}.j2'OWmAKV͚*J H@pD5bTX挞,`;r KHHА-i֬)ӴGBBB]]]]]}Q6a"> 0@D 4BXOl6M5`db"گ|WդNN 4V1&4f7]ӿ< KFcjqG~ӵe>}@u.!d.i&.;k֬>sܜ by2d}} ggg^-1cʮs~eff:88 Op$p8e\aQݧz `uaꝧ)J8oY='nXr-U!O\3+;k˷picBFp¢Y\T伶K7zex,B`+~bs1]ӜlBVm;~{JZFaQG MYO2gg|ϾxkykNYX|y oޮڏ̦'F-MަMB--_|]ZZI)C!D@y @YLBHn^nQn~vYV+Z6']Wںگ5vbs>}F}7EUQ]vw>ܙf4kWo~>X{0oOOMJBLLՌ=͡6uXBȎ;yK}zIIɼyMkǣ &޻wҮ];cuRA{{*=)h>_srs&D]B1rHV\5uN]Y'Yu+{?h۾o/mn{Qa5g_[[2ت{Rך&X,FX̉@|Na}{t: F*3~d˒.e=;?8bֲeϞ|< |':k~&VmQ3:BfOe_Y?}D-;*۷ !!!!!f+5 a"a֠0h@p!Oi^1#L¡5߳>>D*T[WU汜.Jӥo}wT2xB"E'L.&6!$x'xפH{y煷JJKүN)(ARSS#ڵ+33Ǐnnn3f̨ޮ/_())D4y䒒V9ܹs߾}ۙ3g-ZĿKs! 2ɓ'N033"7/aˑ ۴jqbH_7+UeTE9N7/ػagN3Pe.Mgim4Z2t 5U ھn+ܐkg6oѼ ]Jbm5o?~wW5ycG5fMa}ii_Z&N} [67 (ӥ ]J˧d1l!LmLf1in\:֩ ˞Ƈ/nFjjx9=gbh$ʰ\\ƍ!JJJJJJrrr7<}N:)**JJJJKK2\kn*">  9 4548mj֖'w{렁DO@),>gRe5nnnUJ^ۆ2˟rSwEF'Zv}~C~/E\ȱw+|PnXn{/}[Ao݇MIƿwUwwO?wUť6l|*_GLWna%733 &e'G矛o?x+3ӌc X GNt]lٲ{0a¹sn… Ǐ'lٲeVPzzzӦM !ϟ?/0ɂ tViggG7 g~!kY?֦70dJMu R;p;pzj_X?w0FM2z`ᘻr߅ ߲diBvhW1J[a` M 4B񷮎LtF9뼊 JP!ym3WГ \ӷ@es ZBR*UiDHn+]߫sww/`_A^VدۣKN`WI\WDz(,]KyI>K%CheqwdŧUՄw!2t;M?o{0%`TMMtS3Uc pA@-rrrj۶-lݺƍ]]]mۖҨQsVu[&c}}}fw?Ё?~lggײeK:f-Z*PLhhrn:5zZ~.(,Olڗ !V3gGRD!D} D4 B8;+׶} IDATK3  A@B h劶t{=vT!\.w-/߼uE&Tǩs5[ u ͔)SN>xݻw}z+5/ڗ_Pgühfo#Dj6.{꿯*",Y74\]YL1n4:C@Ħ*mB(u&} #[P%BvADۚjʟ( rh4>}ɮ\,>>)SP/~dLt6h 3'QX@ 6QP >} !c[^Ou T1X𺮬uL$b3 4A!|6-?~$ (j(w~#.X|db@-@ *O@'0`@ PO_:= !)q=:S:xb|zn'?17Y@0t\PC DTNU'j|߂BFs8E:ZGw-|ɉTg+,ĉTvGQX@-6QP(V} Y4r A@4REE-DTG%mEI Q4_esGrLzS@yC8;o7V/)ḝdի qBS2.ڙԘ, tbSoTDW:m,ƛ]+de6aT4 9kN^ݽkGQX $,FAX- X1pݓ tzQ1X)_[@7 4Fl_Ҭع  v4v-Q:b1kNBɘ0, tb-Rbѳ T4ʱ _ҾXmTn^C'2zMY2ṁO_RA/F/ {Ϡ7? SoOIuf:p]3W^8FՉ</vs"BT[4k}cХ зJ*m4Zzx1`xÒ)\=: +2qeGց]bbط2qOTP3E[⥂BnNi= 6fԅ;^DQ@|gNf_<"ՉX,&SZ\"߫YRYTƠKok &NIyp=;VhԬTgy%Ύ-G,Ro2}`Fu( R^{-3, [bڂVݻv<ݳ II{Pn:jY9TaQC:{4UNb1 !9-¸٬ݩ‹NBmk !cx2=G}'2lrrXXFUn?xl#Fڠ]`B1[@a >vnn^>Y@H1" xcm?6=;>͛\>MDu bb^5]ةLu"A9x_PxSbХP[b揵l6s$ۣḓ@bưCۃ[.z|YD=r9E78ΰ:T $fMG5< IHJrTM- L;F%A+[z/Y]JY CkIu"A9xV-{T5味Ÿ%@X>ǒqC/qz",'f}ѥLTQfyBR)>r%7/,uBȒ٣~8y~fZ1?KxT,YNf.=̦ۇL::Ԉ4W@ux֨¢к_j 6wf|zjzfWuim?}]bWO_ u7/h$+Mu")]A (M9h]b/9leeX;]4b\:Nikg:r={-{Sj4;/_Bg9HW-Ј@5Vsf17QJ~" s3W^8icy@ma46_1럓ӨBR2qu.[\RRׁ^ On4q;SJu /rteT9lkӃDPdY߷2sPUN…FQ6 T[@mrԭikؽ g97}=]E3@fœ]ji7: @]q.ˉ_4@ߢ1%~Y S>vLI#mp=k s" kǼ'ĕE3Gj6dW SwAq7Pb^5kFDPdYy o%SB#?^ u Lom4(S \0~͎nxRp#gow:7+;RT:'#m(} !̛ϲGR2PP[@iݪ5O= @ ͲcMqA ԾYC9ԩ6ՉHrPB6_Aj_j ![ g6guڼ|(>l{AutSӁ3?}K @a79!l;^{@uʵC[L$m%%hΛ,{u9s}Xd qjAwTǟC*H*{ZJHv(P"ّ(Y -S{TBd;?}'G׭ës|< ٩j.&*"\K{"4:H_[-v TW_/(]Вd@JR,i܇lo ͱJ7=Ki̳WfY^ i+DhB_yniںzh!@SUum;Of !*6F8vexPͲg@;OT~۰]c>9;OIu !BKȦ ЮhI8[@Y5X{K7C<@ː=t}ׅŴsg:m޷cl{)E@h`ц:3Vv Q]S+u l!dxw>o '!.^Zbx#8E]}wȾV䤅.b02 qںZ!trgX[Z!v+Z_0-#gy~aK58I є=}*-?ܾlܜ#Yk5-\C{< x>CM^`'(<8Hx|O&0Ҳ Sm,v/MFBtLXRkZ郁y/ -[[Qg}^tR`_nM1ʿ|BY'YH5']sv /"+ZRBztrh漗6UU5-|ǴWE xP ڕ4~s)u}1DEx{݂&]m[ԥi@RUB WiBHݏy{b)>h1=u9ק@ϋWo5;m 39{(HW`BVYWlCI)hu55uL Bq&9]񧙋>~*kM=u9b$fLg[DI; 8h9- Lj_ǧoQ]SK;ZWӷ>iB|ʯs}A= /_׷B/c>H?:HDy>{vKCC#hE_ !-*ENixJG5=G{,xokU5rGN]06y8 .#u Jw0om "%!F;%l!DNVtR`eIN]z)}c,X#@+y"s6{ڄ#IJWЮ`/cG ڝpv !hV-  %t[4g{LbF wU閕ƒ%i9|„yy0"T%pߖ.nd˖K7nVQ񳄸P;\ug !|66&7%X>"/PtlOEeb>.m &.Y0YSG.&#%?uxVnWSi-g ! cIvě8lip=.3 bb3Դ d",܎vp<iq[SL~}Rm9ŠK>wĆubѢYIAUP9S ?~麎Wo=lCp+Re0]Ic(\d",H74WaSb"){<+--çnp6- (txf8Ť'%)YZJ|X~׳o4;yٙ.:jh_Emd$!J-¿ #sFl 5KJ;v]z"",e) Zsٗo2%nAjӣghkhh ڕ4~5R]m ahy2RℐOG߯(uKprGںz9Yo+t(4V?u:5$Ŏ6|Pzo>3ǹIЧ"Z% - Cs}] Z9W+*t¨BFhbVzkl@l:癬;}`;u!&h7;bo#dҎn&,NXH3f bn{Ua!DIAv@ 9[@Hp_e7,pҲ =u" }+Ԍl9F>|>i]`AW[I{). -% gphp7E:(BkW.=}0#fbk`0XekjklR&!82y*B郁:L~~Q+d$'2@%ywcswchag Lrڰ~!ip6W[?Fhuv[VYK ԯ'"-2RJЮ$#9~Cc;pׅŊhWy^]M h˜1ch'-Mq*MkkHezLJ9v4#;/BHlZ!jj SXwR|466Zkke_F.J*t~3,Y6vH!=>*P!K#&u!^8iZcꕣ5'X/4PPXDHJDm:x޲)/.NPXW#L-/nby~!..x< 72wcؓP;şhpv;}7:o\jvW}iW N- HJ̈{( ]I 8ސuLz죝IP~=sWۏT5--a 74Jics CTϴC-Wo=;wEε{s?TU׾~[ܫ'n\f wv+yAq3n{J0 W[v~a]iw`2կeDx߸xL3#;/^l7rL;dˊ%9;ƌ7:J}-fҘtZ5s9"/A;Upl!DXH|jH;A) -}v1ԙqpGډ3/_ij0|,<ݧcm.,yjg;;s(\Gɤ`O%^*i@3oaRU]K; ͻOd$+v*8uI^JmKrQQ IDAT˜=˿TҎ$NoJh7`.Ӽu*2ܥ;æ/w؁>(fP $*"n31L޾Na?n}:l`/f !L͓L^5r 7hGp $u-#"#gmkkg9+j&l:y#୧fgG\rIWn}%v(+|͐X4Yoyxx7t-_zK; 3t ৩6wڠ]Is'03wn>qi˝=Ë>`fdi~0l郁x8|wEip9 qQ[YQYIAUS3 ]\z=zE;}bnp1&퀖]!i/l3d0.v'( ۺj`kw(xBɅ" 0u5MM8|k޸Kg;- _yfsstNٴzy'YiEEQ!UaB[1=/ʹy#+j*`*tإS{I vB~xʪں/55U5_*644#Hu %ױ}r2=uKbg&?[{OdetjO;p4'̜#:% `㎖zj'(@ YiT7qś;H.NPXWcƄC;cS&7_dd 6US[;bc9v@ j'XPXokm|O%ysB㛷ů}x]Xç/_{ 1!Arۉ0b"?NX⏥%}\Fа*Ϙ4r䐾||#PtAʴ+ZB/%8?+cmL#O$-p*1½WWHJ72ޝ}Θ6K,#˨d_u Q\lP;ޚ.`0:)J]唺ʵjj^}KSNAzFfUGKgӮhE|ZAnV^nH =+)PJBl^ZbӚ-;eK`uČ]b#;/-C%WE8,`pEN- (+kosz7;5tV٫j,g-u>w6@V8OԆhE?[ФɭS_:fUjFvfSr2&o>bo:92RN&̞fN5MK⫳g^ (h{^a0Az:,}jѸx3i-,]1QUh"^-hХc敗{" WriG/vV;Y9{m {m/drDQMVm;:5#vT`X Y#f%=emuS=x68eSt<¾T|bUޟO;lA>ʊkEEg5^{L jk蒘cv7|4\gWc{z9ю+7rLYho1H}mu>>(V(ߩ̪ڰ~'mZyqsW\v@ `X9W5v@ق&N>) 9;҇R5~t$.RJRv@ST} a3=5ڝDo=z㧲q-.ǻM4椇E;?+;gͻOhGCO.&*>ƂY/ݙȚ~cƙ,v{"HAz.0ݐt,i_8{'E-NgC_[%1xH'Yl`ؙ.ݾ6.C5/v@l=bRV$f544`#l^`u޻qu5iЭ,,hWRuM-=xr#}fEov6@A hWۙ3}lz[ VlxpsnO;v@lԆKpI Ч ׀sR*7L~w=Qk.YML~~c]ۧ؛>,&1-PRjam۪ꚬmN]:ЎJ'yX~bо1<^j}ǹt~Yy1CZf ~n@ۜ. l8lT\BBI}4nΊ+s׉ ٛfE/ԚzSv~k ڕ4|iƙ+'ԛb;裬-2@%={Ch4ωT+tWL;aWt攓CuXu[wֲv[߻Ch/[+vi/-dr.elC|٣CYi9`0>QVTZvAzFO95d?f<{udUm[_n<9qu>M4 +v}Ғɻ7mp06sچ`{wO|$nwFϲr TZ6~򍮅WwRC]m D쯏r7Bg#}[n e-%/0/pQCv9f{O>~`QNVz#@7飢hw;ю4!eO3M_m}6nӇlڴ:ml)3\uK:t"a!V:"Ohj[@&I_T%<7w=GYn js?(agJIO;-`݂2Ҿ{&&>̭sE3S3i4s຺Vtv;m@hUSTgڸ(,&u1=DUum`1sn]gy.%>ʊO^Ӯ`8y7>-{jbsEevW[9qL~~!m >>U)o>K3YUQ9Ƌ4Ѯ|VRK7:ۙ-E.œfMbL+#mݹS|Bw$fx53b?s}C}G|j,,iQ({"|T;A!&̳]>8*Ѕvjs_7չ+/xu6u c<9c(CTD86xFM1&[VU.z+;h,^k)ںIG=C驤hdkcm?|OolG"c2?)ulg`@~vti9qSiyjlCNҹY1B_S[7D}ۢXLM3t˖}{G;-][|?Tbф1ߗʁWz9d_ Im\֡4՚_u4qQMo4ݾZ;۾c5u !,u'/MYy˖-q䷋N1tʇ]{4񮸤wϮZ_$O*n5{O:vv ^9琒ׯ__x177wކֲR]6q(5 Dh]uV.vA?ܲZd3-~~Kζ>?h֣>~[оtw+xنkcFuXۧ}rÛwpΞQΠO~e?kŲy e ~Eihh-HKKKJJ v֍b IKKWTTHJJJKK.m I{Ojt՛4ttjO;K9ӗ bX>P;լHIpZ~*K&TsXIBHjFw%N\25#n4]EѳtM,(,/=9MwUs5!7\W*]g>I4-iÇl !#m>|>x-Ty;XlUwW!:ߥ(6iBzڽۢںw/DY4{2!eKxBjVSNEљ={*B\DPQYu5uAӓ_3c\]K*AFPg8{+;X,[ r~S!ٓ[^^XYYBH={_Qõ;gְ'5||F͘4pƅ]N2@eBIb´Z٩6/_Zy]8ԥZI9S~1})ƺ?~F}mԬ4X0{W r;}]X<`M-}4]RKBrӍMx9u ^sN;rĀQCBF8w0>hjE?J1b霓箟|##/Yk8wҍk} +#_4]xJ+dz4ݲt'ӓ_?hgڗbe-,LIEXbsmKxfCzi1 ׮jzFv^.fXZHlwDek?bhS 'dP/xwn]g|_'fΦ= (u[riOBmJ;s\NLa!*lͿ2Rj"BҒFf]QFFBB")))++tlӍ222>oܶgG/]i, .Ѻ IDAT{&z&j,^KaaoJ<ͧ dڳyi݅  St ܝNP 5ɤKB{wXmr8j??MۡWL|gٵ+o;yD͖6ig{쌀Woj9ٰaäEDD̙ڑ1:1cΌoex^/)/I8v^=.?`5όj]FO;ښ!$6x-n&Kƥl\sfR54:eGa qQ,;}Ǎ'Ke*ildx•n<+\:ikŔv !# W*G4nrlsu] ۧ 6Q ^1A4'u ؎ƅ! odo> pTrvG;d_c#^BF\v?VV^{,ߍֹYQFfb2OPoCa1q웴C8OAaщs! nROIAѣK@?{y;4 61c` onl`S4Nuж _fwdn]SWD;e4q۞;vF~>)\e**BEĥr5[</f`05ym߻5`oRۊ~ZkM;}GUUp;-ۙ#(+K?e'P/zΝB;$khЁv'-)neme8pƅ'2@EoٓWpae=o['/w9(.%5i!Q bEizKm;&_t`pGןp}Όq[8P;qoBوMkl2nl{)99c& OKKkMC;V, W$f0y0}mYF 01&CIK[.KVYc.} m74K>-,KWG ]3-QZu 6X͝gϔTմ6a$&-46L53})-x:Os`/1&Iξ`OZc{ov_v_m/{SCத.1trٓruZd-. SpSm?3Ovp$ qQ{}9Τ@ f 8zZ׃sf.\ɝ浪i+>~h?amL"{- bϺ2E}Ǥ1Cd"#% ^{|o&A[(\IsZMM7h?iP!XB\ ա}U+O΍){<5tF+6eTUҮ2}mLr]jsE1X,ӗ'̳]jWYZFD[cGN ]ikI V޺U:wl?uCyEBi!޼->|\!4aK٥w/^*coJ8L~~W[hYs"`w 1C,w:fĀܬmN=B]m .))!6m*gʯUmshεTgFջ/^2=Mw/[?*שciЄn#.&tq[_^=mQ UY8sv @3W|X:;{.v4U0|gɪNFTҺkXPogG0;rR7p);ʄŤް? FΞo>`!%p q=;}&gʸa#gwCI):lLr]jsE@GMmԦMu5v*['#iL0ZhbElGDXH;boOe[S 0=]L2s~DPVo<1NuvO6vgwqKfD%;zΘ#?ͧ Yoy|7E#4̃v%56b["`c@B\k^!fNV7j_B;cS$O;>.AZT˫Iwl"/wȾi #.uLMm"hu5;cSM7^5*/ڙ. ״؈͒L|{grn8L-+ùCf И)rp!TbRN336mQ @ɶ:1Jҍ4M aPk⽳N&;Hj6a!AW[s)5umCӎ`KOSG;8gLba h1qiz(N;-`CanJ?qiӐb {xx^JZK~oShw}zH[lYk|C-9.K?sC-ܜ}C;ħ-e0[ _2^;} Ә5m4䱣kۜW*1C'YW50\8cќmTU$JpͲ>R=zaz#ky||xh47dfݧW3"[D$Iyx>NXiFv^൴CH҈ r9vo;s+53_*QV?_sb]iBHAakm_oo(d}Y{^wc@ml.>Tத?.?JOQȊKtM>q`_ݼD]!aU- U5,¶ҝ;v puNXmBxf OaO-߶Kx/&ȵ;jZL?ځ;w_7}ӓ>+\N_m=zuW g ҫGW Ua1Cԗ9{O7֩v\SR\d;} h>|??$?O7,Q_Ip.6h1Qy]^]Oʈ8fӪ'm؞qj]}=FQ XW|JHccm]I߿+}⭄,վ?| 0ϿxԶvPᳳgʘ>;.({_&[*^wGnsL鋟FMk?{fOQ!-^M?C;8[hT1]:F0[oN&NF^9n`ڝyz*[KȮZExQ]O'l Xy~!!ks/_ m=hg.?Qg6޾>d s5h/-A}[?Fe%y{E1KhyΌq;` Hc7rܾll!ts58ѝyhh7ůUմ0uΥlZU=v^?~*kd>Iwu8t`/>1l`/.iUA{_ho72s:H[LTvM;z7?.cn{iIh!,u8sLva`@hU%3^8sLέUC+kLQӘ:_Oi*V sٗc ^8һgWm<㧲^c74~>;HR6pS`Όq?`74%%h޼Xhsc ;.rȈ3CX9߾ѝ?DbdE ^K;xd/ yh{4кKK,?m_73}'rxmtRT|3(⇝Y+6WU ed$ǫ 9??߄у1XX,VUusJ = >Ƈ]>18>KCCQYVFW62BId7!*DTFD*suZ%G+6:~Kr?r2&|:"hvªuhy#`at14,[g7N;S1z jn&5#g՜T]yŸBHjVqIAXq;m4 xyc4tpw;c"f./-+]pZj 9g٦E[@Xt>ᓗC@|@]Mow!@ϡMYɛ#)!dck!OY^SځTVUX}-jޣ;m*f?f1upupLNܛaWKp:;?BQA.iGQ6RTĸ#. ~@L ('#=wf! 9jtvEwci"MMTڵzm]hkeI{;&~*ªnY+$!!d2 !g>'d;۱,^VFvZEeU1K&muqlcC5tpŮճ1Uwlv,y|C~ @c%%%>aH➤iPb0vٺOŴ[@8zDJBbh/!Q*5 ry7sO_~YblS\m;1lMͺr-+͒ebblN5B,╣+i'. "lޣ3ʁɩ5or6Qqjݶ}Vm>ջ>܃Gldx]8\nz:UiHYw 1e˖UVڳgҲC2r,c0ȶ.P4]ywşKuUVUnw\- zYQYu4q\;uaMlDY_~=<9Spm#blu{SxNθVw9OP |#\KyI;~BZʢU2!3s9X@a2Y WllA297&8as4Nb!>KϡFյ/ h ryKWc#8,_ܿ{MmGveջٛvx-L.&pyF̸󠪺p8ݸӐa"NYQ>qyX|Ԅ;u7L@ekakU~ua*촛nyvS>|2ʗvl4RNV gN|Бӣ֚yY4sKJigoY~ÅI!՜k7}bbb,~XpC=ۧKwjA9)ÙLeia2m*- bibcjԜvڹh 5WYyeWn&_ܴŲ0tpw05 bM(+6USVPSjTU]UIKZTօW&5#'n^ (z&'+:j؄}{Y!yI{tƶ/WoM9)vl4ZRNVNV܇\8s[gEohkdh(O@p(xTtάM4ԔhG7Yڶi=r"c7,Ҙv@=/cP M~}TUxhn\0KM@c6ɿߢ}ikb*[ ?v +w8ژz ).iM޾xZ+7f% IDAT.עS_WTTB] ?ڙvޫʪf:*85Wbo5;Çn\t\RRL@ݳ]o>xZUgx\\\˜Ҿi˖w߾baѬoc;L~OVZqO?}%%%>/' fڛg F_ =ҩsVTTBl![_/4;39o++%mmХ{wV/0 ?;+ v~TGSQf{^ ۷n.&lOW]Mw^$I;@ 5Qru~a+в'ڙߵ}oB\\!k֜~QIIyU%'? !z%}N dd$nz4h@ٵ3왜?WWs߮\qbOٵ3rJnqqyqqyJJ]i/E7^}iYYՇwH#h(~{9y)&\݌_*jiZ5Nr4;w hizdcnrhQASrB:{HHK6+?92Rk'/qg+(]l-F >eniR2}!ru떘/-\[Ԛ(ާ;!]AYÖ-^< -֮N9|=sg611˙9:-5 wΞ|de3 f#))-kEee=wjk8r{͑xǏ>rLNU%_ݏ_o> ywb?z%৵?Q c֭F9v (2kS%l B÷EK>Hn = G{cܴ9W2oryz:NV6nMh66vn uiɬ=nbEI{]%$p}{WՋ&+.a1'^=FQAv ~ [h@C˹m[Vv $lN;DBKM1=]i`Z%/O{/O{BHr3z+k \ZkkюhTTȚB.n, `hP{+f5PGv]Pr2"۵ ];8Ml \.R>>tiStBf QQޑDqIijFNZƝ[,jhcjomdkfb@*7\*6ΟlsײbJIӎ];NO=JJJv:&Jv&N_lHYǰvmi+9Ynm:!p8܇oIȉ[UblqsV6ʊ{i'61iɘK>x%.LW[v@Ek=z gBkG;96EB݇O c2h@Jhb1M uZhRMR[2Uڍ;CCh7tMN}v,4];[UU+sjMCUyY[A@m J\˦ d~lyKT;nkB).)͸}{y ʖf66l̍c=i'4iٽt|XlA#̚0pz0RGWJLKI#:Xn?ͻ'Υ_?6u!!:ޣԌwb],-%inLA^v/i ˦:XL2ޣK()юeGX.vn t?8ܟvqsmv7obC;@a@Xٖff~>\.~wR3rf4l`c`cڶY3-uڽ 46aa;ߐ+fjGW(/?4dAN\mi@_'&[vbڭѴCf &abkb;!$᳴;׳/ڴ08Xjެ)^t. lwʨ<B)a1Gdi05Pktu4b<o ]mmLi=4B-[l3ط!s[R3r2| Ɲ5[s< Ue 6 %%i'f- Yų|;.9W-H1[Ts8bts7lMlB4151ӎR,CgEo`Xf&-[YZN(o96X~vq1$ MEI>fs;vD;T{W-{OѮg/8v@7H9ژ:ژB^-Hϸ`Ӯ% , mLZIIIN?bFLf`l{l PU#];v4;ٮDmm{e ]M-Po**mԧvG-kB).){-[o? n ^zy5v{ĸ0;+E?dX.cKXx2DF2RR31[И$Izqi4*-ڑvstsY92&}*,'+mR'4VF>aaAS ET䣦>aa.nhaX֭.ߛv ԏ;G f ෨*yy{yB**nɿ{#+kryʹԭZZniݺ<݇¢wu)HIAnߺk̊Kȉ(%)N; tuO:꒴c jMhq3_x1 -Pn?ZTR87Po$X4ZU]EfN^ZFΎg"lryʖffm,(+)2=nݧ771QFFM^|35hG|ےYc\c,- pg/-0,.\-d/v4Zblvsӏ~~c_ҎQ O\ߨ_Fώz\O?}[wxrs}\^|!^ƔW vv)G8mi- pZHߦ`I.Yiqzhn4i)GSGSEşsrgf?Hqgo}$h*;ؚ:X43le %%Ճ\ϼry<Ǣdk_ |=1cD <ʗv7;YuW{ -flL/6 -\i)I-f y9!ۂ̜yW(Xb1 k[ZXZJJx#7 ;-= HNVzkص{#n}|n;e7Bd2hq5] ]b[jݧ  4T'Ts8fνFVeZ-(,^|uF WRB  F(_cf&/8`ʹiGD¢IL\ϩM9K7?~ZOGv ѵ{_?b5iYĸ vOLMeUՕUW8K  5:{؟Y+Bj&4wvƻnbݺXZF[umH;l: q1[ u2k).ikye%܆,/iԦua3b졝A&DWUWnA!!.ڤ̻CN&_|-{޴C3и.>~?(ZV^`m%Y-qa3C"n>aaYY"aX &{4a7 m,MlPpf.\׵  4r7[f1$%X71YOMHSޢ0QVGx} O]?‚/~#+v  ;+;?Zq'fOJ;c%޾X\BLOKȠV ]*[}}} !v&yuaTִ1nhWLp`8Ҙ޸`Ajb~L߾ h4r-PYU7?1|-t5^?mi3C`0hG&jncMA;TTWyeUϥh'j*};9۵`Б3}~l {`h>?td)i@17~> wKvKUEv @Lƌ[g9}ӀI_]@!~;O[Z\RJ17v’]2Ғ{ -ҵcU B;J;BB+¢,L ^-x#%^ݸdh@TKݣ_cΊhk370qɤ[2K3BC^ʰ]h @ 3>~ÁEşiޱowqcnu4n|-{ش[Df !lYp3GhvPfijp-t\.oOgk/O{-"@iJ}B;Dʨcgv>Oh2ou ߮r?7uтʱy7pAw<"JJJbݒ)f~v TMBGC۞|vτQ}UUi6B1ozR;6zJG6^O'vV ]̙S_yF0dtֹ2Ӣ4Ô?ćv(º (4Tn]طgѳ"op@$(sO]D=Y;UUW5\ʍ#)WRf}=QQYsײknʾ00O]͌ݲy܇Ɔ.rǮ֭oYakٴ7֭[9Z*CaQ灓_~O)-+߰ػ?baa7o444!yyy ț7og !d2VF8u XbCi@QQWUQG[k-C #& Ұn!NZz=fܾO;SBs)7ds▷هOZڵ#!dզe+GO۹"2DNVRڭ]k7ݹ΁>垨?_z=k?>%`!$vҲr UV&Lӯak|^GKE/3۲f>W˛!?)PWW? !ʹNn5F:ҮyqQqzini-Agif?H_)v@HnN)\u3d~.-+/Zp"h zگ}\CHx8{kt-ѭb=JJŠ6!ޤE9uڗΧ܋DśRg!?mPSB6/Z8] \";ZVlbl^^^-v.6v ;-4y<'h݇_uoAؘ:, h|9R0_-!Th.GΏo ӎ2=rmG^)y﯀9<!'8xcQǢmOOZC~3Bȴ_zWdqa1uh@9xRyŷ=u ?~`ux^JKe?4ҲrB 6&˵5tIII^4$4rMJuKh5U6]H͜rR/os2cDyχq>n?vl}:n{lz5G>]L1GM^L4#矽xen>n+*e 16[c!cAhj_ 2R#V'SVOھػ_kO3*+BTUQUUWVVBͼg-ސs k?~IBt>|jJ r[({& - W5sFחg!L૨ _~zx.'-%IDe[?i#yZz_XY&fѤWo $n?KWG7+DԱ3R3`7$Lhhɺؠ j'nppI =i=Cjhؠ7o&\8hj4Y<3`ʡFLUյ-~_yE5}{[@vVj4)=\hW ¿hr$q-f.\)v|T6i?DRB|PHq2ɋ]g+-5MA3-u-e Mmfӧ'_]O;Dgpwx0 ?6&B>vݒ)Nf{x^t,-`|X3hFy-f o5=hZ%-mޓd2}:qhPjΜv"&zfkarhI׿FmpY=ԝn?BUEQJR˷CDK؂u:~=hW^QjӁehryōf -FH^NfcLh|Ԅu{ }v46Nf8_U6-?֋vCIz;og! 0[vX= |:hl‚j4JښjXp8ܙwww@$7!\./*nkzZ[@@a3cf'w.t1kʪjEx/vN^ݴ[i7U} Qf/ &^ ';qL?! Zv>e𨾴C@pa99YuK\0qc|=]Nᑱ[3nߧLShWg/F,4ɿ ]M->|PRJ@i)I- B>}pݨn |e\Dɹب\.v4ukb3bϥe[>i*zS@B$-\T-h/>y2+n_]=ihl3Ý2v h-QgYv+]@}`0Q!%f.ZOꓺraQIyE%FdCI)ش[(+*nTM-E]ha!Us8ϿhMu9 B+*DYaq?-Q$%%,b΄YI\͢]BoъKy<jJL&C/[{J[(6oԛ[Vhެ)-g=~}e->:V]M=%30\t+w@VVق?fU-t|h&^quK9Xe;]d-&'+;7pߺS3vv1K3I/Xs-Pԛ(}_Hq:xRѳ,j+DCFOv9+i@)+bOzmk-5dnhhee',l-qarҴ@={ǁ3NÊPg-6UՋ&Z=bjç..a й}.n:RQi$ ?D,Wf[Z]z2jb܌Q F-<~C 7ەv1H'wc<.`_z!➿|KBEI[ԏQk,fd(! zعX.y@\L;HRBmۘPz=m'ShЈؾχ>pnw3KJ.JIAn‰I;>yiqxT֪jQ , g_Nc-P;r҄ϴC޲5_x?r&.I=܈v.ճOz.؆f jեC+ln^A7ri rHPdYYJJiϖ&4K(Zb{E7E$fӎCB&;u޵sԚ(.F&fM;(ס>ю`0B>~* ZCjA͖ㅄ/oe?K&$z4ޜvc=}u8qm8aZj9oڈ-{O |-v.Uŕ &lu[de0[;?~n|d0A@cջQ~AmL.ӎۺp:6ti.t1]]Pw,]M]A!(P\mG <+˷[WaXnf-h)%sn4%.hB; DHiYy cJ/\,b (h0[5v8)=Dmh޴ZDs8\-K夋KiW GWTƬc}es%mw\g6 *!a( {V `(8eCC( @?||l9QI B>O#ҡD&;/˟{Cƌvˊ/z_L:pQ+G7'场zt&}76N#E'{ٹBLLtNf:.:|S_f59\ߙySN#,$v{\a5CwѤ+ܰ`ϱ 9?nii%iniy c,GY'E'0޼|S.qGSCcS7 F- L8r2C@)ܽιcs?vq2Z`}.v""B|#罪?e erN369}z١ҤCж{N:Y.dovQqy̍;Ʀm.@TDt&;Ż;"-E: @x:zzi%%c{qCwjoh7teT'!AAҹ[UO/|N ۺ^ˉյCW4@{ ,wD1໰jΝD: @'Hȟt-|||mZjJCogaoL 4c_غjx1ҡGеՕn ۺ5&d0\@O]vҘ!7l08u""-|7R#lЗ/ލMʴ6yPt(9qiOߊK4 >fN::M\ܱnFNi!7jo NTDs ]QiN+ˑ::ؾO"'t݋܏bt'6ab-}=:A uMxzƌ1.sf$ ]99{=YDD[HliʲN%G}lꦆ̅G?:Ѷ|||*v?MY6ׅsv"t+'vbs4ݛmsDoCtv"|;>QAw }Wzx\W8}U &(# x NߊOi=خ\Lz8t3`ӊg907-1>$2i񆃑gEDD;Ż5]\>w,!=u{.1~,gIz+Mv'RS"  >>qNI_ĦsSS=Ig/b'?۸T~&45^ rŠ 7dGھf6Н>7rh_\v䅃P,n$IIcO8s{¼V&{,T! @)O\܂,Iޔp5>Nv={5CN:#+.xk :{N_r dpżv61ВL:P Cm]gfOv4#$^0~~F-㢪$K:ؤc> IW;gͤ"BC|+t =֡m/\deӱ|w~ދ>yP@K pKk[Gt.{,&뚚[= rQ23,sL-Ot.-m,1W<~С_:JP@t.nwq7578{ w}#vdLJ3-ODX6Vxwò0hp IDATiEQ0EQ-mb¤pȤy߁W g^?fOq;*?WZVU99>qB눉 _:m{OX+wm\`mO:t9uU}[o&8@QҊnjhlZ gG1v&6+08/bSnZ1}QXfݬź(ufK̘8JR\-RIؽziyfs:s@Z0}\PD #}ۇt^' @QT{G h M[N:$fx?xbMu} *WgA5uwmom5-jrؽz 5vF[s}[k+]u.7~ EQdfw,"-E: 𺺏 ߼,!5Gi.VA 7={'..:sҨєdILt?OlU +\F[޴PAv\%KxhVFv46MQbQ:zKcU !$$l{|j-~~=ಹ'>yޞO^iY>@!$(x&+וyn^9t'-K71c̟6tkC=}CRy2Mt8gM"\s^( }mڞ-II# Gضճ,|]=xn " :}[q֜abE:O+{9bH a)G[M˧!,-Vo*83RyIMo3Q[]t.g#v)O\iM5QP@t4s N^Pi~s$瞸}< ]44p2:`h;1QnU^Y0OPv~ARZ}CN:I|;EQޤ;гTV}854e 5,7}&YxNpD⤅&Cu0W1:͎Oɾ8'0ZWSe8[q6(5B72%K8i M@an@o"= guլ c,qУIJ_6<T :::-^JCRSpp/y/XՋ&Ou)@Ϣ0o˗m9bcoc '`'B 3Y=[)ɐ= gϓ/?՛hn\>TI?W@X4i˖V:{,rv@:@nФ+^8aY.]V/<zi!QIK7;;PJtޏ-Ym,֊mM 5=t1#C➆VW^6\KMt4-߼ Kk4ۻeQIS/ƚ-jڥ~Pޝ A޺,f;=HCc*eޗ~G`Hܴ;|DIg!o9*O: p!q!qivv.*HGbEL {WTZ.=fcLlbb"x[\=>Iaiwz%E9W?9[ngG lI~}.8uݥ[Kf˵B@Rz7n]b/qHK9ٛyr;q}lx$4.<:#MIj1CRt"-2rN^ ;dbY~|JEe/y{+ɬY>uZ؅O >B:Kov'x'+}In-֮+Ijg/q:1vNv&8DžTQY,u"8K_X4s9%=qwUaO⚰Ւ{t^(9!/*{,:0$6>5Xw hC;s|"!5#Ϙ`:~,t/zD"[E3tlתySHg::qəOB㞄eHJ;ٛ:ؚٙMg!q/bSeJ;2w711+}͙k/]4sy%ܥàC<+Lkitt rt8Yzc^A:Hihl2w^NW; qjTǎ4zfm,VdNEHP2BGqӧpƸݥ$ܥź(WmM# s^~g0+?uIdt<ҲwA a1)TMM4~ N L|]D?q{KQGY"-E:OCUV}8GBKf;4{@) ҡttcicIg%,1PJ܁t6zARoh z\TZ.)!nga8p4NN NKljjް3701:x [,+%{eX1ɣc 6W'` AN8q^ȽʤsДZI:H{5{'OcI: |630<6ELjTB@Ha$f&eFĦV 2շ37 ^V߼;ƽ:4}ڨEW\2e^>q37~l/%{W߷u ]w.]-Nt/bRcS#Ҫ?H2շ6շ06t: oUELgFL)(66в6շ67SrU^ > Mag={x C)(*xϻ/>v RHeo+-\V;m,=6ƿK:H`ٮ󷗔UD#&J:6Q MCGYǨyN%e2ATcYyQxfyep]KO-NPQYs~m,wg'ȓܥzii#tΙ0n9o:M6WK~~ 5͓ZZXky)nPS1<:%4:9,:\TDH֜Ká}lg&e&de665kkn9t 5\x߻ʚcu5U# NjFUO`~3&iaCKdٻٴbHg>7=ʈtNW?<>(1-mi9/bR_ĦħdڵԔlMƺ!zqII1 ĴV~⦆ZFZ#tԅPBnt6isG04rqu¯"wkn?p}uؑf+O  :{n41"y杶`IgLQ7ټxu/bRc3b2ӳ l֜agn`lGLt@2?mSW756563V!Uҳ /~"$(8c\ 6V@Pi) 9ܝmH=leҲ K:N[d>~YKte9Y:M}Cr!c[3@LaUGױ4 7Rl6;u\RfL3&1U[Nl!3@ntںxXV^imX:g;c>> %w=0Nl%H%WZ8/71Һ~r;,=LtiܨCHg4k~=$29ct)ɱ "BfuĽG04ȸČf^ZVacSD?q:fF&FZZ?a/_ N).9~h" _( xÿj͢ZjJC!In~9w,7GYziKwV98#ܝHgm::ie1 ̊1QaCm;s#-MLLtF^KʌO͎M+joMIv4GnTs(67ށV/5hebB_OY[#@7+>_#~̫V+縏޷u ,=áw|C^"~|QKN: @Ԓ+ M13Ҷk6\POMHPtF*>$$&&~WWUnlel"u۠[zO'M:a9n~N|OSc~#-#^{YСϟ4JιN& >N:ȿ굵명ٲtMEˤؤ̸̴V{@) 3#m c=::*xBUM䬔dfnm]??b3Bo)(KʌIHa*c˩0(Bg?}b2Uu3k#^ajot7$0S_3κuE*Mq[>Ӯǯq#Y^IŻj%kO;|swΛ|;xau>l"-|MLT1濩s?/.ߺbhTվ-e]Q8 ::/bS<=<)c/=?\.)=wYĝt2w㿯"ELwt&+tt29|N IDATe5|||FڦFfFژ]"*Ka1s^74 ij*زOt!:Y$fxx?uۓZtefSYXSLtKȗ鵹-mog~z3]Eë{> њ_Oܸ+G[d!671ԊKʼ|Os+ME? PňoS|ʪNRngkn`kn]Oȅ~G{qؚN uնfõM H^ULzc}-GG/1tZ3c3b3RDEMMsbhӍ"z1)y)fާ Cdefo[̌͌޼LUIvđ]dJN_ L#ۺ.Hf䟸MQԹk>QLI }[3(*0$n gY4($*Nu6~6 8u_N67OH>bEmZ>mѯ'.[8}gdKk۱ w)Zt~N\6В~|{ _SlmcCIC\}A1^ӕL;}4kz?wFJ8\C--SLeⱉHZvu+sKR3 rrمm,>b: YЋa'09> }TmC “{̝27;/yuu3'tu匿T=C}Nv[_+.VwnyNZ۵ag~vSW -'?݈1Kr K=䬁(QP5ߧU@QԠA?ej},)=GDEOt>F@:ש`HpiG|nsqu_`eD: @w`gŧd'f'f CL 4͌5J953(5#?%#?- 3UHPP0\_HOHOM]U_uЛ[=R{{GD\W`@P ??{Sl< MͭH~![Z JI|}u41Q4_{龢"œ[lv>-(/X̰k oU;~đQtu\ZZJIJs^qW}Zw 8?}Ku$5`dRC~xA5RSCS&! D&-sݿ5to4%廽~eTC-# )"&*E3W7fhi O"zLMvceH"|||l6f*^45~=? 8gX@QXSmu匜W+.]ܰζ]mP zھzt TRV̟bfW]gdkn@:W4\p SVW&TVR5xPۉޱx!wo^H: @'{[QXHPWC3e6^NW__y`ڼiN m:C t g/wTySb2o~W>mh[ca!;MY.=qp^T$YxtWO7> ܋WT֔U(tXOfߩE)ZY*F'0?DO+GROR7nP[Pjj-::[{8a65Fv뺹 ̅?)&*wk(g=LJ {p @ږ̋MʌM*ɘ vubT AG;$%#?/kSWgЗuU)L<%L L \`6f]Ǝ4?LsuH*ܶbkk۸$2 _{oSvڲwC (E).vM|TN\`ic6,6@s݂ ;x< /nco$8+.~㻚_= ;tGpDbϏ !ec΢)ܯ,Y&G@>1\_ckjn/N*`fJO*llj+kTZ4=-U~LM-^W$_ha!YϞ<$K:W/b뱛Zgˢ|[;jyfiNq:~\ݧfҪ~}9\_]( O:&loߧgfJ*df*(.koҠ+rjzZ*bb"Ho`GL{UH{x[іH UO^[Y!.M; f^&gyɃ,kHnF-<"3~ ~Y;t,17Mʬ]W-6|&&i=g/5> Iwcd2\]-Mx1cBKcݛ{Q}kq:~̜?%ed0Sy՟Ny4]_K(Suts JR2J8_cE  AS|*~&:>|a%MKjN"–&znt7zx3t:MoW\t..4kĴH<DvYkM&8|>%%3?.13s鈗CQW׷S546e0R2K_oh+q44%6G{{˔,'oU)ˌ3qu258tƙtB٣LOu1cJu .5h ޗ~/r6-z|ШI 9z @k+1-s֛Ujo {UXF~q#P2>6<xqaPlR&]YnI#" $fyE繒BZUi6'TTX,4xh#,kJJMJMLIf5 i t膺jt??~Yƙ{/+-X(,$ASX.n_joM,m0QLL IKݺCNFa9&N>rQ}m,f[O\첪,,f-uEÓ}:G{{Gna)g,A 3sGL@HMgp ?]vAIfnIW&GLKMQ[]YWCE[CY[MIF_p)t NJ{[X*%op'{K}IbZ|F|<2.MAv,Q3'*M:@'`OOv N]kϽ2t褳@/zYU^XHPSMEצ1th:*"BBS^Y[WWW_Q@_Y~P̽)->U"HhljӢٚ3ؙ`c/+||ӂ2}).$HWTTY,wqYۥ[N|ҋt3f_]h2,|DN>&Clv늬┌,8(,~TSҠ)hi tEa҂B55%e?.{[9x(K ::/bS=(etWkSſ?~,j Q~gHV+T.DQOy^w  9\_e1EYy9%Y%EeT5芟4eYlM-YyOC_ľL21cgO("-eoa8z!V𠬼bO[W34Ol)Sr]9wʘ}[M#rsܰl _{uբvo^D: TZ.- =0= -A$iZ46M_$/C:&mWsK2r^fmo+iՕijJr2X-rEaɡel ؙYNݧ#".+0 (F@ɴ 6#IG-r:lݫ:_ZXr0VV{{NAizfAzV!RPS(a4Ut5U8}١Ҥ+*-/*}3 Di46MNS _tָf9C'wUwB< +)Nsu:-x(EB^BZUi6'lO'>]Ĵ\Ύ7;D5U:tDz`߫Wuv~INAinaE Ҕ*rj*4(.~nůCC#?7*Yؙ[JB^SP oi6AFz\_d3qi)?6R3'JzvYUIt7}ٮOi uts JR2Sy+)HOCӤ)*rjjn///.*-+.,~S$?THIS/ٗt^- 9,:%("!Ebr11tE;#Cc;Fٌl7kSۤ0FM]y ˦}~{GG/߭K*P*7LZP@ 8v]iwU.+FZppHй[ZyEU:tNC74gp}.( @Sՠ+j4iJ2*ptt HޫA/#♍MÆ 04574@t:r-m!I>O"|F;ض]ƍ2bu=8x񭃦Z[2r^-Z0֡{\Ee K#ZRVuL%J2r2$3_N 2p󊙜A{{˼kerVN~IV~q 3/U]TDM3t:!wUjy%j)#AWTNSФ+[;ҳ ¢SBc-mJ2vVFx ׫k zзiZ||=pleR3 /--WmqgC:ݏϨ|_鏂|6ecN]=I;TM~duj~}^G:ޛWٯҳ 3sr J9eM2JcvIYEV^1™Ii(|Zm$/NSd fpԌ|~~M[s;=ћ*'>IrOo3͑,G:յ.+ t=}C(6%(2~܌廟Ĵw|E~~'hJݓ$*9vF ڶzmvlz[ܚ_U[y*HQAR:ʺ**jiim+,~uⲼ׹?Q5t@0AC')*-J N~ZSqؐF6f ,Mr J{i>qi)ҹGxčֶ6sK1{B=u/g6@kۅOp8om%ټ;XE ;:e\wx[}D$H"%Q{زe[rGIgM'mґMyo$mo4I&iV 'Mر%Oyiڒ(@81GbTImY"8ޯG I|y0No#&Be- 5-w^s@$ x]Y??ey-H$.(ܹ'ժ)pXbG}[drY}r4-.C/HC `$NdYB᪱4Nhrٝ6S%-X'&?yL&[Twֵ;ݾ45=/O=R.W߳7޶J ] tWL(R*w_| u#}a)z_?2=;{Wo/R*Օ? IDAT_5o595Km =]iڽVjimt[[ܭͮ:e6 4:6 #`DgNi39mFpLHX-XR&&N9tr;J vo_k:.ɩG<|G8vm_w`wuˊBfz&s{?y:ugI7֚=񇢗o)R*>]I7o5ӫG3!d2Q\Tæy _0u\pd%EMFv4%;`+$Hѓ:! xXb,Yc&^9u玶Mu[ׯڶyϭ,1SOyǿxęjUŝ6طΝ yKâK ~Ͽs'> u'tOK~Yg2-Jb~w<{ހԯ~vB!wX[[M.Gvz(ZB.:avX5Vwn5(BW @`YS'N?|S]ހtmZ(tn.ozuxm 5 ޏ~Ln1695mn}4 ,`;?uW:O#B]Q0Xb0Ao`tl\&9F% -7C |h0 F<~_0:95-(++i;mFXc317l.%~T_=Bڤki,]/_=YL߱[ׯ/[p _9 >toNx7PT~w?|>u8\74J|EWؿc>ko|bnooktDŽMu.{ಯr;##`~-5c#'_=Jїurf-Znټzf.|>L/'^ R uwطcUХaQfs_W~ gffn>_袮ӧ>쉢B4-O_}-/L]?m6`p?uz=陌\.sL 5U Z1 l^Džۺ8~SSFzWmмfK.g`>=7`5ٻN>|R+ ]O~7isGά\Q?y~vX\O_pvA d2rv4&j֑$XА-0:6~c:~\I4*Bz2Xw[&/QוTNDΌE ]PTn=Nʊ=\fr>x衇BW`I=.pζlpW;U h[@"H ēB\f1jZi뜖U-u+GN JVغVS@`#h<=95-XQVZ_gb&Q[\,t![kpx/uCǟ9QUܷ{[ܵiEYikoZ6 v{^7 F Zk}Ϯ&nVU2`_ъ KMM!T+kf͸fU;i36&=)X"O{8JG[wrjZjijmvoLXBO}':KJwnm=^Х-&g&4)}{~MkV~HwmڱfW)cOUĴB.߶avNWbEd7s|\{OӶ.J.'|ٳ!R){G-uFJ 8}l6wyW2bAZL:tK=pWKO|T*>W|vv^H 595wz]^/陌\.5ҀXT(txr|$bPQ( ET.d&i39(pL&X@B,&ƴG!s=/ur;?~<,.Rn۰aÚ k\N gj@jp_~/eu{V.m>]78g?ڲnvN_y׾իXNL|[n~^j W$$ SgsevK}V*G덳wljmsjۗth6^}[g2~#;&g^y<@Wgsv^fmgCU _ba ,"'^.OlVknǁ;\iaѢ/A !rbpLN]YN$7x]|m;Juxڽ?{hRXuwke'j,nRz/8Rqq[7s}pjk'{SS3B[w4lR748OwuEg6޿Ym?7oBbR OEi)C0Id2Bʊfܵƶ_ B%X {H- f2WzOʿ/6~چ k6iX6@ }ޏ}dz=r[y޻1я{58 Bo~'*m _NMUu.Ê%gR8j[ċ/t~B-[#鮗^2qoزf ϟBLL׶ڭ6}C>_޳WԴjE$2vr@:.o` :{{\&j)B7mdB?,u Ԋ geѨ8mъ$d )RcBɳ{=m.0ԭ-Wk{&uUe1Zu}o#g>k.;.t(ZB?:\lIі !|pWsevA{{;~atd/n4\7TBW/x- 0Qx<>TjTQZF/; xWmɽw[s\.'5-8 ᘧ/ zBݽUcݽgk}檱r25= ǃ@8@(Eb\./(_V={;l贙VCiIqkK,n+Jmh޶Y3ힶvϙ~ Jޱ~jwscM?(-)|?y/O}?tXl9ǖur,#S*2!5_ \}3?չ\]jkKoB&2]/*e33\BmBiUə؉oXBFۻNņp"2rl^krٹH}q@<b|>/."nǝ;79mF5Ħ@pRKL&ukxNiK$jkuxڽ ]cmmq6Z[ܭR.B!"A_>?¿/j6طx N#kz^oLU[u!]Ӻ1;aV,6lW;ݸf]zȄJUT$Җz:J(\.ѶbϝwH'fc,v|8ڠ?zG7N}nor9\Z 4H<e eD6Q޾Ce`ԫC N`)35={!|?ik<ѱ񲲒Mu-5Z]u6hr;;[OˇO??022 x˭_G"+WoX#(*v;cǼ6nQ*gFv;מ>qn޿UqՐbZ}" Te{t}:jltl㦚3}w8éSHddBNKC>$zBݽPO_H%Ek}mֆ:ꮵ]2l(2H4Ӿ`$MNd f;m&͸{:^mҫ6٨%O pr!\Niy=\? E :m?:֬덿˷wwGyCk[k[Җ^7{P΅~cfVZ㏝};<13 h"+Fd2rl\{v5M. )_(K /X*582ɈK z!pڌNѠWk*` ,Sr]cuXҖ ]7~H7 0ԭ-Wk{jQ.hg?qO` 8tͭMlٿgkC5f񏎵XWg,eHG!546WNLLǢGz_zW{nw \.%z]dy:A%">q3υ[WObcjlO~||z:zjB!ņ3=f@I woEe_B`#J1/B4]&Cj_0WV,tZ T4RRBa56f4&i3:m&N|4ˑ-G,1 LdZkscMsC7I2= :|^\ʵomvdBWw[~@q|-R5>1 y}=}AO_OL !*+VkVw]cuX뜖B܍^}/SDbei3U [9bA1"T[il̛;>{'^ R6޲nlffݑšB-0:6~ie V. |H$R_0' f;m&^mԫkl&(d b„? {dzXaԩ[[܍n{jk`pLgiiέlٿgAW]X`qfsH?x}`8zmiqXZАPg2hr oD⁁x( P*Ff٭Io5ifͬ+d yN !zS|әlvuS41]B,Xl|tu{M2a58m&7Nl) ]fct0$ɁK7d,9(VUZYo5Fլvި(BB(L?\{eIDATރO:|lWUruvo{VUB-XŢOZty]^$Bvjr9jN`&$A4&Cc>+kfNmԫA65&@X'&ۻ:}^g/BueotMnI7p=ٮ"-kܵ69m-#^F{}@ FTV]VZp)@r4h8 d8 EH"MI ¨SZIg5Affjq,jd Ecjz:.o r퐮kpٙϿ|GΌ^pXع6mRZRzcAzϭ޲+oSFň-1_0ZD}@$9! &i3v#uK ]!MӱD:OEi_0lBa5@`ЫjUH~l͑6R{a)mr6.mfp':2[if_wR kXxrã}p?½^_8=4"(R*VrZ:崘y T \/ӓS>+JQa 3L1ϵX&}h }p? '2٬B!5gg\0M\4/_= KFf,*WFi3nݰjvxATcT(ۇǤl๣g|toi35MȬu++V{u Hrȋ p|h \AE:i3n^t{Il.ID:MIh"'f;4Uzmդ[Tofil٠5zmL&+ KUnmq_SX.W4vX ,bAOH!+|>油;)WPUVU<~c{Ol`cjz&0@?"ĤBUi3Mvڌ5vŨS(.|MM$X*Ē䥏T<1O J !VZZ^m1jl&Ƥ AU@%lEq "3XJHQVM?VŤS*h; wwUeyZUQn_XQV2Rk"(++i;mFwΝlɠY> l.ל\042695-) K2FnھŠWjNmky`~-X*W\8J-ͥFڽ8T(FjpX va3:`2hr۸a?)txo;`$MJSQTuFvHvDLf Sx:OEx*HK IܨS^uj^cЫMzA< XRJK]FSx+G>Ճ :fb1p\.V+Bn3_([1ѱP$K#BSW NnX%LzA leXZbՙ玶$d53[ k"(_Y檱:m][kl_HNhB F.OHM9}c %bI72`0 X  b=E'&ee%Nn18FUlŨk q$Cي@&9%܊ SRz HG⩋c .Mu*U4`f ڠ޸W9x7HC|>/.90FlڤzB^CL&bPCc!L&3456cKC={MNnTW*58OCD:R@49>1)rE٠iLͶ FڠWFZ֪U= DބsH Dh2‰P4qᓱd: !WYM:Yo1h-&͢fjs'%OJ_d|H<E/ /ĒlN\@76ja5,3p4C#cRち+\Qf3*ˍQ^Tot*}]Cc`Dj4D飯~ ViIQT;mɠSZb H|WO~|k3BTsZ6}4W/ Azh$J"T2=Odz8'LFڳbQiTzi3j*^ӨjF%RܭWmEp| EH"8kkIiH.jEo6jFլ35Flt?xrO?^_(Pd ?2x*OKHr&B4ٝ6޹oci3-kxBSWI-zr^cЫ `,^d 0*W4Mn|4D(p±d<9$Bh*btf֤AcЫj|2ד_bt.R#o#aVH,55=#P(6i39m- lt`hx,HKkFr^S-ETnQ&:` [ L&35&fSk EX"+\Gۢqi΂[ NYT(,R _a<19!dB!+v J d2j2[725Rb.o<: *UQ1RtJUaҫ :5 z'x]U*UH"&t$S@DZWQVVb1h jIgЪFQ6]X8gǾ͟%RCR!. ̫~0 E1(I#`6a5kqs`VV*\ƣB߀pڌB]jN]*WWU2%m X&i&IӃ#@2=Ogceu^STi+u*Z[k zNid2KrT(٠yǢt25!)^:j"5H I2J64:M٨iLz^6zmՊ:&7].Ώ+Vن&rBD6}33l&+nP`tl<I ͎*ڱHVPUYn4hVfI1\hk ;ުiimOpiy/MT"==TFJUaԫzgKˍ:,Xm;9yHx.~M@,C#c?XS4"7PR\d8?XT&ZJT*JKo1D[UVZ, P'"t"5$-JcїcȅٝTzMRVuZJVU:ۚ>:yֵlMqڌ6ٮL6T*3ջbX2}@zhx4HGۗ V2^7RܤWʊRrpѷP`3dj(%/AO &h1XV5U:MR25*ZT5U-,A<9xōHzh$58ree%JuUV]u)|P!mQWUj+5*uuzwǁO !rYqqчΏ;5֫$[E'oRۚ/OduN2dMFN]i96E@O0\>=4IJihjp8=89{񮡑l67"灺-**Uժ RʇFf+O~뇿jn{v)ycE|OPV( R)(߻Ϥcehx,58, fdzxҖѩUQB *U峙jՕ7G\'|'x-yQZy}?{Fo'u{=y3O>r'Lg3WGx__T[2oG*UyvqxLH[KG'&/emՕ ,ق\./k۷}o ̣e۵}oD?±g_:K"*tDkXQVbҽӳ˲CB|g2Bǟ9GVڶ|;3Wt>Iw; !<}珶'Åp8[[RVZ\V1=E]z\&/++0>bE^[}j旻ꮱ>rBWNd DŽ_(Vyۦ][׬r)6,IrlͪEXp dB\^TRݱqmou=I(-&rŵoזmXR/ ]@h׶}-U:-&ݏ%UB4`.d \-s![B̅l 0`.d \-s![B̅l 0`.d \-s![B̅l 0`.d \-s![B̅l 0`.d \-s![B̅l vl@ 0 f̎?I %roP@[oP@[oP@[oP@[oP@[oP@[cu:><u9OW_ί\q}ẒRb˖"VY9HK[B!WV=yfXjcc  =VOYYFBB)'$6ŋEeՊӥP?/ T^VGTW=PCFVmYg޵'k.}RbɢVɲ%\&5HVVk!!1ꩩ Hiiv; LYEcJ87CGoN& 93a5PMU?T?LʎzF3ڞnoѥA:+$2Y/rxELCI$X2*2㝇 13:oka:C鰒W:^,,([H}}3l:UMRRt~FFN}8;4o=+N5uln>zKy٩КNX% ǁNq00))-[]ee%@TݩNq0033mxzUo"{ V R҃YMMg%u23 ʤe|у$޿jFHz}<iHDx$g_ظk&!3T@|mMc;{y^^*%%i1`1'pqv} &F!;#/;%f&8;š k_`oAUU>`Zj>ɱͰU;}`0a[,{fE@HU~;@YOOEWWd51`쬉,H$}YQQGEE0QHC;%)Zw--maa8&DEEÇ9ݻ_$0 wE::Jv-m9=Ո6` 5(+iz%u2@"6^ء*-mEs_qS\0:55Y=}L=}U >Tkj* 1LLfi#&n.[xO` Bѷ Fl XfQ("dQnZK IK  \Poi$)(((*sl^z{[aH;{Ȉ,7e3gw V;Sde%F4epx([WT25[ )usK޾--n䒑^PT,[I_ر5$%{}qr 棔g333oܻƒO\"8!^x/%++l; fXr1R"zǼW{Nw,OvZ[;ڔ5ٗ.ݎxP[[okkXYhwwht++S99i^{T]hc|wi7<ʁ^2^NNZNNzUgVlٺLRR\EEa'dx?G1 ,/[WDmٲٝrtk70& e@Lڏ1[/nٺpLD!۶-i}ƒ)SDpq#vޣlSw&{q~99{bBSSk55%hhh";uH&^GQQkvѻĴ'nݺ`0ZקI"L&$HDIss ךҒ&&zwx&#o>H~q`_[z[3gϲJ|,GFFƌX%]M=sdNcvwmݻX|;Ǐs! a6rr ;۱ @K[y?u{)-- tW`ꔱ}aae-T*8,4n]0 SVZIҞ?y5 8?)11w7OBYWddbii%ΨMvg! Q{֪6/ުkI=QIiwp@`uu dEg)BÑx##z2>kW|'i?~wυ{.p5ɓ<})Q2u$nmIOj=fdVAttu4 VuuInG_8v|Q2.:֦JZ_2cSbbZzz֟d/70twc=x磻$7⿷?֮cooqzL泜aaa e&,ʕȈb!!!C#]㉦Mn]t⭢ىG~/Iȑkj{b՛H0pI G}ӧO뫫tuu+++׭[U#z'O/*++[YY%%% j_7ĉ浿a>mJwX?[h\E:3,~.-4l>g`ZtZܕ';.7cC:w8d|eIK/\d^lo gV7;coy?> &cmy߄AۗoR>RI8s e@gw۩_GOQ#FS?Yܪ)S|VZ@mTlA;)UDvhpC)$D"-X@VVV]]}߾}PQQ1w\yyy%%۷ԤP(NNNۤ;w522:})w]ȾUss&DsN8 ݨe~~~T;̴ ˅ ͛7WWWw`fM"Mr ;f 5Iw_\m[po#>QRd2w94c[#QAHKI6tg}ۦkm~}&mUۍYj_]'QRp o"9ʉI ".4_x*3| Q5XXn~ܼӃN{uE8d v-pN*~]Y%Ν7o&L<… O 1c%$$o1򲴴d/K@@ŋϟ)>>1c"""( /0̹s^xcc㄄YY>M` f';;;|az]}ýw70}Qq)5u-TCCdyq`h?'"6(<N܊/nhl~4/}_745/nRMm}aQɬ;uxovlq]IK?qd'7HH{֔P0%h AkVhVhuSu~u%7:\7Goo迯l^lmsmL^>mTkI7{aMa <4+S <--zʕ ++Qbgg%<u-mdN$Mii:\ 7xu6eHx.8Xwy "%&&EEE\KTTTXDGGO4IMMLmGlܛ7 ->|8###aff:8qL'O$&dz7})&+fG[qnzmo9~kiyS'ˌFϲ`-_ ۮ@]gSr_ma1pWUS SgHk9m\Gݥ0yj-`R9o"YCN>"坢.SSSZRZZJ,yzzrQ2r$)Nyʔ)~~~V"İz˖-)%11qĉ۶m[x1,8q"{!1~ddd1cSa/Svd.t]`HCc5d>[f2N.*ƾ(&*zzR&M #q!{vbd1ksմ}urQoܾs{mi/ti? Vf2˗/_~X}}T&ALk/4==}߾}EEEڳgfs͙3g644]v׮=H Ayu LjÆ}z 诩 #mlB*/K9Ҹ<ꯩ:!UIոqnn:{]R_]솼Е{*6G[# m5m7l~9mi 1c``@$GtD"6p̙)S444^z޽[000[nAx NAt_Vgӎ3?L߲(|g ԕ`+յ5[ O-3f->464Pޝ =c))ZOhXkA^HP$BfxKS`ސy!ráGվk7'KsLkյž]Zuvԅ 54ʋ+DEKK |}}+**._tҮ55}t##+++ϖ-XF]\7'֭;w\YYYYYٙ3g6lZ k[77'O566677gee8qֶO=G.1g*d]b_}{N`>_oq\wJT!Gʭ{-t/1BT˝\%sM!C/1^:c??͛7ߟ;w}-Uxŝ;wn߾2WJJJsaďo~x9Y) q1WGW/c:DE(:Zjۼk=mٿ&;k*Rdq1#L{pl_hV? _(ܾ7a'6u΢Qg9$)#\ IDATnxpY $) K.9?|ϵ0q"]9]EIBVq?Pذa㏀7kqU:hTTݻYGvwwׯB766ްaCj*O3F}N#npӧOoܸߟ߱pїφ7Bu7o… K-ۧG Bʣf'L@o1#zwӺ! U kcWa~8'B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#=q3Qo cT$PAXa 1-wyee[;9j{w,_Zp嚨TUꀭ&%qs{luɢ8\2o<$$$*-,{;~G 8'押?b﫴&\[aCG&`]ᣳNS8߱"!LI_]ukw\$>xbU+ݧ;Գ %>b;|ZF6L=A]@QWgn~ǂzp!2kAgcmWUPǨP߆Ig/JϘ/E\߱iaNoЩí ?WTo$ރ ?ڥf<߱AsPhuJmk?: D6İ[Za#.'nIX~L溺֫HBB@"8nү0?}r>A"4-I@Xt׏ m_zD'󧢮cA_gV&UU獵tmmWAd0.\kcmbcA_[? sw""`h\u[DB G鳅OS=ʼno90Bd ٺ}\Y՗_߼u e}+AF^jdB}&nv11?;B³O9ٴ@" Mxaw*Jj9Iys -t_Ʀc)Ŀԓಜ7@[<ڒ~L_*n~өST 3ɢW;0G^߁}kI$ʗT DLNyx߁yI<8v\xH{~z+,fLKLx߁t&kzdd1&cR1 NJHJ@@80w]ڵ 9 wc^F`Gxe5p+ȯ϶- AVlSR,-r{Eާo?8?_5uSRT=22u +ko=Y(d_ x͠#vQ?L>أ pUNiA76omn~_4O{ˎqcbt:_ΠnIJG c3CÈU0ћ^ԏ- [ZwQBG _awS{8mƬ%Q~Qע66wZ%FΫ bEU'د\ίlԴk!B9M-i%_ DZredZ>-5duғI^Cҩ3kp&y~96W3>មL[;屳Dr_U@T\#Bwed6ؒTcW5J=>q2re~´ubtחy57vrɉт)hk7*7!qY[8tkɉNWuɫ;w\&1x ȿ:6uLo|}VQo7TUZZ c5G 武ZnfhXcuue~q^@k?p"hiҖ܄DKk/ S;女뛷r82^@ݻR_^vɅcۣ4ڏI|] țb,(65&$2X fo?n:b~?(_/ oSJ:u$1f/ Gy~ U4jS/^@Ny;a[~ڡo+F($=?/ 22!_E9>ijbDe/ oh~*){MV^@ΎǾUY/ W[;女]\;Qᰓ˵uO*}ݩ}}{lG^^l亖c{9346F({S>^@N:}ē|[6íK,?H`0NW>&(/p8zTӠೞs˨MM%z;l{T͵L88>$^Owas[Y<N3edkyUTnb\oIk2ŭ*O۱6a YdBP*iS{Bdr/8nԅwx^NĻ(-|4~Gў;LkkQZ7Iɵ)!/ߩ)bBT]i7S^܎xq; /N;EY_30xӿ\m鐢}D䵵ëSݻJ ! O@AWr$?U ˗"53hn<6&'rBR8;*Aq=*^N'Fo:qM8nlo> $sQ{aw?n ,^Ϋ^xWedH^įY\Wwu044CEEcUU+M)eexzJ9د\'ܦ&p!ĻBRQQ|4E $E$%Lv!.adzdQQ.6;8mNe wc{WEGOOBaǝm-^1&nD$%%l,{ {-PP$tmm<q`w+WBY6o%7B7]ZE+>uœldQѕQ'IDE0/V>JK\:;9ezνM555' t]yo  AvEsm4;ڿiA@72`/6vi7u 6؈*y# [Z` 6,ypc7m\}7QČ:q[Z88 XCe%;C>5qGo .'D#kDŽD+M55`IX0wŸC"qe8qIeS0whsTT[[_VƯPU457!v~L]gEW:LiqZ:;`"CL^|#,B"" cGsNݲR_jʨq1!2(¥1Dg~ L`H{{b22d2Z#BHE^gmIBw,$=4-̽YAHn?9-;7L_+,,LHHj%Y;D>Zfc;o 1v;@# lW/!pwwo? 4R_PTh"@BgϞB~УxJe )=t(!, Ey!$0f6l{~GWI!&q`dĉеE{| YX3w9  ܵZ]$2Yo\yu(r2ee ^=W^% 5UXT8G/^eq ##ڏ5>r* $kf9uo>=%c!$=PkeAXRu6Ԙ06vv1߁ =YkUjMw%h[lԚ񁓚S2 ݻΪ& 6n n/Ύ8NJSPZ]}P^"sp:qh=,un.3{Zû3^U6ЍT*k${f[ѭ;zbQolH\9T@ +u! .,*fhDZ]؄kINĪ5Xä<L)|:t7 1j?%-.,ZSPPdݯ g6>vyNA}9C`lU fD":ZhݿԝF;&6y;&qWSI{9*z]c]hua1 "-cSz&q]Ke5"q?/Ǵ Q(PrCVq 1csEn ^=rϱwf:./刳CO >vyL'^) 7RV&VBfGj@$u'Ui.4x2s@znź,DmH^ce2D"LM>"8I30فHh H [2)!ә;]BSD&*+jOVإ2چ,%`;lN( w0xJ99F1ԦrО1^G'oN_R01X? *#TFZq]J .n}-Gœ kņw,|1޶xQr]~/ݺ3Y%<^Cap$x绎35+fPƦg^s,"m&\gPee )6~V\l7^=j q=] m1~}}lBڽ}D>ohQjklP q|2)8G!I!&q`xu  G[H!O#qngt(!f G!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$0#$B 8B 0L!$_Q XOs-?'w8u(n-[7!ԫHWe_Lu~K UYEaa$))N!!w"#޾}d2t=E .cƌ`"&&ߎi q Meq#ӗՒ:<--Mܦ9JyNRARug3c,BLl 7*ʫՕgϞ26sH,Lk:?d2w8ĪV$00<))풒4}떽)) RS_uv6`qqXgnݼZ,/{uFF&'Ȉpv GՅVѣg?߹kE;@6nOK{*..xFV?O-?OK!E{Ps 5k<7oދNRR_bHvv}_̽p!lٲWFd.Y2}rrYYy;ó9}כ ">ܽ,2q(9ykW[o8n#A3jjdd.99L7Ά.U6uhl'**RUYr@sD?U(#^III^ick R |ˑ.R̠q 6&9:ZTWUWׅ?x0=G?LH{eo]|MwLJJ=v,'O^*i}ʌNiYy}w">L⽗twNj:v?lZvō1r*k54-God߆+W"# t=f'6ݺuŋJd'N5I$#G~ Eii+g#ŎcU]N51D"ވ\YYnII|lQJRRzyYU[s =.GFG'H$MMqg8f:ԅCP;Hg~Ļ|?{yҍf̘/_w =G|DQ[WM`8pڇg,w˗Ti$Rt /mPkHn޼yذaRRRzzzV*((w\}7:'RmuV⑅gw9|p```lli}ԉx'Tg.7Ƒ`i}}}uuuXXneeuZB=ɓ'싮VVVIIImmFpcc_06gw -g4rD˙Sl{ Z6yѳT0re ~ufaƓPS[ϱ!;h2f$KFW`D6GͷųWzM<6b<@c%~r_BB+ЙvSϾ+kV:*wJ))%V4P:GFc?:lsFo]"{4ܯG❵;D"H ʪ۷***Ν+//}v|ܿ޼y EQQ)22ut:}Ν:::FFFO<޽K"ٷjnn$Hwi@Rۙ޽\BBBBBϏJt#apႃ1y.LS?!$'OPSJ=,/+ lS($ҿ~quaomDIuV8\mG#-%p+gm2뮁q;UEl7>eu Cf_u],&G]J2'F('&1|SkQ D֤bycqšOwٞf8kש.∓/wčN*~]Y%Ν7o&L<… O\ &~>cggq㧛8pҒ},/?3gf''xcƌP(Dd2Ν{EF-Z}>lWPPPuww6v{IiG/oQaRkZweN;;Lh~NDlr]}Qx&8( _VQsi^2οoh4j^ݤ¢Yw?k#G]Wlb˓ ~ qo:Oop_AuA) aJb-xx^Ь|Kn Y-u.n _ؼژ}ש$o=šzKyCyhV.oo<--zʕ ++Qbgg%<`o;2R2 s;[[ľY ;kOhk"^koVg:̎7|ZbȜHVɛ7 >u.6okmː.'[#*8]pZ䓓_āHsbQQ&ѓ&MRSS#?}1&&&fd$۹ HK:>>>Ȉ`pD'N`/'.S9}4{ɓ'I`0<=\ z׫TRdכ,wo߾9s9sA4_gڵ7n3fk+1܌+4*)cx-l}_Hà"rY6~H@cnپG6i("cd?b!ȷ]A Vׯ[22ĝvǍ/}Y *"N޺E[77A&DOXbȽ"R3[8qaƌpz1It: / "??4Zpabb7yٳgCxx6__HDD2Zh!";w:`yŹ_F?"( _[Df[yW^m̋>(^D?i'O;rӺDdmE'p45J`|=}W%n3J{i5Gi[aVt anϹx%7n\JJJXXwFM`7+FXX5z{{oܸqƌz'2274%Fo7|ӢEq٣iViW;mС|Yf+*|gn~Ǐ_v :յTR&۷OO )f ۄWBY=zG^5~.Wn\e6,T?FTڏs8%3Z:K,楗^ӵl-ٳtM6:t(///88cƎߺg+κN}|vN?:is?ܦϧ+ן.^>գUf[a+W{ݪaqAX,y8K6olH -4Nxg6j5Aӳ\qTJ6olHjlAguWlQb;F'k|l_'~%Cر}ml&w҂*u+YFwOK 5=2ixPc,"^>mPK>z& 5%x͛Sp'-lpFǻ;iVny^F{Ypܦ2Vb$Zgs{u v0҂l.G 9\:يl! @Bтl.G ZfZaN+u+Z@ @a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0-+u3ر#**U8Q #त)SٜkمVTTTttp"`pu Ԫw_8=:@ỈqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BT-{Z p*Ba u.Њ8(8*) Tq n\bjE~i';;'~턇T~9@`0bfӒc'Ιr]SP⨤.g3qjW_f]uk1剘>NzßgpC[<`œ8?O=V>zv}2yYaCEHp_2HcD`0pS\Z*8BpAD _ՠ!Qyt:_P~5m71=N(s⨼jVejŋH'_^"sYٹ9yG^yZ~]8p #qT^uoɿ6iKP>O=u"Qy͘^aeژָ`ekw|n!vl3wWR-ՅJbNFqP! #@a8(0BFqP! #@a8(0:Xl%[\zѵkU/%e퉉_~~;R<˾0@ELKx@Đ_P?wwG{Ēv8Z{kTR^~Arز-P!ܫcjת{ʾ$@]8\ )>6??b{t.) P!ަ,$1!t}F%$^\U(BfT<>ݴ؇5 ~17ҍ;~lNn^ZD/Vj׮|O۶z%[lܤ]>JÆ)3מ8~1$$`N5kV]"r`MrUmۆճgKsİޢ7|퍇3x ԡ >%cq5WbZM{I}.ݾM!!"}yc׮e=Gs7q(y4nR#;4޷leْtc:tv"Ɨ6@W^Kz)%"A5mYEl۷dVV^fvHPpMhA$>+Cn40CDww"PED.~Ĉ-[4|E!IDAT|wJ>|(.""r_F#Fv)zOOwu/^sv^`Bºq@D'"V#<"Q&Mꄆ־0 8l,^t:ٺ巍df^+x%YcxvnKCW\ xկ;_:G;z䜈Ԯ',޽7]QOD.\*"u+wh. Ӊ[aaM вJXز؄ F5o311U q<\!^{B]]8 )0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! #@a8(0BFqP! pue*==%# k(#:%*IMMMNNvu(A% qx0BFdP^lIENDB`python-oerplib-0.8.4/doc/source/_static/rel_res_partner_v3.png000066400000000000000000002150661245703354300245150ustar00rootroot00000000000000PNG  IHDR?bKGD IDATxwXW{P(b!*bl {K勩E111=XEQD( "eI&lap~;.wޝ B'%iB0#$Br 8B1&ٳB7zxxH; xLܹsEEE !zƹs d0'R !zF7Ab88Bv_l";xxˤEu$Br 8B1cHȏ>6k;nm44UW.?B.r=>ŬwyPRRikdRWה46ѣ|fs:̆Rz锊:r[;1ch66>^u5"˗JV.?;yh/7WfJ̥Ƨ_w<zz L룏2l}!cd=g [0r({jaCYSIe ^ 6ej5 NNNO-`KF77޽/L٩Һͩ{S{SuQ\e:1cT50ʟ|cooJXZ[Z3p?]8W?*{رeQxqS// 0ˮ~D47#Fj8s:W-}- w1zܝwa%$ԾiRUe5yw{2 pG^>.쬢J[ZڴU;o0WsegosI+55 =7ޙLNƍwvv6onnx!^ZAmm$_r)Fu|&W9`1Op7mhh =s;3 w0ulZFJ Zuu++nΓ~$.7^WY[~= u{~3Ovve` `lݡpt#d ()) ^F6 TRRmuUB|"a3xיڙfbڙWxBErSep_2`-"O_݉o׮fѿ EˍؘGv݄^,e&SDgϧ-~zzJJJZZÆۀz;,y 뢡VWW>F j]m# wwQ(0gSg23xx!3x{w;{S6igo:;)y0oH9sG0ii^UutkkߟPgrVbn߾Y27]FJI缯+F^<شُzZUU52Ϛdzx jVVH+9R'ݼpS =,|^4gz5k-jQXٛr]Fm$JDƲkQ/I\:~oMU9DY/d&)iSÇb:JYY×gN&/ xq7ШjHNzq9C$~TJKK K]]uQQN{k;{ښ贷eׯe ٨΂XƝ;.ϝhI}m8?xuMLtץ5Sg 50u 45|(Kz}{q̙j1FbzzڵD*p董죮e MHY~Aڊs̑v cIƚMX::ꣽ7-H..up8!pb:Ċ^yy|ckn䒕YX\,R`GH2D7R_CCcr[VNNΫ$J6H0^8.YYY2m#8&BrǙ8#RTSCdHVq PY8ISBHaG!9I!&qcBHaG!9I!&qcBHaG!9I!&qcBHaG!9I!&qc8f0k 8.h>^OS!BHv8ϋy֧#.=y򢥥XװOP=<,ZTRҺmM;lI &.r<y={NFDӓx}`\]!V;F>NHqikkk w ::*)~YzTdJJ/ihuJD:.n\]yAIW+/JMf'xzNvii>u͕:+}''N\^r`ɒSxi744eg?p>Z"$SpLW5<=zh{;(~/tww`Ejjl!|ҭt1wq9zX7M6[oV@|\@vӧ^vs?s<<FS~SnLS 8qf5m^_xrزuw߹}N6;#}GzzbIqqq|qע$NZZbk  E 㢶6 h{ԎGee=yY9׫ČI"䳔Jd2pڕ\$#]9`ffD>Ӯ)))J23s6MѦ*++ h;p-͟:+GHx/UW`0΄;{G%%F||Z]]iiiWYH~9؜~='}`S M;wp3m |9rxǫuk\VVWWqZx/uzrKK)C{3fwwߨuȫA:+ ꩑ނܻ֭n'K&_Y G>y~|tNdfv<0S:<ȟMt:.`cML Lej/yiiiGwQpwٺuY߾fLҥ7mkbX~O7y{74c2l9s'>9pg}q.!ia_8s̜9s2{@Y/S\)f%!᭯cx&+ٷHBrBHhg0b^"|wxx8P) z Օ` e0Tɉ'uuul͛kjjoBraʕZZZ3f(((رU]]]]]588U: ponmmfŋ"h̙3:`0oriKKĉyO>^C^^ىyQ%/^''' \\\ѣM:¬@} sTQQQTɳg@KKKWKH\g:ӝ]#H)3 ϟ?SwwwpT]]X,B<J*i@YY}RPSSfTa(0WP666R%Lq&f`` AV!_:1hʔ)Ϟ=KII100#ᴷ_ $Ea(| ?H;:M\ɾȏKAAAK p%6Q̟?N8q X` RSSyM!Ŗqqq`ccpʗ/_Y+px ^hQ?b^QQQUUjbbB--}555%''S_lɓ'^,w{{{;;;HKK9rLMM<Ԕsjކ<=zTWWb ǩP` :_Y(0)&;wr+eܸqy|n8ꔊ/|a[[[}}}ɻ:𔑑VRRڪ3`///<@ }D:㳡@d|MBBBZZZmmѣ Bwձ>}9D_1F ρI ~/lI_O>J ڎ?Nӯɓ'>=H=="ri||9}=z5k֐.#󙙙TIeeebbbnnlvg\ݻG>.//x񢊊 y78|0fccc^^޳gϟX!_I*T礎D$&񔔔O?~^JLL䂂UUU___999ɣF*ZfSSS/\K/sW/..^ti>}^|y…y޻w/33S[[Ɔb\r˗&L M |}.\hmm㔔8swwdX<:N}$\:s`R$9-ZdmmM0q322][[dZZZ&}}} 1tP___jeԩ&&&l6{̘1UBf14={:Ų$or>}!^RRB-9rdjj={]vEFFׯ*TH90)~~~3q;BL\oE\'r%e333򁦦&ϒʭPUU1=zAuu5!8Qfivvٳg;TrOs`R$9ɣIL\bH͚Jspc j>:}Rr/驣d2[ZZoΧ!_FD:g/P4 7 JJJ?~HNN̬`0اfΜyʕGq8~v667ydGG;w+))R<,+77>p,pC1yzz2whii 2K㤻Frρ)nꜨ;lxC%8R_N IDAT77U@TTUB޵RKK*:u)3*W%b%#F@||<=< H6 {(0r\*c… ׊+8')~U@cc#UBMJ@>=}tg檤AUVVVVVVRRRRRvbzIID@@Ţգ\*c[M)KAZ*''7uuu<+cj*!ޞrp8vjQkkdTV¶mҎFH9 I&qd/>Q {{{ܸqC ;55[#OdŸBnC )]|wu▖sgϮXB:9:TPPܜ{Ao=,] mmx2ŋƍ嗗/_677p]GwpvM>;wϵ`[NF!_>&NyPS# aQ`---ƍcVt-ɦHmmmvvvl6_~~-5lͧaJg͚ennbԜ7nܘ.jl)e @/l=%88EUUUCC;,,\MǬQ$.3CR7Û!.H։q~X!$L.@p)<<G!9I76¯ܼeHڇRᔞ2㺴@r;<3q8u?$ ȋv&3?3f$V\ihh5cƌjqy?ᨔʠZ3;;"~^'H444\jUCCC>/̱u {=z}f'a&`#K2%E> ]tyI W"H,++333/YrM {%q++ژ}͛6ϓ0ektVB=v`?3&3@llU׎ Uuo .^NNNӮ$lg? Ig5 x&.,?ypjiXXX׎W$qx**tyf6)YI$q#z ON1t)w%]z6>LI $p'{Mϖ@=c'IMFԶ67H$Ƴ Hmr-j@ؙubccׯ_SN1ylFFFp٦.ˑ_zN+O2d݋-_lrMG]b(;LOQגw0M>.Tv7o}pFg\Z[>ݸ+##*y)Y~ūjjln K00P!'GD$DG[;P'UUO_ 9qW};w˗3\WW+7HQcx0oOv ?ܼq,>evCCSv ;n8aoIM&ԥf3&xBU#]f<ЖVz{gS'x\QaB9v|t+}߾S?:{6j|_jͻw/h?>qs篓I\!a\b  r|*n::l߬4w3gM02cζۿ_'~7_B @vӧ^vs?w6d͛8#Fj BUsNE o3gnܷyv|ɣtutu&^veROj@Br/)B3qBVTTוoٲ tt>`@@9D;jP1cpHes'ygĠ?HOO㶾nGE%O.DE%Hr&Uݼo t--˥<=]#G8Z^bO=655&H/)Ba9 _&'gtL. \QZ.1ms&'MIQQaQל$K@eԚRS%-\giijm'qrtṾe_ӧO^$&ޟ2ūR==ʚr33#$33WH8Lb?…ף<;~;OZ˗rl;ϴ\sNrcyy)|FRI`TIed?/0c8!/Ǘ77\x0i'ŋ7˪Z[>b^6k>wϩʆ䌝;bEE%Uq8յqw(y!)ׯc)E agg;8Xؘ~,E /*Yq6}¿>Dk~ԐᇆkiOwxn8}ٞz5ӯNGߺt~uA W_Y Rl\󯭭mG<4V^>͊ `0uuuK,133 En۶sŋ[XXX,'FEEul߾ښf;99=Oy&033kllolaa`0nܸ1[7liiٱckpppkw ĉ޺͛kjjb2 bLvS' w}L h-w/_?iTصD&7  4թ Ϛ繷!Os 8}$H5ŴUSXUCk .\Rc34k)D=*'1u#3CggvrjЃkћ&Fx /Oږ/㊓$q-pg̘qEҥK}jj*Urŋ7ŋӦMt#Grg#G&%%ڵOb޽nnnR>G}ǎ#K'Nϵرc]b Ev>'άoHH0}m;v&WqwjԵ,)Em;jpZlj]}Yx,4,x%h||ț)oj닊KFgҼO*Z_<̳-+q;p…AfgghjjSAAA{ȑÇkkkΝ;`nnZVVА[lZMKWnP%ד||Oc]|}G?rem;\5EݢWv>d _Ҷ2a}Ûi |઼k"gVkǚbuVC H;s)mT^UV:<:w@ ^||I\`m OLpmá{T<r/k4L>-..YbllLm=uTSSS&oTUUꌊug@K.7|p "ٳ۹"633T=J/X9| x x^.\_IP͛:,3.0z[SoNM5}Mڮ@][Crbz^Tr+r`t>;zlk -T_ϵ1] SO=¨gQO?aa"KLMMyOϜ93|jSWѴ.gm u/bk֬a2i-[RJrr)S*++?>(77LB/$Osrr$++ Ǝ %|PPP ۯNٱ/ [d*[lkh 1Wk[*[zqPORL95j\-UeR Cid͓[R_^ͻz5kOwJڡ)26٥CځAZO>۷j}}f*AU:q...!!!ZZZ}]`}9s444_EDH ӿ U^u./x' ,L +%dMJ&2G[+9B^OH䟶RHVh}o_XϫW.\uCa@‹*<[kc*1=-<7yl:;lƊ -r!((ޞLZdɒݻi ?^^^^^^~ر7RA~mС܃zz*&,&SMUjn"|`AV.[ginbv }]''sWWS`M]JT,%V.&.LU w\ol2P3 _|?}%/M^6d Keni'H26nlggf>|Xf Zf۲ׯرBڳgOttYY,ƍӅѕ;W; $bҥGOw%XxPX! +((KJ;o)>[yj{}QP3q$ߟGN>>>Qw߰aBHdnj[oߙɎs*̘8SĹ u9W)̽S:GW!>bҎ-^;S'dVHB7Ǝn/v Ghh@z蠝Ҏ)8Lݨ v態S*)+K;ԣؚ@DĶ/S8.p"$ލ|ӤiZZ N-[ݕ;I!$ L%͜S~v H &q 84l>#eĻ=Jځ )`kj29vH|h IDAT7oJJRHĻœؼII[RHĻlXxGځ P.Nk[ɣGfic~:$.yI97&iFEC974"B عa'H;$5l ?kpK:pB gegߺYHqաCWRVf()TU_ܹQz[v؟vk{9&N=UfpZ[{z), $yӳO4{9u5QbhgfFH;.p8Env@UKmBiLEC 3gY^R4~g uuҎ ) Ĵ65}ӷ%ScARVYPPYe1t SE:otC%jK;$}Tu==q;sV!!EI\bB7C\ځ Y4dGWkjR4% 9ez5Q2~$ d$>b6ow:Cd^$@$.Mo;#idˌ"44H;P0KsmmnH;$Oimjzr3Fځ I\n=6hA2MĤpi &qq<|Ci aGHLu_:I;$վ~]xᄡAXH;uzxZ`,2H(i X+ [0_ځHw\8a\hiGE1dP,gMH;B( ǽ{ɖz iǂ_x׵s8C =Kځ yb?gRĻY7%%CϓVgۖ3+WnhYKЌYï,?awܺ_T% K韠ε?|kmG z۪kC{ܩV>s熦p-qo=Fm3QE>}̚}Cu7-S7nJ+ؤ^qE; y㖆9--;wL]3u흮nHcɽ̲,3X¼"+ل}xkKԮ4 ;fd\HZXlH#/Z8|`0]<7h[5|/$h9I~$/01%\g \AS\:$)AU[ Xrëԋ@pjrQac^B⦴Ԟ5i\ߗa:A.C߼zE-{57UX׮(XIyW~R];~ U_l6d/HJFK8]cf0$6IL} Nyy(fwH0wsՃ+M\+Kˍ55U/^̟G!WH:x~ٺ]n w*LcǗ6opjԵM`uQQ[KK}EEv}mUVXz%]?snlު֦gK0\u `~c+6 ]Ô#jKKkKKݬ蝻6#sq:>}q0/HJ6aܛ1,yX1++,*ɼw0wH>͉q`/L-60Uɕ&W ;$8:UR,?ȧ? Vy# X<ε6lL>tSݚ޼v칞CtϥBvѷ; fvejiVX8 !L9CGu}ͮ#y=h'4n>hoo_&:. t6qtv @ub1^P_τ1a{H{Gۣ4֒O_ee]>X߅US>x|Qשq'I 8elJ[Kr7]g]>A;.pG'7j$ 1>S& OҜ\zan3My3kV+1A;`M  W xe^$si^ NU+?٠גR_ISzz"LP!vV/Sxz|;n%HGW]XS'o"q3Ҏ-w.ױ ePqfV\^VMq^߾ >ꔮxŋSH;5=A?ۑiW+"&j0f?Qk댁5j8i1nӧKΞޒq:~'E-{򴲠k`Yx܄KócWUJ`doD \Bn46߅J~%0wEvemSӾnåu7=;~#.pc箆Nv!M##\F}H>tykcc[ssi䃇w{*>O^ {_EEmmuee@mVV#lsYp[.n,_k^lkn.H}h,S|ĖՒ'6|z巏3jggPU+M##vدd^b=Gpt r1=T!u6o;RN.Yvqz 4 l πT"dcp+>"[ezr|t<!Z0u ҝ)) MϺdnm >S`]QVQA\!moSK L\d_?*#c)c˖hh C^$>vi;llA?o^pXw?4jJ}+.|,5\g17WfXjjnܰ9=MԪ:a`3e6d?~ujdT,5>.w[KKyn+لg"Kُ}_JE]]tɈ]T]Ԕv,=-aszY#{\vJB9g"{xjRHY{s8w%E>3/!͛7^%<|yۗ:=T^YaD$/]" ^b(v''_J;QCO]Kq.Y².K, \!ũ3`'xR~g)PY8qAýLJťbeD㤉ff**ڛׅ3i$R1q\CҁC߾x&@"C]y!B$<MJ; =+u&q4'%ۏ{Wځ a1ĕ,u[[&v HA0l!)cE{'-i;noKav00Ҏ)nË9--+Lj~II׏sWą a%[!&,U՗+L*HN1ah(@BQb2M84.+kin@vY(`%\4F:Fΐ0F'dq4/DDcL"!&(B"F ((Ѐ@o@N*^[n{EmA 8W5%11Bqw1#CaDS{|pL;q/d 4T\Vvu=2Q~;,q46*5%Bww߾# ~7.iqd|v8MQQq?_8|K2[``å1qO/`{TRTHj.> s z1?Ѹ&/E82 qG"(W"emm7 T]3]1+Խ@ ωk99) ੷t >!5,b-|v6nZ4va_4]BVghUOO3Xq|ǎ4~>yL#dBCx66MUU<u6&voDYSu Y+@TuANޣz{%ބ;LdݼB70> Q/ %E&bab<G}A\'ѣ_MdBCt 2qB77gk;㝷d<^aݓ?x` AQ`ccWh(o4uG 7b VEfͶC;Țy>l]0=R>29fLdl<&+EQ,KKK?덹q^rO(\Mdϖ:9;bm)k=/s_0017=Ҡ@h`H&O2uPYY`\b }b쮘!q|B@9S8q靻>a Pn^DfuouԽ@h`G! A!,gCȹ)|.K;(hW^tE ; w9BV.^<2:T,i.x?Ol%Go X0~H{5UKOxv?蔔W IHaQ9Kx3q+/ |}:뮥1}d dđv1 X0<^AN#X3|1t}}⸓~SjC]=İ?L+7lݩt?w{Og.hF2<4`,G._hs+yEnp%bT[Oo=?!_{^}#.z-Oj8m靻?}\Ǎḏo!)H%̯. 'OAPMTX 097iz1Y{N"d<đvvW j5m|h.p<4^<?OO W&w?*"+W9 뼥z~틃Ex0#Nگ^w8ŵ\\r8=2"VXfNj'<2.m3}[Ȱ'r+EdzzzWfಏ<'8קFcP~s#!+^Q(:xߗggg hP3񟛽sr􉍙 sNYfס~lw*Ǯ-ls/!<GTg~>~lXӷj=q祐?D>۫5 _m 7w^x^,=#/=?G#.x' ^ʏ>򣟹q涪[,i.p~%q@%?ͩisM};nT稜->g]uG̐YYY?|cg_5`}Bcsݑ)٦8BY0 !d0#ûSip#t$. N@!˂<'Nɞh PK{3b O  B 8BY0 !d0# B 8BY0 !d0# B 8BY0 !d0# B 8BY0 !d0ĴυE֬|Gߍ j_͇Bݽz&suMMf'̌kz9!#ADAeb{N/ 7 G]OOo~~ $ߒSw !v%%Z[̙3ӷn9{RlyR3źo?ϿNy t˕;w~YXp{Ĉ$.6̓ٲZ}f!K ?4Dn/o'MXtz{U9 _rS(YYSw8[ ۼ)"j*+kfcPLJ4EQԩ2nCӗ_敕][~z2^VXx𢥜AA|Pk?ϷSԌ;+MMoowR>V piWڒƍYp6mF ^Y{o9p䫯>˥[|T[_4a͛wٿĪU~9x<3mT*rCl!ygT*մiȑСS>RQyyg񵯿8iR${/F yrӹ' ^do?$""W@Qa9x{{+7lGGWL̸EhEQTb9k*]8i3KxӾhh."Džhܻ'~@uu=(hll [\p…+d[o6!7 >xcݻbkVEwG}g: <(_' ˊR4ر!?ts~:8{>d`/rIe)I0U>\Q3YD>_t|}W7I{cc3]rrZ?ٳ?=#)9k*@]}9FCRS*̓fRQY3i]:sT$h>eA BqdINGRcG!82V?s̒%K|nj3zzz&N$ cccsrr>c`"eee1x".ٿll}HHۍT*]jyjkkEJr˖-"H( BHͽ&ɥ7ۻiӦ {{L/(V[G+ xGJrƌZ;I>fϞ3<۷o(JR-ZHv!EC߿(\>m+L>]Tr@vq\Kl2jYYYIHHHHH6͊x\\\^^^[[R߾};DEE[n'';w򒒒sO0_3 IDAT2߿ogg󛛛IX,|>tR$** 6l`!d1bDAAAGGӧ`E%''޽{222 )))\*P}\&{ ioo}v||<L28 N={a~v@7vޭO8EQg]v;wx"f nP!E#G@xx8EQ\eϞ= T~;L.@7OTWWC4`5òW_}ARA 9d'nAСCMN4 >L B!Bvkk+]"HbJSS.>Kodt 8VčrasƍEׯ_'Igyã֭[eeef"K)^JR_ ;{ ?|:0}j៥&*999,,'}w Fcǎi]_gȐ! .߿-Z4d4""Ο?wFȢ. ,, N<ɬ| e&ꭠFPb?P7xHH[---YYY>>>m۶ wY__/KKK pZ,/^P(//ݻ ^SS#JrwMA:~a>b833~{a٧wr\R_Y_K.e('|Ro^zIkcǒwj Յe!z޼yAμpԩ 66VPp@i>ɥ7h6R0{zzRSSCCCy>> MQ*իW13JʷnݪٓSN%$$|@tҥBL˗9::Ι3 ˓t[l4 erq gCY3OφNA! A!,q`BȂ+{ [sǼ3#qA!3glx"B=S8RS\?t勢4%_,XKgNEtg6 k d,h1=-?vo j 痛ѡV}Nׯfk3Hm!%!{4AszZ u勢tq:/ZYr;=Zkq iL dY8Ni0+_#K$j%gR4&~CfKC}"hNO`AܼNqssSdp9 !K2SONO40+_wSi"K)[oȬ'; SY:'&&vvv0{>rO+7@JJD"ikk^r%YgϞZLP(cbbIޒ%K ))i׮] L- .5-yaSW(J9qvYC.tm!PL18s ,8>jS+wtttww_bEWW RNa7dNX)RV1=BȚaz6B 8BY0 !dL1B3822 P18B!+a?њ=x2٦Bxn0Ϡ,aOlrI5Yd֭fw}W=B3'6r$cǎ|T#5)mOwHIIW(oY(.oUC>Em޼YmCeeetf/{7HK]_i'Nd򕌹pn}YQ2pI5+ѣ!Ɇ~Bh>>)񑑑---iiiw{n``4!vNJ%ORyضm9n:!3 rF9S= ,ommw+b"d0`~[ ѕ=IM ωiZ?V˞ U,]$>F0_gٲe_nj> ,f1\B!0[ 9o9s1cL2O!666==A!3eA܄'BR9qP1# 0)!`G!dLMTnٲE$ BP(RSSu5>Bڹ%%ִƺۇ_^z֭x'''OOW_}U*jao'l?c*ӳiӦi.>}R$3;ƽB {%%PR9""(X\4|@ w%G5EQE^*wަC@JJ^ϏР5d2eUL_VVX,J%%%QQQa֭[AAA#F`6j',~N ={H$_c{} Si ?81 CCC)".ɡo5x^vڋeEP(ɚȻ™\.ŞtX,Ri{}DGĜ2WLF'(F,vttJ"&+JBJVimmuwwm|Z FpXRBmoo"[ k'llaaapIf'>B i#""Zl 8s ٳgw\^v}?Ӥ$zGq5-5k >>~Ϟ=2LPTUU3<ZqqqWWWqqklVo 2lr|ԩbcc 5 BjS@P+ԕ111%Vܿذa9s2{ g;wnqqݻooDzn1D cǎx6a„+W?B}?wȐ!#G|7!C_#`̙3g4u/ЯH!,9908n0# Bӳ:;;_~e__ߴ4H$/vss|  pK"ep+x<رcU*)QTcǎx?ޖ`ӄ hǎ{ǘ%U'2 BI8;ɫygX,fiٲXc曞M$UTTL<*ܓH ur9+JR ͹c#ҳwZnqI q: rf͚vڹs',ZHo˖ꃸg,Vsy}]{)r\!4 ˵F544DDDΕ+W9- dH/́}$R*JRыԒH !sq{{{|Td"C{z6#$RZs\!4 ˵Flذƍ6mz7/_;m-߶m8;;ܹ^.2/l}gbX,gffj65KI_Wٻw/ 6,==F*ׯ޽;::ZW pLןBE82ȿ[raooL& 9}4{˖ω{z6%bX)B]9HZH$~~~C޲ nl\J X2(Z!K+a^x*܃pB-[ ☞ !dB 8BY0 !d0= qF+ .g]!?aM̟9ǒճ#w}W<46BȨHY9hSJ+27z$Bw$RΝ;WQQLQɓQz{{KJJ~駖&[_nnnSN?~<&9xbMMMgg'Oc,mӻּj;;~ZH_rE&dQEEEyyycccww˘1cMF^ڥR.^XQQ!HrP( 9r$"t+Ma @drF ⽽_|͛7ڍ7۷^xƍKq??G2dy L&uVuu… G1w}R4##T*/\\]oڡC]F(СC/_K$ə3gVXaoo@GGǵk׮]qFEp쳉} F 2F eee7o2d1c{9sJKKkkk _WSSSZZ:e[nAAAr<77 7:iҤ>uռB2Y:&;-9s>|xCCÇ@_xqPPА!C~|СC₃|~ccɓ'z꩟~ O>aGGGRysˢAE:c40Mm"3a ^QQqqq&L % .rG5裏ѣG/_ s%UVV666ɓKKK9"J1of͚3g/V͚5#g1b/=ۯ]SO477x>>>AAAF"b,B˨fMc6CهV5\/WF& U(=rJvvf!t0ziz+*'s,334]6Ν_߼yC;44t,8uCo5\F5l;iF&2-׾q͋9***&&NTnڴh}-Zj&^ \vm}}۷nܸqƍ'O>,sdr,:!shãUUU"."wQRHKz):Im̥cdwuuű\*аj*JhlllIWoNz,B}Tw #3gIoΝ;;w$}^^ޥKRT*E Bz*󏾡C3gd2\.z9vLWn[[[T*kjjR魠c=_|Eyyy[[[wwwOOOKKKyyyzz:dddTTT;3J]dIYY٥KZZZlllbbbOSii˗[ZZx{w B(B87Z]!AY<PE Bfβ''.վI+X+666<S k>7 ̳WȢ[~xp'>WCQToBoSNxlHA޽{O B:IKK8qP(!:L|ccc]]]CBB֯_|fܹNNNWJjed~xSZOwvv...iii H/^o3+2xOkT*l"BP(Dt3g,Yߟ{xx̘1#??!qѤT*g̘k[ *jѢEDDDS\4|]JvV]_͛lرcuT}oJ׈RZr|ڴi5OT*u5=ʾ!󗐐`^Qwؑ䔖Fǔ̝;Y,77(8p_VVX,J%%%QQQW^ %%~ĈOx"K(qL߉š5kҥKJ%O>^A9XF\TTwަC>}?&uڔJe}})ڼy3ݸVp oؑ8yݻ.%=|0"f[ 44(ǏK9 _9fii)z]%+Xh*Lf={H$"_O:5{aÆ1p:88l"Xx^==P(dMMM^^^KI;͍.tttJEe?JAPR-P*I9 V;+Xh*@ b']G,{{{ Tz j>tX&Őᗑ۫RT*=V^ŦVbq r7n(W_~L&(ԝB!ó$t%+*""Ο?bzB6I<55+dHzQ'N_f+9uF}wj5OF9_d $%%ڵAPѡ\߳gOmmL&S(UUUXXXYPP믳wΖk<{kڵknnnnnOE"HڲW\]3~[ |I]ҵDNC^z>Fؿr)^!B SN՜Y EK.k6)R*%,NJHH """))ҥKdiuuu||+e[[իG'+Xpr<99LI@e)IMM G>Z݂]☞ !dBkpmB8BYu$!+GYx1,{lY^B4 !hBȂ,=Z}faOOϿhoo!C >3LFWМYYYñķn ImY( ߯W*---9993f`Y=994O@JJ sEḤV2ydR?==43 mѩxd2)!L do瀀۷o7N28čdb1cڵks s>EVYMo8MTs 025SfC>s%? 5 j%I&g1O߿eu{X,ljj@\Q.t4G?lmmU;: IDATmmmmllH5 g)HX~tD"}zW_}>`>,B=%cAwh&0d 1117n8{lJJJBB7|7G3Q3ƩCȺܓ>agsf5.&&7ήҶQzO@o8̊fBP;5${C@ݰs۶mgg;wR{}N8ARC:u~Ν-[P($K?ȕ77>L,LWWWqeK޽{`ذa555RT._~}ѺZ@hq~aevu!!![ofeeC78uGcSLM>0n3_Yr%Y5GST߃8/cxd>˼Ő}v0}اdoZ1&OFQ޽{G>YZZZ|`>'rDg ┾qđ9 㰳snnns̩+L%B ӳ!2_BȂaG! 6pA9 4`p {$roG!q` 555surr\zT*U%K)`mPcȰ'6[su4{L\R 4tcgOlH<%%:/LAQ͛D@FFƁ4S\z?4ƾ dp v1ް#qG3)?ZRA;?ûS@bYAPY@kk;.\P3w8::JR[[[z Je_J8S{5l;!u3JfOD3K|0Bjp rF9΂ $OJJD"ikk^rZ#^^^-5k֐S[[+ EUUUzzzLL1!3mc?en[ $КmA;i>񎎎+VbsI+/B N7V1BȚ)!q`BȂaG! fA299yҤI666ZIIIƍ k֬}w!=VN3^a„ ̯7o޼yG}Ԙ]C`Gcƌy\BTTΝ;+++ EKK_H޾vځ'Be_z3f9rdHH BH={(\^ooM333W4l!ӓ6qD'''Pì>8NFL|)92=: {KV'ͥ|Qte˖U"}ikk+,,4iر;yR*3f5#qY@3lذQQQ rrro߾M6e-:3,,l޽}#֭[)--Ν;rdܹd)yq2vtt > q.Y8ϧKYnpNNNk׮U(})Ӹq`ZrG'ÇƃA ZBlmmmmmmll7q|+]3 HJkzDQ;e^!K!IRdH$F# n NED$C>@N9|08paBȲ#͸D1Jz6Y2ݵc`65 ;f&_H\oV'.cǎ555'N+SL1xK,]v544(yq\}-ga%_iòSZzxx\zՀB )'|R,; ωhǎ:@RRZ ctٳ/҈#߭[~3f16yyyǏMFd$ !d0=B!A!,q`BȂYUWDY`*#`cU/w[!B #3Yz6r_vqqMKKDxb777OOϷ~yp1!5򍝞M5=ǎ{ǘ%lPk)]_J;Lv?YpҥJY'Е5HQyfcBbǎ٘u[l;v젗rYeeetz/{7^z6RݻZK8&!Yx^veEGGGTjkkKW#>O2r=`ccYH6.\PFBҦY%P(ɚ4Y >\#S̒H$_eJ_z6 VB=BH gbIfL"Vm3Mz6`)l)s#g]ud/1 Daz6vVa4 I ӳ r !dB 8BY0 !d*[ 4#'ٲ B q`BȂYRfT*]jyjkkuu@TnٲE$ BP(RSS{+ɘ( G>}/T@h`L &1[{! fg --- \.6mfOT*)"oqٺu+MQQQ\n"4ا޹}3iĞr_XcF 6l H$bqqqQ+}:00֯_Ϭ@>'''޽{222 )))Ewgg'YE.>~y޽ಛ w.@vrqق#fj)Lҿ? fهT8~8]rgV ### 33ه={ĉ 99|%zn"4ا޹e0'1[{ʡ~A WZBlmmmmmmllHg&WGWhmmK$ sd0;Fތ,kjj@@=z<<<:::d29 O9DhO sdaN.#uSZI ñ n1}!>>>22%--mw O 5;g bI98>bsaa!kV 'O2 O8dB;miu|{aDtM sda;1[Ѐb?P7޽{`ذa555RT._~}XJ>]tssbX,tuuƵz{{Ɋ^^^R^pM;L;CBB୷jiiimm:HptdݕrԩS5P([ɢM6u/V@hO sdQ#lAƘ; E ko-!_;;;/_8gΜj]ryrr2Iw""##l٢)*++gggu!{BƦwj z+hNOӣ-JҠ‡dAܒ2Re233׿nݺ[Ffr12vX/3g3ƴA! 1b8lG!뜸BذaCpp02Lfu$C^QBXG_~%:tjj g#:7o)BGt% $q=]SBB;v,:JJ5vX{'&===iii'Ntrr 999 Y8Rd>>>W#cӀumE>3V{1BITΘ1C&ٳM‚ 8 b8iW45QyfRLJ$J~}Djر#??)--Ν;rdܹ:~Z4NJ`܎ ro 4כYf]v -2` *ƍݻwk]J&{4HYcG}x%J^Vu j]Y!H W\!XBBL她DLFС(~z\&iD"g'_?8krwwRi`ucɂ=%F(4qL״aÆ7nlڴ7߼|2ɝbA2;v̰1#HIIH$mmm+WTsԩ;w;vk׮ ܶm[AA! dHJJڵkWCCB(++SÂDfy]ԛI"@NN)/ 00PkT*|I]S[4gJRX z5 0ydzJ BHRJ9::?~, Nf)>[Rz6+;! B 8BY0 !dEz6P-VϪҳ!ޠHF4mB0=BYKϦܜZ!.K*)]vx ZT*l"BP(DSjlMke,X2Q\.6mfӧ+J@|`z6vVg%K&*͍j\TTwަC>}? gc|?gӺ9B%LT呑ɬgDlH|ҳi-,ԛJK&*͎1\.b[ HR.@|`z6v~ae2֚,t٬gK{&*2:{zzXZ 'O2 O8A7el3֮]{ΝW^yg"Nfggr]-,Ztc߾}͟~iRR!˂;SxaS=VȞ꥗^Z,˧NBll,yK72_ NQ޽{G>Zj,V^=b>2ryrr2iA DFFnٲl,>cz65ûSB/ !d0#3}]ظq7#3|E!ceus3#q`^`<^@4hqBi{eBBȂH\RwttM:uҺ/tvv>|!!!/^H$r\(DEE9RvߑNnnk|ԩSe2YnnnUUĉ|Io Hjjj"""~ۃn1cL6*"Qwww^^޵kzzzFJO---mkk˱&wYﳄąq򹹹ȑ#C %tMLv֭ =Zm]vڵk >UrС E^^[QQQ}}=YZTTE^ͬoR4##J ۢ(СC/_K$ə3gVXaooo. СC׮]#熆?g߾}w566666޸qcҥz+mfA40nu֋/$sss+++I&W~ O>aGGGRysܙ?Ϯ~>,pV\\:Ç߽{m/_:th\\\pp0oll>e!c  zʕlFb͝;믿y͛7IIhhח|prrZBp[[[2kJrS"s`jDV G'j@+Y[ g8283eӧOSbggT*7mDW \vm}}۷nܸqƍ'OtP8sL&W^=~8BFFFEEŃT*0R XT9yd]]RSKyc_|Q^^R^^>ZRYSSC$w%T*JǼѕ3sѣG_pΝ;]]]666nnn!!!̿XG ,8y?#őigg?---<}ܸqZ1BXS|"B#q`tN||]\\|}}@",^f/9sfɒ%|>cƌMܺu+>>W_J'joo~v#2B%JWZ}y jrrro߾SLO@??܎RH}IJrFAhA?xii)z]%*N={aØwpp K;6srr.uG-2?N9rÙ%}t  dT> kWJO\.'_ j%<|ꫯtJ*ڭ&$ P([[[[[[A>G [s:1W|dt =%WnD"uѣ!a~{͑;D^`aR+~7n(W_~:j}iWRw!Gh' IҒ{wj@EZ7^HNN #CM6{ٳ pyyL\XX5wxl۶-99֭[ L & IDATD֖rJfgy^{⮮^{Y\)߳gOmmL&S(UUU:BV&11_ٳg֔ n᭭zW lK^dVϥK2+ܿذa s;;;D!BF-zo1wj矓 6m4՟7 ӓjoo#|l~UUլYBʕ+]^^^6O:ADDDRRҥK #˗/wssstt3gNg8?9@d=*Ejۊx!*=ljmtn[][*^`kE+TPF0v:N$$_Aygd2۷Y-srEEEU'~7xm۶EDDeee/ $%%}'6mmO0M=~qqq9<1NTTT/"3Pڑ#G9,yT!q݊SLn`W&MӧWXqKKK] !SgmmB}̀ħL2e]!͘hkg})z} Yo5kW-j+#f)!0MqoimK#2bx%B ٔJ}V0%KD">[ĭq͚57nߴYf͚5>$%Emܸq͚5S0DqӔT-zxxpYD}իW^cݯz< / GJ+bLLLUU3C]y?'ysy1}1{޽{Ջ<^Dbf<[UUUtt4GSNhKp": V^ 111OG1mW̟?>]wƳ!>>0;p(DWٸ4 CH] J7柜ƺElɀ}ϦFh&Axv3G!>q샌^#dRǎniiٷoӧOkowdd}&(UKSG4}}&wXvmUUD"KKK  dL1c&˗Ǔ펢wLi?}}&ȗϺ y葖vG;{>>}/++Ϗ ?qݹ;a#}t}_bEjjjII }?IWrR`&=O\>UUURСC ;Ha&M֢E;|1*}d{#}^<٧p…Bǎ۷o =>cxLAnL+mJLA|`Od>rP7ԅ>̀1qci[Oӝ JȒ}2xrunz-BH{t3Zot B!gC!W!dcN;ꌛT*ݲe˨Qmmm"Dgkkkkk|B!jd+U0 iqdQZAwai)Qg$ +L^Q,.0ayf?|BԔ\1ͬ*d踏[' 'g76c' QgJٴioٲR,QEq䔘X]]]]]@j샟32ёU2n8V1WrJRA65<==YWBFFXL/ǭғD~&7fUH)q3#F;w.$fa||<}P)DTY|oUqJOgOFA\lQglll~ (yP}MM lll}F(fGUqJOgYi0A;Nu=CԔY|_ ,PSg ⣛ԋ:#\C Ǐ3 ;Fb4UHJOU&هGح/52'>~={TVVWWWGGGFkpppضm[UUX,.,,d~?$%%999bx> 4h|guuu )))zǻAtttr777H@HVɢٔ{+//Dbƍ;w;v,3~:;qyQg$/,?X<~xEAAAmmm}YB^*R;JnW!C}*=Y85{tFIR>H$X___kkk;; #xXEFGGՅAsumT*2oCplBQaQQ8880?BH -dQlR}HiL"vJJ믿]ǘQgW,X ))O>!#W_T]wD[)U!e(fa~BHS 8ǠY[[a~Bt9vtIw!d0 !n?Di:s!C+q2`4k$֭[|H$?Rȝ&DL00Kx+br__1jVSMMM>}0*5?䟣F~VZ#1QQX6}7 ]j? ibm!'Gy)ϑH)~ p &\㴴4ѣGqG#O$RZ#17TrgHV A8C9jkAی~61 !?娱Z_p4;4D5Ξ=KBFIQ59ᏡV*rtU;=== &&155uѢE62m4Xti^^ޓ'O.]"dhRiMM͡C@,~mmmqqqM)?o<ݻVTTD' @jj*ٺ\˗޽v׮]"2|GݛȌ3B!d4t!w%sqHL(wU as1k*tԩ...-~:=C5J531qɒ%^^^B 5adzm۶-"""(((++K}A)g#aaayyy?~A\\guBHg )Ga},sϩ)B COOO4iR>},--bŊ3gXZZ_!3t%>uT|61Mz ³ҕ8B!CW /7BBȀi7e}ٲe ϝ;?g^;+ D"ŋ]]]fΜYQQ(GT8A (Jۡq jmmիH olU 93KvM}:fV|"N2Di;uo1;FMN455YcRs͹yYYY7ofY\\L}_UU' 퓍,vƍu[fffʦ0:uuVzlEQ_}kCEEE~W.^ߗ[N^@QÇLJY3 [?B{J\gW$b1I(LD X\SSJ7RSSncc#6HkkVON$%d@(J$}AHڶVWW'XOFr}`:ux6#x6mBaRg+*x6ĵ lHmC 8Ƴ: Ƴ RZl{gΉݣll(7gsڢP(`Cj.S]QƳ8gCFA:alYd2C\`!+qd>S(H8B0 P78B0BȀ BLl"hŮ3gά`UNH$~~~~~~  lCLYf1QWWǬ%e; >>> Ql6>>x6ma<7UxnSI円'''RbmmJWhmm3G(233[555t555666"O#vvv"ܜ.! BP"pBֶMvss̑{:ӅzC 1`(4Ba`BfԴϖG<77d8~8رcG؆Ƴ8^GDD䴴dgg/[ Os]rWw][[[[[k׮Hz؆Ƴ:).~:do1Y,?^AAAmmm<0 ;gfljJ|Ϟ= .tvv1cFnn uN:EBlll|}}Oٜk׮mܸtuh{[fff&0:uuVzlEQ_}kC 1W(>9;|za7WanKd"((ʕ+0a٩X,0KE>bU:t + œt}v}X0g!d׭_4}Ly?ؔ-WçW!&p36kfaVBLA!pG!8B0BȀ 1G:= Tq2Vx%B q2`ZĥRǎniiٷoӧO jfJn|*pl!l&[=;E6 G*%^#d@0>;EkVUUI$4rQ\WD7>n!l{WJ|С|2Tv-RjF!l܌J\A蟟O"h!!!=b#+P__,ssssss333333zF[(E TWW]J~Ctvv2ψ(4F?ke:߿,???&&&<<ĉ˖-㳮nJ+ 0͈i+Ŋ%%%@M!TgJDE Ƴ8\&''WUUIҚC#< RSSee)Mt2,fg[`S-ZDr?JݔVPEƳq39q ⅅ . ;vJdicc%KB!A(En<+("BA"ƳEJqTʊg?L[ l!cl!!dpG!8B0/]r{ޞ<>u$|s!՝wȑ#/oݺ}gggv[B+LJ|ȑ6mx'O;а|r]w !2݄ .0_N:m̘1EEEB 4l0{a(K (dp8NDgkkkkk|B iAܑCi<ه2Y{xxթTAnJÀeL e &H$>(u &iAܑCaԗdV!y啝ܜտXrJ? @L>)11:!!Gç3-;rH#pdc.> >>>*U>0 2At$*$QϏOJ3gZwF M>r܆y $ IDAT}a@eL @z[uu5@xO uC~VmtCwF $K?-#rd^z<0srr[ r B(: ǏgV>v 3ӂzsǏ߳gOeee{{{uuutt4ڒ/޽{ǎ#"]l&&&@޽E"X,qΝ;ǎ+wu@>tvvjjjjjj69*P*A >!%%\):% StZ`ELAA0 đ>be6>( ӂq d* ===׭[GG-hiiYp݌3n߾jE/)ea@8#tQQQ$7::TP R;-q5i쇟V@!dB q2`8#Ӈ)pGa 6L :)!Z)!d2R Μ9#zfa[[@ ᓻƝ INCRt˖-F JKKg=nAi& |v EQi&f[#TV4' !‘?GD" -E}`mیkZ)@wwR" ɅBȤp!O"$?+i&߲eKeeX,.(( mY? NSm8kr(jԨQE^p\yzEEAS|r2)iy1bܹSnO 4܆:l_? ={lnnnmm%vb(hOB&# !Yi}>8(8M% ݷ^|y˖-߿i8?rd2$ ܹsΝ;qICܿ^_ƓțN?(Y__MfBV5EAS,Ei/J듴#G톢I(8wssTXZ4{l{GDD䴴dggӧ~mmmqqqLM```rrrUUT*9tNEiy@ddw}w޽":Q㬧?/_{]vEFFҋʸg[`wО={H ^p)MVCȤ=4DUK$_~YQSzjDqas,YEO0u8(eji&rחH$$.((LYڄQp 0A\7l ,HJJOmL gӼt,B*[ [L2lذn:B<:((|!PWt tgB?|8B& Ƴ!4zx6vVt Bg-]RƳ:/tj6MѢ l܌kZ=@رcb޽B / 'vڵ76֭[333eـgڼysnnl<[VV͛5O:Ŋg#A`{x<K5GĻ;=0RƳq3A\7w,]199\'}@8xp{{{NGNgY_0gݻwܹ{///UzLYwdz]zuɒ%0w9shig3uܳ-g# "rO(gxwdzqO!Ua<7uφBB/BȀ B؞'.333?~LQeKtGMa}f0 S<썘q]:'NpwIYv%^VV8pdR8.'&+3A D{%!d42̙Grg.CGGGAA+WgϞcǎ577g"]~ϗ׋b[[[OOѣG8))))..~a{{ax8v !]_KJJjkk)ׯ߸q㘏"+..nnnvvv?~ȑ#w9|yyyKKU߾}Ǎ7h 9W^yֵkv!.zq%§T*ݽ{wEE]Ç7wQ,yssׯ_~z~˗/KϞ=[ZZ[YY Bݣc޽7oޤK******cÖqW@lmmu۷̙CTN_=Aj ~zE1KJKK+4[XXXQQamm=yd 7nddd2[-r L0瞳H$w_… /_ѣɓ>|x{N8cאa]hӢ7oZZZ+Æ }ٳgunݺ[o 0@,EEE >x^xS(>yڵk999Ajܹ CJ(꥗^ @|X$ =bmhԩ`tHA`WJhmmH3<8%YSXXءCn޼I:xYfّjSg@cc#M `,g]50mmmիWSSSeOgǂ򃐞͕߿omm={˗/{{ʎ?NSGGGv!Lg]eeeQ5zhGGG DaFH67* *]z :;;eK.?ON. xٳGJKKR;`ffֿ@CC7|Cݻx~)Zc~={|NO&NHǡTbd(5 k׮(NKK#FUMq%H$.^A/)!!ǝc[\\.Jꊋw ! 4'N_?~,J+++s=zٳg[[[bk׎="Mtʐ!C.^|/ܿ?I\-))!0r;vlii;w~'fرcΝ;wa1F"E)x҉1cƔ޾};=====]GKJ.]n"m >ydPXZZ̜MVH wyuuueĈ>y?~leeGnt)S :~|䉙A9ENϛ7ҥKuuufff)@ 8|SSȑ#q7 샌GFA:d>zMGQ  B3BңA!N G8^k#*L pO* B7P[8!MA! !d5˝1`J۷;Ҳo߾O+'''999YYY 4hʕMMM*m]$-^a̙HgΛ7C(388833Sֈ^z11bGGdž  `eeCmKW`?aaaK,DVPo9~JH8xDgkkkkkK,)q (NTZG.3 ,X?oÇ755Y{xx1+0 ٳU)ݻE~0wkkkY^s*9! bB Ł &H$>O^'SLєpͶWt6g]J"եSEد_HTPP0zhXj{yyegg777geeή\T>6fe{-}z޽3֬~'f|:FV̤+3;88p9Jqz9*JEHx椤$*ǧADyq)8v옧'74~={TVVWWWGGG-ӻw︸rH$oܸsαc+P9ϠA>khhHIIի#Oo>ߒܔYlJEHx򹥳?PSSSSSDmlrT>6jb8A[iD۰<-"K#""VRs̙;bbbXm|0>sh>kCUZn9Ur,BDXEX,?~/"((OJ>$>vjE%&&:T(zzz[&K .\- ]]]w!JO>ޯ_?Phcc3|K.+hiiYp݌3T;x`++/֭jAnsh666.YK(pWqƳaB0x6|v B0= I#zm;x%B\ܥu!'J!c1RFOq<BH)B)8B0 ~~~ %%Y"K&32ARt˖-F JKKcVNAJZ!=/t?0}tfio!/e{LoR&D|!?sqtVn0kJ޲8qD]])=yP(|뭷HlzEQ_}}+*Bn֭[laaa:Nbmݺ^}2Ҋܾn/O}ym6 +LoRc#FΝ;.%w zi$xwed@?'NLKK{!tCR`]z04K󚛛K'cPPfz4J[wL6gϞo߾uVQQQϞ=NJ_(M!#l ^"0+[ZZΙ3eoiiiI!C_~a@5BH3<tHi>q2yw޽{c.HT[LLL}}}cccjjEX@jj*α a޼ywݻwÁɈyW}(zgV׿27a H$/S[,JZA6wa@DFFқXn޼a% 322bccI]PPѣGydD8Bx6B q2`8#3A+2 "ӄG3A\FSy.0`2J&z%BA+xEE@ prr$%%%%dtvv:99  z䠠 '''++A\ոH$Zx̙3եRǎniiٷoӧO >}@H`IB_/8!%/^///򲳳7ߔÛHR2k,f:dK;(l5>}@H`7ڽ^|E#/333FAOO(NHHطo_~RRRjjjD"QAAѣ]qFfYYYڵk$I]]]ZZRcxO}@H`7WaWi'c_^(:~8/dĉE n߾M=O- hhhR__Om$#]􍍍 TWW]J`@ПTPt-z 锿/=*--=y򤝝]@@@@@ɓ'=zԿooopNCcbbO8l2}zOcĴi_|_$1|p8wj+VHMM%>dɍ &8-'N>ܻw_k׮!~!WTT3iii&ӧO䪪*TZSSs!P,76cg[SVVFoڵkt͛7#"";I~f-rW\hs]z[b!}>܌~N\l)[![ 666Çt --- .tvv1chaa… B;R)s]zC }@HI$dH .JY>'@r1!d0!A! !dpG!Aux=G!m!ux%BL[@VKEk!fҸЩnk B`<7ڽę8v@ ػw/ZB`<,ɳKf9k䥢5"UbmJ߸-<vNYtiqqcrrVRٌv޽sNシl#z !$Ƴ8m]_zuɒ%0w9s\KQBHg3uSj9d}(z !Ƴq36u%N>lQ՞={k...BD(fddƳ\=22ϖ#җx6aK7l!dR0 !A! !did8Bj+q2`8#A! !dpG!8B0BȀ B q2`8#A! !dpG!8B0BȀ B q2`8#ӗx6b|$fffÆ |I#F<ӭS6sGAH; /._]TtY1/g EWX]ZUhwΝ /voT` ݼds Phoէ *]w!J\X1_ff)++ِܼyΝExz8?P0*$/3OS~kjjNL;}2ó1cs S IDATZX`͛k-0yzYYA<Yr{FSW.=u Mvv6C xcN2YR{45%_*+gW&}` sf Y ǎ5S&O Mo)BA\)0揹6|"{g]nvv6Riǧ6U¥K7.]lWrr/7mu<=~YWטs>'lBnn۟Y3O@>Wj4?~rɱ]v'JJn%7oV޼YkѕM_Bv A!>p#PC#"搟N.rusxȿX[[޼yw}^|lWo]zف/rrr(-ؿ/C>\~+:#Fgfώ~P#rqĜSLoЋh>]?%bu,"^rXW׸wZ]?l n- ( (kL[7r(O|~o]'j>{ٹ캓'_, dfRW6u̹T`HڹRYƍHY;woWJo)BJ wztZ K~Ӆ@ƟNRi6EQE R&[g޾r+~}kcǎڵE]vGƎ K ^P5cƄW_ իP,n,g4RHLJo)BJ >sd,x뭩x{Ŏk[߳ewϞ8mZRgMFJ._.ssg쳃9od~|$Zʘ~Tfg_PcL.jSU^?,.ΪC47W8֭ʢ’I!‚ߛ)szӳq?̰n9v,ڶ6I{ᱴ|m]zڭ/>6`@_vzH\XX^YXS؜pBQN*:u̥Ћ6IK(;ؘ]:d>;7{o<Ԓqv۶">T}KWwgd,){)ew~tlwW._MoYKx#…_ϿHJLϼM˗Ke'+3c/z]Oզ){%9FJ&;y"Y|32ή_t*\XXrrƍrC'켪o)Bp_N_ٷϊe yHk6g'M1&N|!`zH)5R8d?gJIIy׹ HqA*#ڂW:(R}Tl"+nW߈!HUx%B q88drW*hKKJu§M- ڥKV\s[YY 4裏>uNT!x9޺uk߼*W*h*0qZMQuϨVGeȑ6mx'O;а|r]wͨ8BH.\|9uT771c)ZA[WdvmϿo[{]ڢ>6`ka??sĒ4vTz#g]\~,X kŎC%ɬ"UN7?\gL_|s:e'm=g}it˹-GG6hwPZiBձ!oÀ6Z|tgR]7.9D.Roƹ[vJ>6q_Yo2@ ZZZϟاO-[@}}ܹs]]]?sΞ=;o<PسgL6;::6l0`+++KgΜ}imme!~~6 z&D"󳵵󋍍moa2=%''999+W655fm}c vӃǥe澽] Ҷ;;:c$+VO}e,IGO%%MEԴ,ֺ>N-=:(Yr7 Ǚ3Dž~tjzg<Gl,lnEcGv;ۧퟖy;UN&1oADҔ)_ COz0_ֵzgT¬~'f}n*0m!}AΜ9t#Gs%w7osV#1c‚ >RRw ǍWPPiӦO>3o]tѣۥ%$${ή]HI[[[pppnn. N<) 􄢨s۷ .\FM8[[$jmYK{ Y\'p^ƙOWC& qv&&Ar7Ѱ\b]r6n`e-Mb `Y փfϴҴ&qݦBa=$-:r_<փ權[sS5zXqCR'K+M NVm1q\+))ijjёU2n8zɓ'gdd466J$oFj3--ݻc)##[ZZHX,۷/Oz}yy9]NNN =z=~ԈDѣGU5v)KN2:}25Щr!φZ8le=vYX~A!#'.dxl1]A!}fWoϣ&/wuŔSˆ#`=,)w)Y7V&]r6(V/n 荣ÿV{W eueu?h@?2f\A^r6}!To'/ oNN!Zg\#7"wjkf+ƮH O-YT"#S QQQC !ܛI_||24صkWXXH$7o,!Ccǎ3<óÇsɝmPc vg]y"j=U&5457=~sF޲ռ{ikH=qޣVM~)'&ɿc.sFOS^~RBï^ҨT5uDfF~w{:ڊLGB@ĉ;9-;N.^k?&_Iz\%ֈjyzz@LLL}}}cccjjEkj>>> ޯ_?Phcc3|Kx4^FB,X'lڴI}X!UTT tǧ` ߕ7A)SsQPA!>kk렠 w :Z]5IՏs_E3|:-nf͚5rWY~=.]ڷo/rƍvOOI&}.u)8#iƳu5 AM;qͼVxu>/Dj UGXBʼiև94mzK5*[)QrO g%PPM>YT8C̈́Uy%VQ7ϫ--+j_> N?}UeiL. ,A'pDx-tgixM߹bNѻˁl(Ϧa}&s6»ׄ_GVoOa6u6· cIYEIY}z?mĚMl}c眿tϪKp6cZb&s{l*˼Շ94F`tϪ*񄡷НQ v.6݈`Dy6g°$1fki. =۵ۈd6~7MSyEeZ[m/Vl(Mڸ+񄡷НQ X_Gy6Ս<˳]YHDO.KD# Tm5|B]<ܵOx _Ex7pޞcc;{k+g8Q0 3ݕ)GHܼ/#3Оխ\lH`*mSth9ù}o箬jw7x`t"Jlm]u{Dc%H.\ID-""aazpYCI4) X$q^ae}ֶ݂::_p>O R0'ˣO;&U4/&o-:95rEͻ_MvRx #q^1CC3Wp-ZoAb~m~xDE 8X`~9̵n LxҮSK*R?_CN04I7-p5Me@D s"$ bH"$ bH"$ bH"$ bH"$ bH"$ bH"$ bH"$ b-9Gr׉+r-τ{mm}p?KZ lhhpvrNSM4IH0r(28pH܆h]TW|qLt nHlmmbe2[Emm I nLt]]ZL&8v,!8ݽ۵UkSƍ6G8"$f H&{Z&{N>+$B8:N=I=mxD Ịgpf3oHb$f3ypԆ$IP@.@|lFG+fg(!w H$ GF;QBszgܱR իW; Щɺ0ۮchGwٷoe6.+˕k(@'>˿MHCox#O_?DB3fFؕkr}nSOv\iJMMݴW-ussׯO_?93^z>%EGsݜ{gݗO^^޻kGN! ީ`$KW.߫#!AvxhKUum;y ?Qa0 oǎ]S =ҹʄ[7<զˈ' :|ڵbBա_?IRKjqΜwG>=TpGG/E 46=F];U7Fu0'ޒܙs.IEFƍGNQ]-S_Vq"beD5sυG8;۟=s{to. /_c<"։]觻,nW\5eADӱ z}3kjޕGD};5 $ޒ=zTTB"Zֆ]{]<={>ձgώS b5zLo2^:q^źWƩje{`C0[c'?ܼQOCCõ]OD>]bGt̙㠠DTZZED^^}=; aH"!X!h9z"=Ȩ.2;[Զ];Ȩ!b;شjз?iP3 Sљӷ!ldׯ+juU w=F B #q+}JNmCW2ؾ[z^e c};^ݿ78O E nG9Lw)ʧB}[vj11 $0݌V-(޽{'M~6w,-PK>Qgzrr0' bH"$ bxc pH@Ĭb$ƙ 4V^^C>VĻv:isG J?!L8!8!8!8!8!8!8!8YEQ:83kf9]p{._mP{j9.ˁ8Э[E.\%߸qWШUd 1FVDנ21"ɤuuʃYx{J3`i8D#qkTgdwя?)9 ~kdguj3źڹCZȑ#GH$\]GO){nYj#O Dz BQfhTiqvv$St[=ƍַ_N# [YDEDkLMRM[ɦ̓m}|o.زcWWaO>xpRtx׮_qժmSqK*23Off˄Iܪ<8qL&}p"z6&b'{yapnsk+_^A<1\r3q ˗n|v<[į+cyx}{J>^6qK7v8"zGGvw}H~o9,dsV`񆆆92OCv?AB38͟rXX}X`v^>'twz >a\BB>\22%"/Dtݻ1aDDU?M/@Hz1 vcGG㯼2F"4eat x "㏒었_/$y ""!{Jh!?|~yvz7]kf$nNTTTBDJmQQQɩSޔjդym"H$ 0 9UYa4)DԳg]+.^~\܉_}v~VchfH֋}Kg)dZ_`kF9{J0ӗۓ}Z^^y^)rյ6opгgLĭTu_%ɞ+3$?IV֯Տέ["G:Սjo5;N\SS{ԥ$".u *..H{&aZc0~88{Jkkuu;wHɜ3{}%%**2QS\0R*u}nxyӧɓʝ0a8 vwjo@y,zzO:}vT֥FEr}h;SǍ֮}[VF?gFj`?dHϿxI|ŋgt-zy>}+y^Zق~RϋK_(ooOͥgπw}5>~G`.-j޽&Mj(~_%IcM8h+$ D)"UQp@D01$q$I?.$*4=J}{74>0ijkl/,8ߧ~aAsGcA$q1aHļ;bbs稡,1O@8ۀ$=i"s CƍǏwqq={G:(+V999999\oVWWnnnމDT^^/{xx|èǏO6cǎ2m۶111;U}Z__'w}澳ťw|Juý:tw;v숎nݺ}@@… +++y6_T&&&):::%%_ j Ӣٳǀc,--رYzUO\.aÆ) 2aT[oWsdjԞN>]m{x4v"[R[˜;<CLָ툈厫aԩBBB*++UO "&&FtŠ e8_0ѿ.\HD;w>zhUUՑ#G:ujY|9nz˖-IIIDa9srΜ9D2x`n#GLKKP(}ߟ7%%cǎ%!Cx$qhq'''"ZdIaaB(++KIIafӦMDwޒGߟ-ZĮz_j$&&޹sG.?^sS ,u [8ѿă~Z;WKhh(mݺUu͛7Y}eimp1cƴo^*}!,$\~ZjCֵ'xx0M##"3͛7pHDYYY]FDݺuc{ED7n$.l5$nD2㧚Is  щ'4?{|Z`SR %X>+J±o̝;733ѣf0۷n۶->>[d___"JHH(//HNN5kVSitR*[`<**jǎJd߾}D$ˉ}_zر7oyfMMMmm+W6mBӦM#/ݻyyyMD]+S\ ƚL022RdFGGֲ}4O/KBB^yU4 dz8]ёH|ca!df͚.;w.J$W(Ç׵^{M. ,u5r0'nD2gXgcǎuvvnӦ̙3>|v˗:888::X{0OJrʕݺuҥҥKR][0Z801D33 ;c LaRu8|p\\L&stt ?}4T$W(+Wd/*gghٳgwY&JAFCy6xۭfʳY8-q@s"8< BhN8<لcˇ$@y6 $ZXlI lI\;!UVSkT6j|/kHw4Q.skNi6'?vϞ*V'Sk55ڄT25 ˳Lcz jؽ+ƸX &B_7[Uy6!%ӌrVUU5 $nD2$_J`55ڄT2F%_MH4\$~鎢-_f +ub% mB*~$βlBJ")//74|xX &BcIYIy6!r6Jh(jLQM̮ŗg/KXQ7.STSkl/kK%ƒ{ >&M|cрjjBZV25}Yay6i&H, -FWͮ]VWo:tw!3w(& 4K \D *g'<Ay6 ( N1 M @sHPX [ I@ĐD I\ Rnݺp///;;:>|X=BbŊ0'''''+W~z.܌fx2:*KTk*3Ea׬բz<\ryTTÆ S(QkB˨f *0ؽH\ݻwђ%K  EYYYJJڋСCj\֮].ZfMVVV֭lR\\\\\zȑ5k֨n$//BO~'-rFFf}k3s%fea-[#+υY6&o޼@u){x깄֭[Uڼy3nD_26Ta0x-mXel???#Fvx깰]RRb"rttT݈f= /߈$nTa-S(((NHHsqqIOO7ojz.4} *yYQWY*FDD,X 99̙3DvS@":xjJ1<,T*U˴k׎n޼ɵ;vLfA/TgEI\;v*ʒ}\.ԩSh۷o/-----ݶm[||<'%''L=ݹs*557Y }@9Ʀ5k,եZWaH-DGGla^{M__FmO*7oBesϢ3,Θ1_&yxxDDDlذATKt˗/g8::X60LEEٳ;w,T( 2Z[l dK.Mn9zRVه I¡Ae 9qC1$qC1$qC1$qC1$qC1$qC1$qC1$qC1$qC1$qC1$qC1$q; Hř;I0c0œsG nkIeÜ8!8!vl핲IENDB`python-oerplib-0.8.4/doc/source/_static/rel_res_partner_v4.png000066400000000000000000001120621245703354300245060ustar00rootroot00000000000000PNG  IHDRxʋ\bKGD IDATxyTWHMP@D\X7}UVm_JE{u"*( (cB@x<ɛ7w7<ABH:Bb B 38B)*!'s̑U!ԝ6n)($F̙3EEE !Ǚ3g e0yGU&2 !GײA:x388Bۘuҁd"pd:x`G!E!qp$sW=-v:+"yxڏߺŬ{yŌ&6xeojV_ߜ,.ѣf:̎Ryʊzr{^c 0FSS1|AdC~UQ嬛I2m'O/{ss#?n|<Ɏ'}Ɏr h{'%j gxg2 =ƎvJ7neKKsw ml'KTG wVeEC *ml.CagGGFv[_}0b+npyye{vx l>p?/fnN鉿R5NLʭkkNJ ? }C.*(?70*)_9KO- O'A1&:pH>ΖBvvѡ uɓS &&V+X:`O(;%Gf7LLt)О6m74VQQ6DXcsuuVzU[[]KKmpw%buM0N;{HIy s@C-&So_׎/402q/Xhtp4_Ѓ^<^GGCGGky#@tzzAIIV]]sb"'3a\aLq;ʤH):>O7SO.Gg@Bsޚ?x`rQJSO/{QB*|U"FcЮ_{8ɩw<CE9( &&6bf{nbKuc>puvp4F%'ZHĥ>bЯ G=fpErxJƽc|KWOKW~2% -rwNMMWOus0UUG_:yjL6=fvuucJ򓨋E'OٜAZ&|rv^?˾W/6lV)dy= hmɏmMC?5/<£_^@[yW9C^R ǠU`0N:uJ BFUHBo=p;99%;B.I91J4TؓX3w\Y")GH:֬hpNT*76qu;GQ!6nCl%ϟW$$8ϋNkkk-077̳>P@ u1ɓlvaߝ \n[dDܕe..Ӧ6|x/#B2?z׮QQ .m[a} Ļ:$&bwbcSe'NDfwG.E%RK++kQqH{괴l9~'LwngUPmQ9ü! _SS]@ 9a>6qxKV%bJM515\vNjOvc.\9[d)S ;+N8ޣ]NjkkFyGFDG'I^xx Na"7nCc73$uxU>cu՜f5$ħ1dg?=y+7c P7>xEDD?*}zwߝ`0$mQVs&n\gE>p;@@Vϖ~ٷog5b`h'N{{w,*..wСNԯ+X60 Eِ /pbGee=y٭[Yiv:׫$"d&rTUv먼S2zt#gd)P*ٜo vs:گ=/#(poLHHg0C> #!!\@nf[tT)?ijjɸx@%t[{׉攔!y5Ì]PZZnm^p6Xj..>6Hr{WSaR_Šx΃WSfIAAQ~tT) N&ϟ{7ݼy,?~`G1M!mߝ0ѫ#$[_Ot:.pc{3:*/Xﭫ>jԐ{G] >ϖcd-]:Oټ֯秝 715d2U,ΛtPL'qb!`_8uܹse r{@y/S\(=$=QRT)r!E}pRTr ?F놓SqBbqp5!QBBI!'%%-^ʊbO81&& >|Odd$P' z ͍` g0nnnTɱc||| 7mT[[+!W\ibb;cƌj޾}[hhhkk+'4+nݺV]]a4c :uu2\f'Nw?駟`ԩN2vID[[… W\[[AH9g̙3'Uee%A|0vX6- ě(a\AtRå∑2_tttMM .**ڹs'Kw:::aaaӧOoi_+&b***Ȓrbؿ?XZZ766&''͛;{8)$qF>}`ӦMAC<!!!@yr T֑/^QFI ةS3}/A.UUUUUU*$%+r!OjʘP^^Nߤ 455EYx<z TMMMT 5ђ_~DĪUrrr׽^:ۛ2eqJJʳgRSSɥArmmmmmmĿMA.!iz+?$`fpLj䧤`GGGuv} nN=Gpjjj ,cǎ;v .\F.uvv4׌rKLoBxGGGt}/¿x= $W,?իtuuSTTܜB}iffǏn"ܹs ==Zt!077߿~~~cccsssNNξ}z$MQ^^^^^~axL+oBOC`Xo/rHCBBx$Wf7>r Y>h E֭OB=g̘A?W=" DTi(4WP@/fp ࠮޷ooza١>>>555WaXgd;G;{lKKKqƌNBJ8R2w!ɭHa!mmmnݺ>9ȑ#{p䬬J066vuuTUU%%/⋨XѣG{zz655EEE檨 >|ܸqTg/~!ׯy߿^ZZڪ?p@ooo: } P(o&111==pC y;w{=rHb>ZA_dgg755}7x|@ r믧OR%AAAѣyJKKKKK%m?߅诏V@IJ$>$+rq5!5)gD_dPSqܮېBѕ7pwwg2l6{֭*{,*****377ΝyzzJ_ r^> RRRTx޽ӧOS%AAAl(Ç$$$$$$%nnn a# EW4N$પ/NMMȨTQQ&{磻dffVVV2 ###Yf]tѣG\.o߾~~~M D!ע $xYiz|7Z|dGNPOx\zCO<;UU01#[z8z@ibr+c%YGP A|{--&A[=uG"0#Ŧ..@++K Խ r[nUWWwrr"oGIJJZx2668qbLLLJ,YoaaUUU-244411ٲe Oرc>>>6m֍ᄅ >\GGGKK'22T٦uՋ~,}8uId=K.9<(HB"$->>..n0uT`&iͣfjj OnnnĎD"6Zʔ)АHDB< &g >jBIk: y },$D"$-e",WӦM&I,޹u@Y-[fhh=mڴgϞK%IukF =H a\XL;;͟-gϊdBH~a\ijӟĤD]jjf4{wjkkKܶ\I~ ''y|FA<Әkkkor"#$,ouq6myjXO vرoQq#훯>x(HChoTbT$"{j5m/uww CJ1׮k^'xΙ3Tp#cQ/WUZX.\8o(m")5#B!bwbcSՊ_8-ښ?߾MAm[ߡqRT"&>N|Ntωz ~D9ѣk~p΃[5jhm|q9TӧOJT^/Jjjuμ \~ nqTTbllẵ uP{Ů;vq9g^};[SS}Yc8<k`ɒSx566gg?=w6&x면[Y_@mm}ZZ6:a( zTbT5u.j5կo޳ğG# pXkNNܿvO|3cϞ=:}:f?j;a`džwyޙW .KP8.uVMB_g߬tvY'XLgguvֿJޘ@vӓ'/_rk?nW{ZYr-;b( zTbT5oޤ__&NX1k=O@f {k-<@@w5kS(?[5X[[h]{ZH.%EH(XYY_Ξy#l566s@AAPZZC%m͟-.۷F  o7*fFLLo@LL $ =ѣlUׯ}*x< Kˍ^8rÅ/J酮csshll&v%EH0r 0pa˲e@&s8\u2 `0dIK ..OlYV֓Ǐݺv+k_}tX1=~_Rqp9emm>ߛm;,̘>Yŋ --VNaaWoI='ΟVQ^y -lm{]'ʪSRlʇukI)/rjjn򲓫WS^%PFQ:[?~ƸCCCݸ-ƏݻO\|>::iϞ"Qt%EH˯ߟs%9>>=qQO;?Μ5>553=;}}t潛7%GӯiHffn^߿e~KцZ$D٪k?/"Y2w7xxFG'F.e܉))33smȀٳ'|g_R .LL fK;O_ΨQC".~WSZUUwoS\&M LjH3g\I.,,UQQd7o$}KeFFSx/~w(_x1GUښ澾# P|FY$:SU͛7`DFĽzUijj;r<|]B5uVjjfeEMG,󧝟M-,,e0VV&L=g"5X$/)Baпg0N;w Rn|3?$?t5u89"Բ]vo6^|E`ŧN;Mu;}ɖIw4W{FNԢS/d=lTK 9gC.-t :Ou]ql }^tAgؽ545m믬ߕKM8mhs'27nMjV{u_gq1uuLvST.6Ng<~4Sۦ{ggf]`0%K[XX@UUբE MLLlBM_RRŋX,ĉcbbrnjkkt?M ¢UKKvZ8o~fo߾MKKKKK-44r1r}ӦMbrK $3ԉ##crosȽ@K˗=5Lq%,͍&b'#ol`sO:ZԆgO쏰ϧkmL>z^UzdbڍZUck /\dj˳бkֶ)'<)'.m題C grjm[f99` ֩ږE.㉓~,HȻΘ1T mۖF=ztMx*9i+,]С7\x9s`ȑ;vؽ{w``;}~ޑ#GȒ'&$$9v+WX,Q"!bѢEǏ999Y___9~  :u$=_=_gwoe74&8]=߯B+Z۵٭/K+~9|^gf O Fs%.,<MPQUrCz_o8z뺆s4c;(W!jKKC?l AHAv+Ӝ\l}}}|088 :TVVVVVvA=== 1exxxyyycccrr;l޼Y\'/]KJ"&9.'%ߛkFɕ|\xjI|o΢(s2a}I %zfT.AOZVk˖LaBuTCKw)mL^ Un.:|; "'Zzy3&ʆw<|"^xB>-..[bffFm;uTsss&o444xꌉ5g@W&7|p&ڵ x"ζɋR>L/clƈHz<|13m3w377[R^^N>=uԂ <%GӚ-ӧYd͛7󤔔)STUU}|}Qnn.L2^HvsrrD$++ Ǝ %|PPPʢ˯Eپ/ ._dZcS4y9Ag&VTC]z~ ORN91N#Ms{;j05 ed͓ⴛ7^]λ|9+O1/ߒIs_w]ZSe秧g> 9HfF`0 '|B~H:r׮]?.]D/x"_0 --%Oۛؔt+sm0}wt/EwUq򋗯-֧/qZ c6r痗u/[-/Sg#Xwy]zv}Ww]mx:V)o,? 4sx?#YӧOXBf͚T]]]RRbjjJ$ ]d Y~ݻnNv7lpъ#GlܸZ$ {ڀ455߿KyfDՄ"&SՄa|rN} huM|wJwC?sWo+AI~5ZZ] ѯ&O ӫ ^be_xInڴ^û{Q&kرcE[nݲe |@KK˄ yr劚vGjB9 u45{^>J^ v6R]ekmy=?|$b!_iz2f1jN6k̼oYl)栿㡓yhij7wUY,V񡮽\5,myuq{ ,m#MC9Ϯ-eiʲ! X*,-/+;yǁcƍ}o<(|{Y۪_z588UCCCSSuTѮ]bccgϞmiib4557nܘ!@BZ\9"K>|?ޱccCifY;[! ** .]*X\O[ zOQGɓ'b{umk^7NH`/k]]$@PRӱ!)3] VS:ԭut -_qT  ]k[OAӧ:4tuAX"'V[D!$f{zN쵩:$fp 80mfqVfr9hk%@ 0T9 IDAT6.wRFf.F\^B YdCM[KE忓ԗ:i2AQ!K\>g͈d 5mmPyrJ=>4kn 3'^ͲɌvZ[sbYDfpa&Nu HfԵRˍߵ'y߁))RV9MWEʋR*CEmMd']Kľ^émmSU{+NafXGHuIɝ'gapjZdTW׳07_}9Jq!e(t}ߑu HԴ pvE7ozR_/븐{JMks7}z,[2mXUAAcU!/̭qhy )KݓM55k?u Hlm Zǽ}ifp:sAhȼ9/7:T0KGAJˌ׬u HNL%E2IfpH9xc:Bd @R .ͯ_=Y鏢/eR^6}BYK'o:<0KGM 22u H}xVDA3J>ONXA `P숋#$ s/}KKdRS^*{Oց %\RN`g3X z2ʉ*@R4*)ʮx7x Y"epcs]uV!ބɺgau0BInm=l.&Xz:%hTHAS*2"QyyK: 00~ Tnl3.;"Hpk*@;^lެUy6ٱCC܆}^۰k?ҧ1jfrX`}iaD?@NKWV,Q:ը]V|._o%&&2 l|gθ?NKtQ'샋;vJߔ⮅UkاOMQQPr@?Uk*~b7#A1~੡ ʨjR5h|ڊȿV~KTߵ\zpKO|_^Gɉ}܇>xx`;k!;jllq'ݓWAE?|"o{|Syo®=c ma%'R3 n- x%%FYq ңyW^BCo++^ppޓqq?f&}`74+pZZbC`6wڨĵ/`ơ9e@ 0Id;ۜ\ȧ9Mr 0?|khcR%!^|Hߪ"Y 0s x,xgγމ~6lL9pN]n[}Cߥ"[ fvEjiVD$`!L=m}̎#y58|kDyA:ը^"F|" UpT FZyJ}tj}i{4:iIV6;5Ab씮28eGY8*5b;?隙S)2SSXDuweO`Do-<}G6?˔B)PK/lf&~鳮8zj&쀏)v{B| .ڗ/dH.m%b>m0c`74|c~M-CNLjT!O6.V/iyy~n&HG[Ǻnӷ䴴4VU|"zk!;.m%`joAřYauuk pt+p\LK0u 0g05{3G2C2/q8 y |B-;. c;[wt*LO?^rw7xml Uݼ_uϭDi-|[w,&Idz R4KT0I_|7~Ivd#ggҨfp1ռ| VS OZş]\Nk+&+DBH>#C4Q3R*xii)}NѝJN=αj83gc:FX}"8_~}Rz Zk*Sl$vCC^b j. >gCEECEŭ#GoZ$Y3{9 h~]RcjR^J>ՄbJ?qT'ßBΘyoZ>Gu/YvQz 3y nH ߀T"xK߼V|0_;.PYk1or[[ҾsFZ:j񺲲^Rd]Pa2w6y5_zd3+_ n3wk- &칚jj5ט+/1%%)^%͚)ʫ ?KRb 2R"!ԴdH7ɺu!^b=l ) N:]ė4*E`\YH<&` 2*bzjj&ׯJ, 'I&ŤcjPQ!4"21tZNiT)kYUU9?RhD\p^j00fp12Qӧ&: 0IwoMeRATP@ŗȔP2x?_iIt"k*rvnҡU&j d*1nٷ>lʖc4hx @[njLN>i˩ߖ9br<g`M2xU9w5X~)QnZzHT]?Zڞv-mLFEQ]IQ%?_'MDLii 07x@M `=gMM+_G@ܠEsṉ#k:U0WE˗*f+VnRӁ@ c+gN#1SVΝ VfG|?@AWw=R#{b<ů]Wϱ{ M 2toX @ *3FB-$*(H 3iT2xKCHfyYY M_:XӁ@- ^ z̝=bc@G0ȏ?uYj!d1wG:zw5h^ܚuEyy=t P;!WwP+d%#uKBL pPdgƹX3Ғ(c_[g7yLHHj,A/ul#};@ӱ&Y%&yt uY4ZT2OKK۵kẄ́Rk-3{ y؄#׫ëV%&&&$$h: u\ճmCU, uº ։,k: 2RwTw(ծP+<oPj^cvFZy=Is械WD$s4it{BK{0ivAώwo ltttmF K^\v{1̟HOlKlkiMid Bxqt` Qy _+K= U ٺqć9͞ҰO~%^w\޸h8 rfϊx>o}D~I3zv=;3h\#Lǵ8[8{mX&U)`tШ3g67~ <;Ѥ6;b|Hj}t"j:l amv7U-!\y0DoR7o9w**##"-oq_|G3)M`/'޾g~c"-\5i~(5>]U1VSMA.Mq8 y"YjdTqvNiaQvŸa;.H}BD>Z|5izVO;k\ufl8f|CDE;:~w-x00Je }7qV*q'z]qywDdٺ%cTyjYxQS}@ `_8:QY>}@WvGN%|<(3[^&J^g&S"j:t@\ ]CqVJz҈HhfU) |[↫RwH ;>)-*31(?8+UZTͻU) 'ykh/VԆ㓲㓈ȴY)zþMDo{Dtu"M v}0$&D UMikdcoyȀ("qfƍzz6%"r2ȶIva (=*ޤ=!s׳Է4o2_ǵC-zADM+?Rc6'6צ-ַo'f_Qs#H?gJ"vkop'D:%Ԍ'}2U% ߜQꮃOݺ{5ў=(ƻoJe%%9I>4e>FOM &)H@f;2{{ʊE_>4q<;_ݽ՛tR k, uo a?=~t6VӦoPayuO?{FF',9mp럔%!_nݜ^~Þ=\^四Q^^ 7t{rܣRN^{r60餕okKclrIdD̵kwMMNӚ㮮\ʕۑ1Çt3[v}pqqWv>fpRRN fS!:KD0^FSJ(sww^h9cdֶ>ݼyǎ#Gƽ~ǧ_,y'/ 쒓'ϋť:yXZq9QС;9jzNu؅ 7bƏ`Gk~עMK=wJ߾~ZY={moo.~=c$:Z9jLDYÇ͑TPףG={Ngl^BX]=yID~6D!KȴaȕV8`pW`g„kDĎ{O)bRv}JK֯MDv' ;x̳gŢqZڳQS&/"^>D{-)s+囯g{stlHD؞SPPp-d ;M%&&!++ױH{ɱcǎS(]$*=ֶmvի7O%liN֟>z"~H\^^~^^~t+KVq)Pcp'b^{Q>]=޳xh/]g˗mY;lX˷]a 򕾧"ׯ' }NN>|BD͚5vum*YU!plW:b_u]+#qm<-XdE IDATIR|p^/ܳMW( -5k#m#tis`R^mЦCVzu&"==ݐYv9LGGЏz]u7om322ճo~\2իW̙Ӯ];SSSgg?<--Mq*utͳyJ?8j'ZjOS1kAgeou{Z;~iӦҥK\͛:99Μ9Sӡut veAAA666:uJLLhPpaaoW8uÑS>y.ip֤١nu_l;{+To)},AݻWp-pdɖ-[F-L'{0`tqm~˱sK.˷)~ӧwAz&L7m.)..8sLnݺ=zT(raQFm۶MwwSN&1 ܹ6r9{zZvc]H8`o&dLܷ-Vtڽw+m5`љ/ǽ 6%$$o322.lr~0PϘk"23{{-oߞ/^̾]bDxM{{{"Znr7J/\~=c$:u"3gHwBD...;+IS+>[I^w>=NND{=+񝻎n%?"JJM.[jM5 7LUg$-E2 l1PA&׎DZKsl޵iW[x{G/I&o޼155-%J]K~q033&M1B͡C>裂3f,Z\)09DVR^;M/u.5m܀>%؈Mr鰅sS":;5[ca9GMۑTI؏c) ;iSeܺ9_ pr"o8V4=;28ڤkDTCZv ŋ]]](֑J o~WvW_?6m4pS._|Dta$͛s ݝ.\ wA~gO]sID?"F6D|ݮܼy>;[NMٗ}#OE%=qYgADϖOw/w2hw_(D4D4+fxIqiqArhڑicEqtzW2qVA֮ۻH,yvDI&rss>}jcc#=I:vXXz/_p362 KzzFn.M0[G94I@=K#Bt2620uUJ)Ba < M&M 'MYL&BzF&[¸m;Y: u>}YgU*1k֬Pf͚oV"233ϕرc/044422Xd$}sbŊǏ7jH(Ϛ5UNӳ1x-x(6nܸ7~K.t,r*kxѣG"qi:OG?T _If>j28ߟ^;}ZA;epTYUdq($yw" Iza^Ӿӥz|}JI.p&SM^IN)^R թ|v\fQxUUִ>ޖzwIOM8Wk-JrBuQTNC[[2w-ѡ W؈bb9u^UPZCr%9ŪpU\uSLvjckE$ꝑSGѼEnwEsl/khHnF.)84*WS QjUW;T.hAZC]wmlp'S. [+&6L5bPG%GKlˮQ6O}Z޸2wៗo$/sǏ_X["Qy{ַ272mQTLZ cpjp+=!"Ejs28@u`"*--+D,h@K 0 #.-խbMܼwjwX])|˺>[T,b #Ѱ~y$"Q'#:Pwtcg-,{ f\'e_hCkן&={`co'~(yzz:ݻ>z q9X޴i:<8_!28_!28_!28_!28_!28_!28_!28_!*<@MMXʣ ۫7mŒ}fɗ 54#݇׎vHajP F px jk37צ$J,. _28h=tue fNM5!u---Y3|PSm:zޙIatTH| 1l`wyutt:wxCdpЌA}/>5 ?!fXYLAΚ wAc)+#"==~-M4 魯/$Ҳ?t8 cldN"}^:j:EIOOtP! [Q4 T[QلCtP1YnϮdi:Ppppdd9jr?SQ|# ^ =-l2QSMGՉ r?s͟'5WZf~iMG26j 0 Ց}ĕ6[>~]jPEࠍ4, @u8>\!18_a ^;2yS;~:q׷CzB^nNˏZX(""ͽ}ml̍Rg>}? CujI(矌;_d ]tڢE%gqqJ%'(/Ӊzi8;Ց#7%KVkSjk ۶shDNޓ,5UXX2]JZXmض⩃m^ڴih{߼)&JlͭѪ?_XAqIv-.*x!cTy*kflW$}V\\BD]|w܂;yZ,*iPp/G'>Aw%u،|ׯ 97{K{i:4G/MW4]IDtFRR2E"aNCy2qN.=-eF8v/>=tfJJ4q @YͶmq=M|}ڵ.^|_T"*MI\Ds^<}ML _K]{w}c["l®9zХÇ"F)R7~-$'͎DءcTy*0y&Qc+"jv"}Qڔ6m:7q{#wF77yP^{wulnr5Be⬔_xMDBmǨNU=$e!fo$ "r9HWWR?Hf6ζNN6oܪUýk#P;bn䗔\qqVmdTz*TeCP2x$#zVS?'"xE\\]Ke_7onGDٯ֌vJq­!P)++ 1Vc\UWHaP>uN׼P_WOOA _#9sLttt ۵w"y2vmng` l~/"v5rrQ`occ|܅c }J1Vo\UWHa ^ĝKnҤ[VI]\\\d&&g_TtEI9} ұOP>AU }zFOg%K8~cq K3Bnپ-][^3sòR&+խO/|p2),=ٻ _zpٛ$W$7 !ޡ8fE"N+>F8YP^t.\Z#0W| W$Tp}P @C tP!        N聮~c%cl,`Enwsko$ڣ%0z8֭DtootRtyM҆4c:䡃gH(+)>kIDATtk8mƉ? 0Ҙx"6}=_R"tP u]|WtۿTJJZ\UpE%KbsuĩSIyyNSEE+ǞXRRҾi쬥{i/PA Dp`{"Qm~FgbbDDeQNx_`aa֪ڵoid3DGDAA+Vl;ttW٢bc/rh̙Kg/߹pƟMkaDɒ'۷G'&^c&&FKn:|d/cc/^< NʽpPף7 Y:b[zlp[&U\?)KBhݺ&? [1ysǻw_lvφp:{xմ۴u34Կ?uEض;wnzOeuseee~~1o۲2&:\;9cMD3glndd٦;w>t03n,,L ݝa2DD;v9z47>>e P0>|J;y‘scU߳M .AwVC>]=޳xhx_O.?l1x~>ܵ}JѴ# t+o&t c޼M7nٟl^Q 3fml6VzzNN>+|"Z ?{{==] VYF|!`?"""Z } 6Ic 2"##5ȁY:)#< (|1xqRp/0J T;A ܪΎ 3)WC]Sc?/28?[7:|X 28_1 Ջ4$v קVh"74 @] .Çhjjjmm=y䂂"hɒ%ƞ%%o7??رaaaD3j(+++kk믿ZzuܹѣG7nX(֯_? &&Fz2fߖ.\mƚԴwXsbUmmm6l|LjoݺyΜ9yyy :T^,o?**$?1\/K_3PmZmΝ*cvvvƍ҇~(}|[n"m.4htwQzɖ-[$-ߡ@ طoLƍ*""BUnb$k7^"fhVr\eee#F(=//\$T^$WE1e5 U1Ts!M>}קNjҤŋrÆ D¶a{zz^v-//oʔ)Ddaa!sΒ;::˗"(==}ٲeDԡCI$oRSSOD]tQ|q2lccc"E"ы/Yn5j("""++ >>CD4w\vsPiKiXXXZZZQQQ||w%MRk\wL12x-%{U 7Jo~z6eo ی Klmm%=?~_~vvvzzo7444cbb$K5޽{qqq!!!G1c+------+++++(?9PZiuC\oNNNDtPJ7puu%~DԼyscCxbWWWĉuUc~诿(YQ۵ٳ###]FD BDt#Tڞr/7 6ϊbmW]yO26oޜqFKKK*t8;;_xѠAMU;KϢHL1];.__߿+--$33sɒ%Ddll0̆ nݺu>,(((**{ڵkͥOBW"233[rezzzQQQBBL[[["ڶm.T,+Z(Ym1w7/_\\̶Q66fO0FF@$%UgZ&MĮ6m9 ۋDݻW'|R.T,+Z( x;jaĤ^z'N|U^TTxbCCC###%KH~O3X, uqq100hُ֬?(~+`sas"G CK$$$?I(Z[[YF,K?~<88QFB}֬YW^eז? ۋDP211ɓ'7mT(V.ˊJ k3w|~@PG GU/m1u^ZcpxG=0sPc0+dpxqW28U28jZ*TY TYq)Fű(RH]WQŕ$"),,6mZ  PhMR[Q\ɔ[{5.MUQ)|TE2x`εUʹr^UQq,ƽ]ViSUָT>QFzuM!ڌcU1+.Fű(Bk\ wRWYRZ.={(\wL1ժI+(FŽ(Bk\ w[UGq|V-INNZ*>TDA1*EѸZcburXYY7JPUQM]W뫬)|V)>V~Cz988QHHHNN˗/###'MT>6~G'''[ǧn4nŵv>zh"5kɓDPE5h*~` EѸ,p)W.mypm1f͚%S-<<ݮXB\U\6Ӊat(j  W>㸹. TYw@5m E+U @Z• ]Ԕkj%dpB+dp9bUmmm6l|q6kkD%Kxzz{zzJ?_Pnm-D@$j}ɝ!^[V7kjE)_7K\***+ynD"t'2*)Z_2PAWTYc\1;v ~!==]$x"**J7رc2VXZ|3g,--7lؐnnn~ԩ˗Kw(E>(\9&&|/SQP).g^@D嫬1 h"\ \ Pm ޢE " 9sݻ'=o jkyxxƍZ~=yzzJwR Z_օ:1vժUYsttns~b埕%gff&IwRظR\5 k7̢s޽`SS#G̘1CZUKmO*,,zա .Kqc===ٳgGFF^vdn!PՕ>,_. *ՠA"zdٳgt B5ru(sutXk."***#h̙[l޴iӬY$!H)ղeKvwiii_>tЄ t B5v5?qST$S4iZ|}}_\\a>? ŵd$unR`\iSue;99 Bkkk5kֈbvm -^-edddIa^|9yM B6"|/V6lhѢP(tpp%%v Rp+bL5ɷN U@TYf+dpB+dpB+dpB+dpB+dpB+dpB+dpB+dpB+dpB+dpB+dpt5aȐ!4WǼ5'`F1*jC+dpB-]rIENDB`python-oerplib-0.8.4/doc/source/_static/rel_res_partner_v5.png000066400000000000000000001040621245703354300245100ustar00rootroot00000000000000PNG  IHDRj-X bKGD IDATxwXY'z&(((bbC{] PWյw7v*XATP&k?_뽒33<$fx̜x<Iv@'F>0e;̙3׮]c; *glli&0T/_@ a; Oei޼ś^c;>Ii uFpdC`; T.O }`׻C-xl'FҹYwoaDCFY쭛/rrHJJʶG kcmmUEEܨ_޼yXF+~=9feF22R_'?ؤa-[l&S8W _7kQAɜYnBMM19黇GDzG@JJWo3kc]]u)'::38*#xi먎kٲeO/{wù\)X6mFD? ~^TT\Z׶dCzzo+ Br7m@mw޼Q-,-K>.OyM 4ǎ240K!]4#מݿ=W^^q}ZJӦZ[oƤjMil`qR K _*ߩјV4D5(Uƻwy ;~MvMؙ'},Qa}\ӘܦM5;Xݩ҂ 55ŔcccRjNC榦? }GDm6=֪eK=KDv E+$".Wa@["}e.O½OPk ҃[ѡC<-/z="OdQs[X+)ɅG٭ [0mׄ3goG;0u۸1cFqJtЗq99Y܃H@a4jemLD= OK*,,oO:%Bb`m6!"732 ,sDfdfd>xy|0 OjګW 11* z hӷg~fֈxYmcDeq[[LD'N<5ycuk^%;=V޽_+HKK:wi6kv/NEedˎS"SR2H^^Vb4ʿHljS"$LD6T#З…/_paɮݺb|)..O=y!}Z#;;O-8D$%%\RD.56612nFukq$8=C~o^fq"1N H nyD0 v}ss SS3=vJB4CCD'Oqqq/Q_GZAA,]tDԢ.%'g ]"XYY#xᐔTqq佅0D?3hٮ}"tJlKNU36 46i(×/m, -6\vx&"+ED| Gw3{O3'݂^<ѳe UT勋xI_} |GD6nۢ^J99_> i;tl*--xdۣRZZvP[7_2Lι0PWW,Ae=̭l6T+.$y{bة҂/;9[3֪eKO}۵oUEaҪyÆjģ/_Gx*,dtޗ/D[pkC8<,VU ??.{}}=ZOgԻwq#">mzR*:{n^a/< o> v}*1obwCw"gOb*rM-e "z R*`@h׾iMKbc|yB" OLL@%BUbN>}Zh()ɧf>{{ʺ("ө*6>w=U>IVT5OhthvU%44Wv' =6rqSXv.`#HA'F>0 O }`#HA'F>0 Op-RSSnժ۶mQ?l[5o[05ZqqqZ/tv8Ei© a XVڬ0!˾?~uؿVV]9ԇj#9$rPs` j4Q+fч lfPP~twq:=l;N~퟿EĽ{׮ollp:&](IlT*,=`jj8v}έ[!qRAA ^_]_Qq5;z̭7SSǏ0c*cHjOP .#"rmX'X[ǯwC[PXXbW^x"מmpm'nߺ/x~WpHDݘtyTjz$6.֭}(((\x˗߿% \]7nqq6o> ׳Wg*ϐH] 㿾u뾏Ovǵ`&//}ܮnoF9ssΜQ/{RP5kmN*1nwC-[mۂ?uM6VjvvW\)y`߾V(LUUe"JO|+ݷ5.0UYZYagXNN[{~!{v>u>g ^/_F(++Ο?ڦ->|go.^7A瓐Mmkc_O)@M{PSS3{$?![7h͚_zꬦ,''knnfDgq#Fn #577ްqAy]5fO}|;{K˗o?qr:N[GD<x<"9t'ch׾+_w0$.mV٠jc>|4tXo" ""|yŊD˿Ep8<q8~I^^=UT߶nm͛aa޾QGaw旒Z[UUUD<(UUe+?[x<ސ!FװLnn^~bPYdeecW!`'qg  t E9x菖-oD;:ږڠjjjzzHHKKnmܺ1%&&Lx a22\;;+W|]_+IsǯD4QU%>}-?,**.^ݨࠗ _A]3R' t -**>r2 ڇ~[̀ϟ ?߼?׍DԿ ~orRZAAWfh؈vr55;;7(-y[0WPRRZQQo~!T5oEWul[=+33^"oRF쬈hnwOw<س])0ԩC<Fq-]>[ŧ=G>}z3;:vgo>|s~ɀ݅.MhhdylJۿE C#Y3}SӦM\y4ΜyM~Isu%%W5_Pȿ:"\ޮ}ˑ#2 C .OP;hiivCyBn_*<[wiiF;wiݿW"r.t30>TK31󫲴Xbįj&b>͛ϞIJJURR00е:P;OD]E6݅r)WUcp87}޾IDҧNedCSv/ ./xp8 9E*0,\ \\\BBBbp!|O~ CiD :,f5q5 fA::ՀY)>aRg/-[ֱcGeee999ccydz /$'=ہo^mTTԾ},,,؊ ʀ',=NȸxyURVuRm󬬬7o-\@<>ٳgomiiVHP6>U2]@99yΫwuӨ s}"!ilW5 ;:mԐn8 M8qAǑMcY"sok\AxO7,yuzmuh͞}c?~.\5ju500p۶mK,lݽ{SΝ8z̙3Lr ~I^^^~Dի Hx<ĉD*177 \~}~'>Unٚʭ[$~Nq"S_Nz)7lZ9mN'/\<.~Nr5O6on;ñw>?pm³˯O cEDN]o7{*vG×?'4twL穞"i(hxL(oDD,xܕ*co/Ksl:ʷnMTd&{Iy#d^۹sDڄ S{_~n~ezzܹsh%pΝo߾'$$l߾mܸQNoo7n x*!&}vD*X;%5eHJ==$v+X(/AɇT6#m}-i].zղ;;o#ώKyǰ]PPmbb!>>> rwH^^B_HE_N:͛oYqqH^#Ç ;~p#G]v #$JĤbu`D2+m:ֹ &|!+/\ܼ|]~X8<2'-".BM7iܐ2~'&|&q)D42ޤൡ!eg0<:-ZM{T(LFtt ^"7?9䤎N -IJJ=qS)]+ghHDCuuu7oO=-_\'%((155uʕ3gID…ɥQ^? <Ɛ>U-{xΛ6L_O[^N6;'W{)#{ .y;;>DCA諶Mly5jPEۼq|>ܝ2999 ? An:/,ZI&YYY%$yy\q ݹsJbbb&MƏ/ϭ[F\@ʈHQɣp#"sv|JAH5oHDqϥӌ Lhiܔ])|R_QU((뒸JPSsRTަ[h k{ F ~}Aƕ۸X\yDTrU ` ¶͛7 Ip~w"SOK.wĉCfggϟ?_^&>SSS"}p͛7E 077'Ǐ>U <y(tDۊiю×3ҿg{jF ̜HDcY{G/9yc>wbiiѿw\!r%;ȄW8mKGLj3{-dnjyEy]3wWu&x,stlOڞ ;=0);қKD[(zanݚbV IDAT/Ξ=bU1,--ӧO湺N6ybOU-\ԩS'NXx`1|ȑÇT_./O-\wPp#zTxqCKB .lO_(g]+%eҩ_'89FN/\.w+ }}:۱4F{Û4`VZ 0 O }`#HA'F>0 O }`#HA'F>0 O }`#HA'F>0 O }`#HA'23_JMc;)lPEq oaN@M􉈈E 9u&܌ 3It` ۡ@MQӧw{~>-.YW5ۍ֨] e;:Ai\3Oi636Q؎ jz>EtWñ6tu-IIKt%}LJ 9u&ѯm9bPY%%ZOEa^ |}ֺ榁QQP/㋗k_?ˋvPJWVr$ܾ*"Ann.iD@}xl/o#K::=[l۶MNBBBD s)xū\ VC\XWoASjlǟCNkzk9r8 }-T ;;yUUܺ^rח~v7m15HXV#ҧ4M[vlamG g7q2R!\xCn04SAN{oW#G q]x}|wys)W1@NKX=lGЬ%T^<'=My.B9 Pl` |RSuksQ~>ATj} u߿\g_HIKW[Px<ޓgUV{i^lGP )}LJpS7c ۾MUWv}oy2 lGS<}{g͟JZnn۽JK.SV{@b0P[Tm>sΫk-Omzյ5ַs"}z8e,TDOwL+.ph~}x<^#.^kjw<`j*YyƊ94iE3NpΙ[Kf_`;"r٧أFĎ9ݨ[9vyyחؽz֌wB>E*9} yrt(W/뚷Ě.y~y]]<;cS~gҫd8@>~sp Y%97V(*'ݦxא;D\>Vrh?p$٧BsO8qq99'M}scSma;SQA٩30r'(/im:>z+W,k׮EE'Ny}+/*G8Y~yyWk53jdavDq+|dqQɱxzhj٥czhuŅg N'O8/zuW;@pܴ1+%m,FL{b;Q\:okVLt͐[#FG? \$A&lOo94h;*b 'gmEeʽ^jl);M ”sn>^v,?(SqQ.^ENd.,"||0NƳ 7İWS/CT՜92١lw֮uGWVg1v}z/]|iӷxÀTy '#QNF%#}}s_7idXU'SZ'ǻw+ہTV:6u;믿'cnZFngUlߴp2QdžuՄS~v٩[* v v r[yGh?zdqam,ww'gβ)@p24}׷p8UT'Ϛ%v,Ps)+}|tAn.aa3{浥sY *NFD 9 c>%=hFv/OkggJ-e* +Ã'MCpo sx]K oxW_(Uxs_/Sˬ'E{r}Vk-SZo§y2mJt̑#\Wj^u^~vŅnmNŲ596rLؚî]IOoaGlb,jZlVܽRE=qI7P><9(?gv*.UTڮE­t6=gj=ȏ+q4*eԮOvZZ&[ȏA\穒p2ɨ /~"սx<EA8R^ m{y媠P]_˧$p83^<F y d'@앦 O >vBx F$"wz┧n"뚷Zx_^UFvgVJv%$ Y"йYs!ri}W(*ӭHTuH 5+9yEKEUp_^=--#SZgwt mk%G{iaΝ<*{4J [OΙ5>VG'N9{ǍZi"u* '#'BD<'IșR!O$Y՜DܵMxwoMfNĽlxdV-!NJՍߺ!R^Zq3'==-.aNѭG>p13))?;;:0h[g+'^_[ٍ2,e+v\REU'Y[BBa~~VJʫ7s[6휈+E8%v\Mm^}ު޻q"iсA(W7EK9wڦͅ޼Չյ5נǗj8g2:qW4nĉ/\e2 'nhϤ*U\T~,ƀorJ':6AԱ)?'}óSWO4efЫ:wYkC+ىKEj(/𭗷$Cq]TmiDJD&-nIg74qspqБcIޗQoZNIOۢH;XŶX>P oU xح )rP7hDW7n ]!\fN >~R#ǜ]Dz;q7oߵ3}EoN]Ǥݪ߉i"u* '#o'BD]=x3=b35 Q^FWD4!|]oC:.0S&Lp򘊎Nr}#"Y%%m)jZzbx k;"j3tpM̻)Bbo֭DZ;.l=ؑFD lW:*lJDv]ʵrOylb))hθm >X;ǓM+TI8dDukx!V;QYS^FM5jTmUǺ </{.Zؠ|~Vz=4(W?⢢G ߓWFzC>< {SϔYA]=+99?+8%v\MK@1T^wSP^ζDknZE>5=ݬ4g,L:ȼiZ00eص3lbMݑaӯ<4w/~(Ibhw:O$( yp=p8_y|{oQ屢7VX?'LӕƺYd%}7 LS"p2*NFLԴOudTJz99~;vu3KIK:23_v^LDDu[vjjηo/.^:7̕K]QÃ9[S5]m*z q]9-!03)ťDTXJ_Zv!o#4-jZz2oDIO0//&ёa#Y7vGbeQgeEpip>weᒐS8uuR1Y촴>)kk<W۷DdheW ^]SMV穒p2V. /NFHR.tٿy_sch1lh/}]l'O 9u>,"۴N.^eRdN vp0,aŮwi3{昃K?us?5a୛,)_ew\M0 /JVTPp+`ҺSv_nmς6C].n/!Z}?9ָgee%və'M%[[YC}uM=~(Tµ]LrrM dQ^D'Rgwj7rDɝ&>a5cbJJm v n@ n61i63rs9l?~Ѵif$G1dzjK((蚷xO[Ui45:n2)i.@-vd舨rS7W&v#WJ1o!"* xvk܅Je\C.H54~^ુ'#1kt"C%{ @ycFz,yo)U}NY v=ulf]n5AMbUh<U=J>oǮQ{wwŭڨp2\8U:WOFbfdTV 5?@-6Ij5oe /%HF^̾*Ylf5AYϞHu4~Ry2y {J@exu{,TKwp8Nn]?tHj >kҳ+0ahm(MbIIKP35nk笔ELЬ*+0elLDӫpX M ee޿g;_DӧԘM#C`LJZZaÌ_'ahR0Lɫ(fd/?O))  CrYYlGSnF&) S22ElGSQAIq,T^f Q@CT\XHDHT3ѕU咝MA](~!}R"̤$`$לoߴ/?OJR\n&N<}RJZX>5jic=w' \y>@ܤ3'5 َ@RDD]DWC@|a׮_]aَIDD#vTmwr܄¼^8>jyWb;0J䔕g\5"iEEU@¿MZAÿ+Ll28ʨocju*5;___͏#veUTruDmW24ݭ=C+9~=$)lUB>kt ~icE+eψ ~tr6Cqv,SsJ}x]6CL_8OT/D?ۿΝ:1&<}1+;Ng,xӻ{Fv|ⳅzV XFJ~ݙryL!"f}y?}:6ŗW3z# (avj?]^UbL5_D3'(H+Kɪ635_ " pE}=+d#INDuzXss%"Dj\Y uYu&u90ܤ"j2jP+~?/*kGˬŏ;@Sw.阚,~xbFe9ureP}8~N}k3,oNwK"1g._.N]0;+ ^oCDiuJg]}QLBMl꘮n% ǓGQ\XzM[9NwYCq^~533??P\PP};}}{f^ڻݾ'DH&1{)Ɍ}Xn1Ldf,3%kCEH]d){=w)r^q׹u>ouuE.^ݑ00ry9Ze"i> x ks~u6],⬭Jȃw}Rqixɣ%˗]Ai^[OUum5p P輷$0/]߶-~` 5y /vv:zzãxÛOBBQO[)3nC~=Q*GVV}02VvQ{O {ӆӛ6OvrZ}=a|V+ly M)w_PkٽˍG|N+]$UZ9y=݀ܿp1)QF;kZkvXY}O7(9xg4bT{5'M4/U*_8 bbbzz磼|x/sΙg\XիwcNF>>|RQV.;}TgװS?|H/> fd'o2z䄋g?܉| g;U_G;{n ī#֭^u601vQbIEа_m4|XOLvr:=Y=zw5G)+nޜ66X}$147o1ᛉ /ʕ7@;w~m?ݵ zk`GZOcKKI']k>~\̊U+4xmCyn?[~qeᄊ?ϚȌZ|YX4;zb嶿t1,Jڜg23S4|x)ܸX"'\Wzu܉Zdo "i~}*6kIJH8#Bn;^ܱ={~Ɋ`'e2>&69Ȫ?6~srʣڮ>~עZ )/^Tj֮vhò$1477p@n;h~_Kzt4 ͣыc[>?3У&VVڮ 'Ϫw\{֬=*dYjtT{7ڮ WϞzlڋaff:/v]8 >I,욌dԈ׏Pҝ3JWXcݻ, B&˳[Opǎ M\6ۤxRv y̼h;J-WؾJk?3fLCLL3g8ˑ{tٽK& *I%-9-GCV_ڹ+3#ùQCimig#>("l*f^oPZUhkfm(4 {ool\S:zu)b繭vL~ʮUZ~R̡od5V蛚 Qǫ&Xh B?UګW;w{-[_<|h[ŭm? hc_#>3jN]W,+_^}}PxI7;::j B?޹'o8~Î)SMKtnܨb-ʖͺcܾ:zz 7nkīGٸUq3i'.~ef}5J-WjɫE#J2#Ctʼn?eVݵz z>ݷ,vKKʕwԲjՊ\V?}M?!'Wk>~\ZrՃQ;w] 8SY˭K&#HI_:NnEVff'<}kvvEEBAZTTⓊqfM+6kZb/_ 25txL"v镪NA m{R{ZZ[gLeK.(|HR]ۖ76DG/0#kR/sr;[.EZ}ʖѳ{ T !GWs65Vff~?Ɔ_ãm{4R6{֦֌MvE]~WT:uUC4Fkx<"rOpFFf=aQwn?x"ҼJ m4FezxIjܸ1_PC)rN!lPgN_޲%OMM]\;uYQDvi\w:t}!CfoBOOW}=!!۶nݛ8ֺKV>r +{33s萙.hmKW*|4T7 ! 3!Db⋘zz͛בs P}̭JCCOE95VJierX.ԩKff&wUUu䯿9q;z=rVuy2y/!ذ1BO/)@A@|B+{YYYؔrp(`oYe[sbveJB8::VkE ^aYZN!Bm[ !~VI_u4fcy׺iCr{ܯuU_T7n={ʋINyxTU5VVݣܽ+&m!݇3֭VS_Uر ?<#ͨ}L<;ZW v<ƎUxrS$'!ީCttu4?gㆈcYbj~6tϦ R)wBz^XGJ_ǎÔGUXXYe3S/h)>G !||J[z PwLA]jjZp=!zcݺՄXv=FUBؔB$%hI!szZTDCܹ :Ǎ-~n 􌷎P(JRT(RKJJj=MտVRa.\=s W>s̼'~? uT0;wʝ;Ry\Y:U4P+oV*m4nߡE% _Ji"Sx_ UU\K u'w9յSK-\4R%l;rƁ'djeex#[[kiKTRJ B;wv4F} uz͚y׮()4k^[_߸|BګV8vF)afdd6qq7Ej]WuJPF~/)>ppi ##sBMߌu{߼yokށ_MBl%56zIZZsqN'XF1o^EG9C:oCᓌ̧OsDxϛ^DġνzMo=r;TRŅW/D \֞#9]f q01Eb&9r{I'mâ=v5Vb/_kΠǞϿhvc%d/![;u>~'uy 7X.>O]\ʩ6P`]Ȫ!J--8ާvmL%*|;>utK=Uj׮s{I¡db_k.Zϣ Z^-#Ϟ{䙮n25kUiٲBOOwfЈ "ân޼SթcPǍz?w<(^ϯAħ ݺu>L055vp*>_+)Sȕ\ ձcKBevZ#> PСӏ=}ӳXz?2z;ݼyOPۗn޼v-T(U.)).8::8p r^.sxzz;::j %fer7xu2ֱt'䌟P0аB  yrckUZ5qqq_f͞={ݵU-fG?ĥN.֟QTVm֬Y'Nxebb֭[@;~W___kkkOOCi$>}S@)þTcϻr}Ucr߭c='>p<^B]}UnmgZ{45qf/5v J/Z1\zO,i~^Sluq&'ȿ>aa̕91sj,ahfIBcC;d(38pe' AO?X`hVrVɯ*)-)Wi hίNJL3(Kvߙ#BP(/^vΜ9BnݺYYY,Yr„ ꯼;p@K(ѢEcfddL:uٲ?HwV({+]verBSSS3fxxxxxxɬD%$$QFŊ3fLbbb.&kss'o͕bFuCؔ oei.2B|Qe-aJ蘒:XjZ_WS?C yA?1Y]Aw=RmU[JJK격Kٚپ2_ hnK/^KO.Uߺu+>K}Tة}cǭgT;~^.?gwho:2) !D۶m7mڤjܼyibbbT-+W޽.lڴM6zt֭k߾nݺQQQf9rjyZfM/\|ԒҢE}ilܸqXXJJenV^1[TTŋym.٧Bf9׶U3˔sEMظ䇛6<:zaȏ+Y  Os/)>4L+挿zxӛ#\S ?>xbIoW#B;&I1솿gFͼV9r{zy>ydȣwByO!V" IDATĤn}}+uBQC;EXǟ:u*11qB=z,\PgO>MMMu/T*M1fDDDhhhbb7sA3g|oJJJ``oZ޹sFiff6i$UٳWXK>xgϖYIppի֭[ä5k;wny@Dn+Wnƌ]T-Q5J_D{g+J-KUaKAaf}w-5ﭾTݼk]ƣy;)s}=8QLr{UVSĢc*j䪘"5dJbrij릋r'd&uh밯_reG:_h;wm)Uj;wnFOA4 WAzUanR5W)ϸfffjTxY[[[!ŋۥ%-[޸d!J<==S$..N|a!D||| KG ?ʷUs !ߺ/}fUJFKZY\5!_+?ܸu_gC+-x?iN}+Ce>;sB]s|J~}$GG! }AAA ӓƎEtt_BB飾)66V(M.]tIf%gΜB4nXςRp5 IfRwP϶FIɯlڳ+B̬=տ>g}C:ݣōo갩Aq#=#g(@1wb<s;fG܎q; [mVJT?-4eR9`Ç-[˗fffYFFj?HY>}zΜ9w)[l.]l۶CIIIÆ zs|9T|BFֽԗyóOy1QGϪ>8rFQξ"]!Q=+8I9gOI9TPNqcKyx~I+=߷d*go#;*mZz{]nn]dE!KsNVNB7Z^ߛy{3z}Bd]tc[``K]( ŷ~+駟QFI=H/_4x`g$...BGq֭B+,My8/a3/>=vB&vBً7v]6swy 31dǐH}aao+ Vzfä.lBJ׼ b̙ O>]~}~6_|ɓwZ[[ߛԳgaÆ͛7/ݥzʕ=zG$0%K]xb//Pay˛{FDU Ya`{P[O}puc~XpՖl*uvGƁ~㯈9w_e9]p9T.pyWB߅Y&潵%((Hot=V\K">u &htÇK,)HIIi޼5:4j(,,@f%C}SB;|0 .}pݣ}bf&FX$ipۧq1g~Ֆ?Wt }==c#Wr{~~po пw_Rzy|x1Z5mbldeieA6Tno=Y{iw#=#S}Fm}̭%}Tߴq XE]S1'}&%.j;r2bĈ gggCCO>do  FFF3fPe'Νsv1IYw0;20W^˖-9rY]K6bbb}]0ǵk׶m&իkˑ iMFḋP=ԪUʕ+kOO~5jh.ӇPk ]Z)E % fƛȼtkg?qlO2B'O^z׮].]JKKspphٲq㤷N‚)Zj_ϟf͚={k*['~޴W~EΪUֵkצM߿Ȑ!_ݻ]_}}}===:@ާ!=#c~>Զj[6l}Xڔ>{lظ?hΒ i}-]|,]|^&%}/\vYo!ē}G,[SS~^>]k76vn=83c&J=eA:Ԥ]T#D;otkn%\kv9aB+IN\Zx|u?\g*Xڪ~ 猌̠k4ڭuM~!5 eso2pmնN:\\"@P(/^vΜ9BnݺYYY,Yr„ ?޽~%ZhǔS:::.[Lں{nBakkWJJBصkW:MMMSSSg̘abbbbb&F+VаB cƌILL<+3f];>kTHݞֶGjl_ꦥS~o+4"Ja͂g-K3Gu h*}.J蘒e:XjLP՟é,e}B|Q FآnhAUcQ,N㌲}tobՆbb.̓z&<}VН{T[UAYKKG|^#(Rm۶ݴiqӦMQ\{h iӦ6mڨwիҥKջ[}BuFEE͚5kȑ:th͚5ՏܧO/rRKJJJ-ۧѳqaaar*Q*ݺu[z nnnQQQ/^􌏏wttZx+f+7>pxƷ.[얈?5#XGZZCL:5\u/*ttB3+5-MeٟhW.+7,SĊ9^{戵?W~S}̣'/_<ɿs87q7mxt>W*mgN-d杓m[5þL;^>W8#UIݮO'.t{WjZ[xse w=r+:g/vlM2ǟ:u*11qB=z,\PgO>MMMu/T*M1fDDDhhhbb7sΕ6}wB3g|z1%%%00P7d-ܹs#G4334iq+VҥKpLr] g2my2OV\elO\Υ{_ﲓ h;wm)Uj;wnFOA4 իW檖5j!RquuԨٳBŋKK-[Lqɒ%Bxzz !ۧ>H\\B!x;~o)-!ovs2qn !{7jZ{q%z{҇%my`]^Bu: YVX4Ͼ^^W)o7^5>׫:U#+-x.yr=݉;97"mllmyuڵ;w+>K7=\ 4HOOOz;v?~~~ ǏӧX!z4t%*9sqߟCڵkS`h/}P*ђ\T !222W} !D1K??Jeξz624HJ~e[mF{>k>0Joʔ)JrÇ/[˗/̲3222R}ӧ̙cnn~ΝevE϶m:t萔4lذ7ʗC%?YR_ >DޛUpBluSyG;!D#;"0*U('8GlѐéiqW1yT vRu+:B/~MϜ2J9{݉#Ϥ]\\XByoP([!O?$M=5JZA|򀀀k<$qqqBcܺubŊ2pssBd{A #>;iu-#)iGN^:pCBq~_O=~쏿"HѾuVDVqW)iW^nG#r1S`gk-xÓ^}{ ˤO§Iw<\B퍅y"WđgB3g&$$<}tP_|񅫫'O޽kmm~o^PPPϞ=Ӈ 6o޼lw+W>zѣG˗/1bjK,vZrrrJJJllŋv^@s'o њ7^}&AR}i.\>}֖<2 WmɶlW!i2gɆ3th]9lgW-\.8?yey,\.S"sn ݣG+Wg">u &htÇK,)HIIi޼5:4j(,,@f%C}SB;|0 .}wzzalNF&F<}@'%m262p4yt/Uv`f~Ֆ?Wt }==c#Wr{~~po9éi)hgh`3~h_e/5G,L|ٱ:Hzӻ.pB]|J o1Wر:U&FV_v^4} ^41"((O>3L|4'&E+,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@Ȣ #Gh ȵ'Oh 7S>x@UyFRJFْ.'++vڙj +Rv ^83xӑhdg@B|YO d!>,'@B|YO d!>,'@B|YO d!>,' '.>.>@F| 1 @=m@Kݮ f@fx7INRlevwpի;[kuG}4C >xϔJeQAC#U-7Y!|?h6wA| c2cևF)]׮jbltBة?;?'~`x {r!D/|ꗰ062UuoQڮ }𞝋&>VT*J7nji}S*Yj!P(J2=#COWUPˑT%*} %[ !޾UJ;vOiwܸ}?UjJjڕkWѼ]8!hѰE1CՒzruq Wm:aN>U 'B8A#'n3ʷY5ێqmzun4rX 3>Ig@B|YO d!>,'@B|YO d!>,'iHpU_^|vUK9ҍVFiФP*ڮ(]j=PGWGGؤT}տVjnɭSe 222tut5vxhYt4:: T-aeK@Ok=Jc& 22jWTdyIO_ϯVJ@Ou hO|iRX[% +}m[5P(32;7V= оV ԸEÚZ, Yc{ n[`!>_s/奴ZA| 3Sc&tu-rvED| v 223tuk͋t]'nR$=#L VG2zYfi h2.I{]4ըQȑ#ڮhK{]˗>}\z7=&]#$$ݻڮh貳\U?JT[\ reܽdzO@P(L M],{/d,^#>GщIoPkܼ /v O OIw jyjp253wv Wt([\__I‹ǯoz299UӠa:^-LL ^xw=.Q}c'9?g퟇?z!u\aJ*4JNN~Q3ιNpI>RKK}ZUߠɣv8s@za9cGo*@sݲʹ$"OF?sT}U(Dヘ<=˫ZKYSwiݿ[jIk6^QU+Xr^LBfjjXv:B+ J99YWԾJΝkKS%׺B!~6!EZZޯ|koTg[13%L'EyrB}{/k_oVUMQWxUS3:HՒv*o=le=5閷 5OްQK[ef(>zvBoPirr{QQW쿔Kt\gtuu/K" hаRO$EG]޶C韺;+fr>Gd {) @[JQ4=:&Kϵ]GB[PH;t#Gx3E>,'t@Q$==w/,1fh XfKHQIUu.ܼ@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO d!>,'@B|YO ,'?KR&IENDB`python-oerplib-0.8.4/doc/source/conf.py000066400000000000000000000160001245703354300200450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # OERPLib documentation build configuration file, created by # sphinx-quickstart on Thu Sep 15 10:49:22 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. if os.path.exists("../.."): sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'OERPLib' copyright = u'2011-Today, ABF Osiell, Sébastien Alix' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.8' # The full version, including alpha/beta/rc tags. release = '0.8' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. #html_theme = 'default' html_theme = 'nature' html_style = 'oerplib.css' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '_static/logo_oerp_64x64.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'OERPLibdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'OERPLib.tex', u'OERPLib Documentation', u'ABF Osiell', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'oerplib', u'OERPLib Documentation', [u'ABF Osiell - Sébastien Alix'], 1) ] python-oerplib-0.8.4/doc/source/download_install.rst000066400000000000000000000032541245703354300226440ustar00rootroot00000000000000.. _download-install: Download and install instructions ================================= Python Package Index (PyPI) --------------------------- You can install OERPLib with the `easy_install` tool:: $ easy_install oerplib Or with `pip`:: $ pip install oerplib An alternative way is to download the tarball from `Python Package Index `_ page, and install manually (replace `X.Y.Z` accordingly):: $ wget http://pypi.python.org/packages/source/O/OERPLib/OERPLib-X.Y.Z.tar.gz $ tar xzvf OERPLib-X.Y.Z.tar.gz $ cd OERPLib-X.Y.Z $ python setup.py install No dependency is required except `pydot `_ for some methods of the :class:`inspect ` service (optional). Source code ----------- The project is hosted on `GitHub `_. To get the last stable version (the ``master`` branch), just type:: $ git clone https://github.com/osiell/oerplib.git .. note:: The project uses the `git-flow` branching model. To contribute, please refer to it. Run tests --------- .. versionadded:: 0.4.0 Unit tests depends on `unittest2` (Python 2.3+) or `unittest` (Python 2.7 and 3.x), and `argparse`. To run unit tests from the project directory, run the following command:: $ PYTHONPATH=.:$PYTHONPATH ./tests/runtests.py --help Then, set your parameters in order to indicate the `OpenERP` server on which you want to perform the tests, for instance:: $ PYTHONPATH=.:$PYTHONPATH ./tests/runtests.py --create_db --server 192.168.1.4 --test_xmlrpc --xmlrpc_port 8069 The name of the database created is ``oerplib-test`` by default. python-oerplib-0.8.4/doc/source/faq.rst000066400000000000000000000057501245703354300200610ustar00rootroot00000000000000.. _faq: Frequently Asked Questions (FAQ) **(New in version 0.8)** ========================================================= Connect to an OpenERP Online (SaaS) instance -------------------------------------------- First, you have to connect on your `OpenERP` instance, and set a password for your user account in order to active the `XML-RPC` protocol. Then, just use the ``xmlrpc+ssl`` protocol with the port 443:: >>> import oerplib >>> oerp = oerplib.OERP('foobar.my.openerp.com', protocol='xmlrpc+ssl', port=443) >>> oerp.db.server_version() '7.saas~1' Update a record with an `on_change` method ------------------------------------------ `OERPLib` does not provide helpers for such methods currently. A call to an ``on_change`` method intend to be executed from a view and there is no support for that (not yet?) such as fill a form, validate it, etc... But you can emulate an ``on_change`` by writing your own function, for instance:: def on_change(oerp, record, method, *args): """Update `record` with the result of the on_change `method`""" res = oerp.execute(record.__osv__['name'], method, *args) for k, v in res['value'].iteritems(): setattr(record, k, v) return record And call it on a record with the desired method and its parameters:: >>> order = oerp.get('sale.order').browse(42) >>> order = on_change(oerp, order, 'product_id_change', ARGS...) >>> oerp.write_record(order) # Save your record To know what parameters to send to the ``on_change``, the :func:`scan_on_change ` method can help you. Some OSV methods does not accept the `context` parameter -------------------------------------------------------- Since `OpenERP` 6.1, the ``context`` parameter can be sent automatically for each call to an `OSV/Model` method (this is the default behaviour since `OERPLib` 0.7). But on the side of the `OpenERP` server, some `OSV` methods have no ``context`` parameter, and `OERPLib` has no way to guess it, which results in an nasty exception. So you have to disable temporarily this behaviour by yourself by setting the ``auto_context`` option to ``False``:: >>> oerp.config['auto_context'] = False # 'get()' method of 'ir.sequence' does not support the context parameter >>> next_seq = oerp.get('ir.sequence').get('stock.lot.serial') >>> oerp.config['auto_context'] = True # Restore the configuration Change the behaviour of a script according to the version of OpenERP -------------------------------------------------------------------- You can compare versions of `OpenERP` servers with the :func:`v ` function applied on the :attr:`OERP.version ` property, for instance:: import oerplib from oerplib.tools import v for session in oerplib.OERP.list(): oerp = oerplib.OERP.load(session) if v(oerp.version) <= v('6.1'): pass # do some stuff else: pass # do something else python-oerplib-0.8.4/doc/source/index.rst000066400000000000000000000073071245703354300204210ustar00rootroot00000000000000.. OERPLib documentation master file, created by sphinx-quickstart on Thu Sep 15 10:49:22 2011. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to OERPLib's documentation! =================================== Introduction ------------ **OERPLib** is a Python module providing an easy way to pilot your **OpenERP** and **Odoo** servers through `RPC`. Features supported: - `XML-RPC` and (legacy) `Net-RPC` protocols, - access to all methods proposed by a model class (even ``browse``) with an API similar to the server-side API, - ability to use named parameters with such methods (server >= `6.1`), - user context automatically sent (server >= `6.1`) providing support for internationalization, - browse records, - execute workflows, - manage databases, - reports downloading, - inspection capabilities (graphical output of relations between models and dependencies between modules, list ``on_change`` methods from model views, ...). Quick start ----------- How does it work? See below:: import oerplib # Prepare the connection to the server oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) # Check available databases print(oerp.db.list()) # Login (the object returned is a browsable record) user = oerp.login('user', 'passwd', 'db_name') print(user.name) # name of the user connected print(user.company_id.name) # the name of its company # Simple 'raw' query user_data = oerp.execute('res.users', 'read', [user.id]) print(user_data) # Use all methods of a model class order_obj = oerp.get('sale.order') order_ids = order_obj.search([]) for order in order_obj.browse(order_ids): print(order.name) products = [line.product_id.name for line in order.order_line] print(products) # Update data through a browsable record user.name = "Brian Jones" oerp.write_record(user) For more details and features, see the :ref:`tutorials `, the :ref:`Frequently Asked Questions (FAQ) ` and the :ref:`API reference ` sections. Contents -------- .. toctree:: :maxdepth: 3 download_install tutorials faq reference Supported OpenERP/Odoo server versions -------------------------------------- `OERPLib` has been tested on `OpenERP` server v5.0, v6.0, v6.1, v7.0 and `Odoo` v8.0. It should work on next versions if `Odoo` keeps a stable API. Supported Python versions ------------------------- `OERPLib` support Python versions 2.6, 2.7. License ------- This software is made available under the `LGPL v3` license. Bugs or suggestions ------------------- Please, feel free to report bugs or suggestions in the `Bug Tracker `_! Make a donation --------------- `OERPLib` is mainly developed on free time. To show your appreciation and support this project, it is possible to make a donation through `PayPal`: .. raw:: html
Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-oerplib-0.8.4/doc/source/ref_common.rst000066400000000000000000000001531245703354300214260ustar00rootroot00000000000000oerplib.service.common ====================== .. autoclass:: oerplib.service.common.Common :members: python-oerplib-0.8.4/doc/source/ref_db.rst000066400000000000000000000001331245703354300205210ustar00rootroot00000000000000oerplib.service.db ================== .. autoclass:: oerplib.service.db.DB :members: python-oerplib-0.8.4/doc/source/ref_error.rst000066400000000000000000000001121245703354300212620ustar00rootroot00000000000000oerplib.error ============= .. automodule:: oerplib.error :members: python-oerplib-0.8.4/doc/source/ref_fields.rst000066400000000000000000000025761245703354300214170ustar00rootroot00000000000000.. _fields: Browse object fields ==================== The table below presents the Python types returned by `OERPLib` for each `OpenERP` fields used by ``browse_record`` objects (see the :func:`browse ` method): ================ ============================== `OpenERP` fields Python types used in `OERPLib` ================ ============================== fields.binary basestring (str or unicode) fields.boolean bool fields.char basestring (str or unicode) fields.date `datetime `_.date fields.datetime `datetime `_.datetime fields.float float fields.integer integer fields.selection basestring (str or unicode) fields.text basestring (str or unicode) ================ ============================== Exceptions made for relation fields: ================ =========================================================== `OpenERP` fields Types used in `OERPLib` ================ =========================================================== fields.many2one ``browse_record`` instance fields.one2many generator to iterate on ``browse_record`` instances fields.many2many generator to iterate on ``browse_record`` instances fields.reference ``browse_record`` instance ================ =========================================================== python-oerplib-0.8.4/doc/source/ref_inspect.rst000066400000000000000000000007031245703354300216040ustar00rootroot00000000000000oerplib.service.inspect **(New in version 0.8)** ================================================ .. automodule:: oerplib.service.inspect :members: oerplib.service.inspect.Relations ''''''''''''''''''''''''''''''''' .. autoclass:: oerplib.service.inspect.relations.Relations :members: oerplib.service.inspect.Dependencies '''''''''''''''''''''''''''''''''''' .. autoclass:: oerplib.service.inspect.dependencies.Dependencies :members: python-oerplib-0.8.4/doc/source/ref_oerp.rst000066400000000000000000000001061245703354300211010ustar00rootroot00000000000000oerplib.OERP ============ .. autoclass:: oerplib.OERP :members: python-oerplib-0.8.4/doc/source/ref_oerplib.rst000066400000000000000000000010631245703354300215730ustar00rootroot00000000000000oerplib ======= .. automodule:: oerplib :members: Here's a sample session using this module:: >>> import oerplib >>> oerp = oerplib.OERP('localhost') # connect to localhost, default port >>> user = oerp.login('admin', 'admin', 'my_database') # login returns an user object >>> user.name 'Administrator' >>> oerp.save('foo') # save session informations in ~/.oerplibrc >>> oerp = oerplib.load('foo') # get a pre-configured session from ~/.oerplibrc python-oerplib-0.8.4/doc/source/ref_osv.rst000066400000000000000000000004301245703354300207430ustar00rootroot00000000000000oerplib.service.osv =================== oerplib.service.osv.Model ''''''''''''''''''''''''' .. autoclass:: oerplib.service.osv.Model :members: oerplib.service.osv.BrowseRecord '''''''''''''''''''''''''''''''' .. autoclass:: oerplib.service.osv.BrowseRecord :members: python-oerplib-0.8.4/doc/source/ref_rpc.rst000066400000000000000000000012471245703354300207270ustar00rootroot00000000000000oerplib.rpc =========== .. automodule:: oerplib.rpc .. autoclass:: oerplib.rpc.Connector :members: XML-RPC and Net-RPC connectors '''''''''''''''''''''''''''''' .. autoclass:: oerplib.rpc.ConnectorXMLRPC :members: .. autoclass:: oerplib.rpc.ConnectorXMLRPCSSL :members: .. autoclass:: oerplib.rpc.ConnectorNetRPC :members: .. automethod:: oerplib.rpc.get_connector JSON-RPC connectors **(New in version 0.8)** '''''''''''''''''''''''''''''''''''''''''''' .. warning:: The support of JSON-RPC is still in the experimental stage. .. autoclass:: oerplib.rpc.ConnectorJSONRPC :members: .. autoclass:: oerplib.rpc.ConnectorJSONRPCSSL :members: python-oerplib-0.8.4/doc/source/ref_session.rst000066400000000000000000000002231245703354300216170ustar00rootroot00000000000000oerplib.tools.session **(New in version 0.8)** ============================================== .. automodule:: oerplib.tools.session :members: python-oerplib-0.8.4/doc/source/ref_tools.rst000066400000000000000000000001111245703354300212700ustar00rootroot00000000000000oerplib.tools ============= .. automodule:: oerplib.tools :members: python-oerplib-0.8.4/doc/source/ref_wizard.rst000066400000000000000000000001531245703354300214360ustar00rootroot00000000000000oerplib.service.wizard ====================== .. autoclass:: oerplib.service.wizard.Wizard :members: python-oerplib-0.8.4/doc/source/reference.rst000066400000000000000000000003561245703354300212450ustar00rootroot00000000000000.. _reference: Reference ========= .. toctree:: :maxdepth: 2 ref_fields ref_oerplib ref_oerp ref_osv ref_common ref_db ref_wizard ref_inspect ref_rpc ref_tools ref_session ref_error python-oerplib-0.8.4/doc/source/tutorials.rst000066400000000000000000000620311245703354300213330ustar00rootroot00000000000000.. _tutorials: Tutorials ========= First step: prepare the connection and login -------------------------------------------- You need an instance of the :class:`OERP ` class to dialog with an `OpenERP/Odoo` server. Let's pretend that you want to connect as `admin` on the `db_name` database of your local server (with the `XML-RPC` service which listens on port `8071`). First, prepare the connection:: >>> import oerplib >>> oerp = oerplib.OERP(server='localhost', protocol='xmlrpc', port=8071) You can also specify the default database to use with the `database` parameter:: >>> oerp = oerplib.OERP(server='localhost', database='db_name', protocol='xmlrpc', port=8071) To check databases available, use the :attr:`oerp.db ` attribute with the **list** method:: >>> oerp.db.list() ['db_name', 'db_name2', ...] The connection is ready, you are able to log in on the server with the account of your choice:: >>> user = oerp.login(user='admin', passwd='admin') Or, if no default database was specified before:: >>> user = oerp.login(user='admin', passwd='admin', database='db_name') The ``login`` method returns an object representing the user connected. It is built from the server-side model ``res.users``, and all its informations are accessible (see :ref:`browse-records` section):: >>> print(user.name) # print the full name of the user >>> print(user.company_id.name) # print the name of its company Now you are connected, you can easily execute any kind of `RPC` queries on your server (execute model methods, trigger workflow, download reports, and handle wizards). .. _tutorials-execute-queries: Execute queries --------------- The basic method to execute queries (related to the ``/object`` `RPC` service) is :func:`execute `. It takes at least two parameters (model name and the method name) following by variable parameters according to the method called. Example:: >>> order_data = oerp.execute('sale.order', 'read', [1], ['name']) This instruction will call the ``read`` method of the model ``sale.order`` with the parameters ``[1]`` (list of record IDs) and ``['name']`` (list of fields to return). However, for usual methods such as ``create``, ``read``, ``write``, ``unlink`` and ``search`` there are convenient shortcuts available (see :class:`oerplib.OERP`):: >>> partner_id = oerp.create('res.partner', {'name': 'Jacky Bob', 'lang': 'fr_FR'}) >>> partner_data = oerp.read('res.partner', [partner_id], ['name']) >>> oerp.write('res.partner', [partner_id], {'name': 'Charly Bob'}) True >>> partner_ids = oerp.search('res.partner', [('name', 'ilike', 'Bob')]) >>> oerp.unlink('res.partner', [partner_id]) True There is another way to perform all methods of a model, with the :func:`get ` method, which provides an API almost syntactically identical to the `OpenERP/Odoo` server side API (see :class:`oerplib.service.osv.Model`):: >>> user_obj = oerp.get('res.users') >>> user_obj.write([1], {'name': "Dupont D."}) True >>> context = user_obj.context_get() >>> context {'lang': 'fr_FR', 'tz': False} >>> product_obj = oerp.get('product.product') >>> product_obj.name_get([3, 4]) [[3, '[PC1] PC Basic'], [4, u'[PC2] Basic+ PC (assembl\xe9 sur commande)']] If you run a server in version `6.1` or above, the user context is automatically sent. You can disable this behaviour with the :attr:`oerplib.OERP.config` property:: >>> oerp.config['auto_context'] = False >>> product_obj.name_get([3, 4]) # Without context, lang 'en_US' by default [[3, '[PC1] Basic PC'], [4, '[PC2] Basic+ PC (assembly on order)']] .. note:: The ``auto_context`` option only affect model methods. Here is another example of how to install a module (you have to be logged as an administrator to perform this task). The ``button_immediate_install`` method used here is available since the `6.1` server version:: >>> module_obj = oerp.get('ir.module.module') >>> module_id = module_obj.search([('name', '=', 'purchase')]) >>> module_obj.button_immediate_install(module_id) .. _browse-records: Browse records -------------- A great functionality of `OERPLib` is its ability to generate objects that are similar to browsable records used on the server side. All of this is possible using the :func:`browse ` method:: # fetch one record partner = oerp.browse('res.partner', 1) # Partner ID = 1 print(partner.name) # fetch several records for partner in oerp.browse('res.partner', [1, 2]): print(partner.name) From such objects, it is possible to easily explore relationships. The related records are generated on the fly:: partner = oerp.browse('res.partner', 3) for child in partner.child_ids: print(child.name) You can browse objects through a :class:`model ` too. In fact, both methods are strictly identical, :func:`oerplib.OERP.browse` is simply a shortcut to the other:: >>> partner1 = oerp.browse('res.partner', 3) >>> partner2 = oerp.get('res.partner').browse(3) >>> partner1 == partner2 True Outside relation fields, Python data types are used, like ``datetime.date`` and ``datetime.datetime``:: >>> order = oerp.browse('purchase.order', 42) >>> order.minimum_planned_date datetime.datetime(2012, 3, 10, 0, 0) >>> order.date_order datetime.date(2012, 3, 8) A list of data types used by ``browse_record`` fields are available :ref:`here `. Update data through browsable records ------------------------------------- Update data of a browsable record is workable with the :func:`write_record ` method of an :class:`OERP ` instance. Let's update the name of a partner:: >>> partner.name = "Caporal Jones" >>> oerp.write_record(partner) This is equivalent to:: >>> oerp.write('res.partner', [partner.id], {'name': "Caporal Jones"}) Char, Float, Integer, Boolean, Text and Binary '''''''''''''''''''''''''''''''''''''''''''''' As see above, it's as simple as that:: >>> partner.name = "New Name" >>> oerp.write_record(partner) Selection ''''''''' Same as above, except there is a check about the value assigned. For instance, the field ``type`` of the ``res.partner`` model accept values contains in ``['default', 'invoice', 'delivery', 'contact', 'other']``:: >>> partner.type = 'default' # Ok >>> partner.type = 'foobar' # Error! Traceback (most recent call last): File "", line 1, in File "oerplib/fields.py", line 58, in setter value = self.check_value(value) File "oerplib/fields.py", line 73, in check_value field_name=self.name, ValueError: The value 'foobar' supplied doesn't match with the possible values '['default', 'invoice', 'delivery', 'contact', 'other']' for the 'type' field Many2One '''''''' You can also update a ``many2one`` field, with either an ID or a browsable record:: >>> partner.parent_id = 1 # with an ID >>> oerp.write_record(partner) >>> parent = oerp.browse('res.partner', 1) # with a browsable record >>> partner.parent_id = parent >>> oerp.write_record(partner) You can't put any ID or browsable record, a check is made on the relationship to ensure data integrity:: >>> user = oerp.browse('res.users', 1) >>> partner = oerp.browse('res.partner', 2) >>> partner.parent_id = user Traceback (most recent call last): File "", line 1, in File "oerplib/fields.py", line 128, in setter o_rel = self.check_value(o_rel) File "oerplib/fields.py", line 144, in check_value field_name=self.name)) ValueError: Instance of 'res.users' supplied doesn't match with the relation 'res.partner' of the 'parent_id' field. One2Many and Many2Many '''''''''''''''''''''' ``one2many`` and ``many2many`` fields can be updated by providing a list of tuple as specified in the `OpenERP/Odoo` documentation, a list of records, a list of record IDs or an empty list or ``False``: With a tuple (as documented), no magic here:: >>> user = oerp.get('res.users').browse(1) >>> user.groups_id = [(6, 0, [8, 5, 6, 4])] >>> oerp.write_record(user) With a list of records:: >>> user = oerp.get('res.users').browse(1) >>> groups = oerp.get('res.groups').browse([8, 5, 6, 4]) >>> user.groups_id = list(groups) >>> oerp.write_record(user) With a list of record IDs:: >>> user = oerp.get('res.users').browse(1) >>> user.groups_id = [8, 5, 6, 4] >>> oerp.write_record(user) The last two examples are equivalent to the first (they generate a ``(6, 0, IDS)`` tuple). However, if you set an empty list or ``False``, a ``(5, )`` tuple will be generated to cut the relation between records:: >>> user = oerp.get('res.users').browse(1) >>> user.groups_id = [] >>> list(user.groups_id) [] >>> user.__data__['updated_values']['groups_id'] [(5,)] >>> user.groups_id = False >>> list(user.groups_id) [] >>> user.__data__['updated_values']['groups_id'] [(5,)] Another facility provided by `OERPLib` is adding and removing objects using `Python` operators ``+=`` and ``-=``. As usual, you can add an ID, a record, or a list of them: With a list of records:: >>> user = oerp.get('res.users').browse(1) >>> groups = oerp.get('res.groups').browse([4, 5]) >>> user.groups_id += list(groups) >>> [g.id for g in user.groups_id] [1, 2, 3, 4, 5] With a list of record IDs:: >>> user.groups_id += [4, 5] >>> [g.id for g in user.groups_id] [1, 2, 3, 4, 5] With an ID only:: >>> user.groups_id -= 4 >>> [g.id for g in user.groups_id] [1, 2, 3, 5] With a record only:: >>> group = oerp.get('res.groups').browse(5) >>> user.groups_id -= group >>> [g.id for g in user.groups_id] [1, 2, 3] Reference ''''''''' To update a ``reference`` field, you have to use either a string or a browsable record as below:: >>> helpdesk = oerp.browse('crm.helpdesk', 1) >>> helpdesk.ref = 'res.partner,1' # with a string with the format '{relation},{id}' >>> oerp.write_record(helpdesk) >>> partner = oerp.browse('res.partner', 1) >>> helpdesk.ref = partner # with a browsable record >>> oerp.write_record(helpdesk) A check is made on the relation name:: >>> helpdesk.ref = 'foo.bar,42' Traceback (most recent call last): File "", line 1, in File "oerplib/service/osv/fields.py", line 213, in __set__ value = self.check_value(value) File "oerplib/service/osv/fields.py", line 244, in check_value self._check_relation(relation) File "oerplib/service/osv/fields.py", line 225, in _check_relation field_name=self.name, ValueError: The value 'foo.bar' supplied doesn't match with the possible values '['res.partner', 'calendar.event', 'crm.meeting']' for the 'ref' field Date and Datetime ''''''''''''''''' ``date`` and ``datetime`` fields accept either string values or ``datetime.date/datetime.datetime`` objects. With ``datetime.date`` and ``datetime.datetime`` objects:: >>> import datetime >>> order = oerp.browse('purchase.order', 42) >>> order.date_order = datetime.date(2011, 9, 20) >>> order.minimum_planned_date = datetime.datetime(2011, 9, 20, 12, 31, 24) >>> oerp.write_record(order) With formated strings:: >>> order.date_order = "2011-09-20" # %Y-%m-%d >>> order.minimum_planned_date = "2011-09-20 12:31:24" # %Y-%m-%d %H:%M:%S >>> oerp.write_record(order) As always, a wrong type will raise an exception:: >>> order.date_order = "foobar" Traceback (most recent call last): File "", line 1, in File "oerplib/fields.py", line 187, in setter value = self.check_value(value) File "oerplib/fields.py", line 203, in check_value self.pattern)) ValueError: Value not well formatted, expecting '%Y-%m-%d' format Generate reports ---------------- Another nice functionnality is the reports generation (related to the ``/report`` `RPC` service) with the :func:`report ` method. You have to supply the name of the report, the name of the model and the ID of the record related:: >>> oerp.report('sale.order', 'sale.order', 1) '/tmp/oerplib_uJ8Iho.pdf' >>> oerp.report('webkitaccount.invoice', 'account.invoice', 1) '/tmp/oerplib_r1W9jG.pdf' The method will return the path to the generated temporary report file. Manage databases ---------------- You can manage server databases with the :attr:`oerplib.OERP.db` property. It offers you a dynamic access to all methods of the ``/db`` RPC service in order to list, create, drop, dump, restore databases and so on. .. note:: You have not to be logged in to perform database management tasks. Instead, you have to use the "super admin" password. Prepare a connection:: >>> import oerplib >>> oerp = oerplib.OERP(server='localhost') At this point, you are able to list databases of this server:: >>> oerp.db.list() [] Let's create a new database:: >>> database_id = oerp.db.create('super_admin_passwd', 'test_db', False, 'fr_FR', 'admin') The creation process may take some time on the server, and you have to wait before using the new database. The state of the creation process is returned by the :func:`get_progress ` method:: >>> database_id = oerp.db.create('super_admin_passwd', 'test_db', False, 'fr_FR', 'admin') >>> while not oerp.db.get_progress('super_admin_passwd', database_id)[0] ... pass >>> oerp.login('admin', 'admin', 'test_db') However, `OERPLib` simplifies this by providing the :func:`create_and_wait ` method:: >>> oerp.db.create_and_wait('super_admin_passwd', 'test_db', False, 'fr_FR', 'admin') [{'login': u'admin', 'password': u'admin', 'name': u'Administrator'}, {'login': u'demo', 'password': u'demo', 'name': u'Demo User'}] Some documentation about methods offered by the ``/db`` RPC service is available :class:`here `. Inspect the metadata of your server **(New in version 0.8)** ------------------------------------------------------------ Draw a graph of relationships between models '''''''''''''''''''''''''''''''''''''''''''' .. note:: This functionality requires the installation of `pydot `_. The :func:`relations ` method will help you to generate a graphic of such relationships:: >>> graph = oerp.inspect.relations(['res.partner']) >>> graph.write('rel_res_partner_v1.png', format='png') .. figure:: _static/rel_res_partner_v1.png :width: 100% Legend: +--------------------------------------------+--------------------------------------------+ | Element | Meaning | +============================================+============================================+ | .. raw:: html | many2one | | | | | partner_id | | +--------------------------------------------+--------------------------------------------+ | .. raw:: html | one2many | | | | | bank_ids | | +--------------------------------------------+--------------------------------------------+ | .. raw:: html | many2many | | | | | company_ids| | +--------------------------------------------+--------------------------------------------+ | .. raw:: html | Field required | | | | | [R] | | +--------------------------------------------+--------------------------------------------+ | .. raw:: html | Field function (readonly) | | | | | [F] | | +--------------------------------------------+--------------------------------------------+ | .. raw:: html | Field function (writable) | | | | | [Fw] | | +--------------------------------------------+--------------------------------------------+ | .. raw:: html | Field function (searchable) | | | | | [Fs] | | +--------------------------------------------+--------------------------------------------+ By default, only the direct relationships of the model ``res.partner`` are shown (this behaviour can be changed with the ``maxdepth`` parameter), and model attributes are hidden. You can control the displayed models through the ``whitelist`` and ``blacklist`` parameters. For instance, assume that you only want data models whose name begins with `res.partner` excluding the `res.partner.bank` model:: >>> graph = oerp.inspect.relations(['res.partner'], whitelist=['res.partner*'], blacklist=['res.partner.bank']) # Notice the use of wildcard here >>> graph.write('rel_res_partner_v2.png', format='png') .. note:: The blacklist has a higher priority than the whitelist .. image:: _static/rel_res_partner_v2.png :width: 350px To display attributes, use the ``attrs_whitelist`` parameter. A wildcard is used here to show attributes of all models (but you can specify which models you want):: >>> graph = oerp.inspect.relations(['res.partner'], whitelist=['res.partner*'], blacklist=['res.partner.bank'], attrs_whitelist=['*']) >>> graph.write('rel_res_partner_v3.png', format='png') .. image:: _static/rel_res_partner_v3.png :width: 350px To hide attributes of some models, you can use the ``attrs_blacklist`` parameter:: >>> graph = oerp.inspect.relations(['res.partner'], whitelist=['res.partner*'], blacklist=['res.partner.bank'], attrs_whitelist=['*'], attrs_blacklist=['res.partner']) >>> graph.write('rel_res_partner_v4.png', format='png') .. image:: _static/rel_res_partner_v4.png :width: 350px Also, some configuration options can be set through the `config` parameter. Here is how to display `many2many` table names:: >>> config = {'show_many2many_table': True} >>> graph = oerp.inspect.relations(['res.partner'], whitelist=['res.partner*'], blacklist=['res.partner.bank'], config=config) >>> graph.write('rel_res_partner_v5.png', format='png') .. image:: _static/rel_res_partner_v5.png :height: 350px For more details, take a look at the :func:`relations ` method documentation. Draw a graph of module dependencies ''''''''''''''''''''''''''''''''''' .. note:: This functionality requires the installation of `pydot `_. You will be able to generate a graphic representing dependencies between all modules with the :func:`dependencies ` method:: >>> graph = oerp.inspect.dependencies() >>> graph.write('dependencies_v1.png', format='png') .. figure:: _static/dependencies_v1.png :width: 900px By default all installed modules are shown on the resulting graph, the red ones can be seen as `root` modules (they depend on no module in the current graph). Assume we have installed the `Accounting and Finance` application, and want to only display dependencies related to the `account` module:: >>> graph = oerp.inspect.dependencies(['account']) >>> graph.write('dependencies_v2.png', format='png') .. figure:: _static/dependencies_v2.png :height: 250px This time the `root` module is ``account``. Modules may also contain data models. To highlight some of them among the modules, set the `models` and `models_blacklist` parameters with one or several patterns (a joker ``*`` can be used):: >>> graph = oerp.inspect.dependencies(['account'], models=['account.invoice.*']) >>> graph.write('dependencies_v3.png', format='png') .. figure:: _static/dependencies_v3.png :height: 250px Modules related to the matching models are shown in green (in addition to the red one). It is possible to display transient models too through the ``show_transient_model`` configuration option (displayed in gray in the following graph):: >>> config = {'show_transient_model': True} >>> graph = oerp.inspect.dependencies(['account'], models=['account.invoice.*'], config=config) >>> graph.write('dependencies_v4.png', format='png') .. figure:: _static/dependencies_v4.png :height: 250px To hide "noisy" modules and restrict the resulting graph only to data models that interest you, add the ``restrict=True`` parameter:: >>> config = {'show_transient_model': True} >>> graph = oerp.inspect.dependencies(['account'], ['account.invoice.*'], restrict=True, config=config) >>> graph.write('dependencies_v5.png', format='png') .. image:: _static/dependencies_v5.png :height: 190px Even in restricted mode, `root` modules which are not concerned by matching `models` are always displayed. Also, if no dependency can be satisfied between modules, the method will try to add one. For instance, the `base` module have no ``account.invoice.tax`` model, and `account` does not directly depends on `base`, so a dependency between `base` and `account` should be added to display a suitable graph:: >>> graph = oerp.inspect.dependencies(['base'], ['account.invoice.tax'], restrict=True) >>> graph.write('dependencies_v6.png', format='png') .. image:: _static/dependencies_v6.png :height: 250px For more details, take a look at the :func:`dependencies ` method documentation. Scan the views of data models to list `on_change` methods ''''''''''''''''''''''''''''''''''''''''''''''''''''''''' `on_change` functions of a model can be listed with the :func:`scan_on_change ` method. Each detected function can be present on several views:: >>> oerp.inspect.scan_on_change(['res.users']) {'res.users': {'on_change_company_id': {'base.view_users_form_simple_modif': {'company_id': ['company_id']}, 'mail.view_users_form_simple_modif_mail': {'company_id': ['company_id']}}, 'onchange_state': {'base.view_users_simple_form': {'state_id': ['state_id']}}}} The dictionary returned is formatted as follows: ``{model: {on_change: {view_name: field: [args]}}}``, e.g. the ``onchange_state`` method is set on the ``state_id`` field of the view ``base.view_users_simple_form``, and take the same field as parameter. Save the session to open it quickly later **(New in version 0.8)** ------------------------------------------------------------------ Once you are authenticated with your :class:`OERP ` instance, you can :func:`save ` these connection information under a code name and use this one to quickly instanciate a new :class:`OERP ` class:: >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'admin', 'my_database') >>> oerp.save('foo') By default, these informations are stored in the ``~/.oerplibrc`` file. You can however use another file:: >>> oerp.save('foo', '~/my_own_oerplibrc') Then, use the :func:`oerplib.OERP.load` class method:: >>> import oerplib >>> oerp = oerplib.OERP.load('foo') Or, if you have saved your configuration in another file:: >>> oerp = oerplib.OERP.load('foo', '~/my_own_oerplibrc') You can check available sessions with :func:`oerplib.OERP.list`, and remove them with :func:`oerplib.OERP.remove`:: >>> oerplib.OERP.list() ['foo'] >>> oerplib.OERP.remove('foo') >>> 'foo' not in oerplib.OERP.list() True Change the timeout ------------------ By default, the timeout is set to 120 seconds for all RPC requests. If your requests need a higher timeout, you can set it through the :attr:`oerplib.OERP.config` property:: >>> oerp.config['timeout'] 120 >>> oerp.config['timeout'] = 300 # Set the timeout to 300 seconds python-oerplib-0.8.4/examples/000077500000000000000000000000001245703354300163225ustar00rootroot00000000000000python-oerplib-0.8.4/examples/example.py000077500000000000000000000055271245703354300203430ustar00rootroot00000000000000#!/usr/bin/env python """A sample script to demonstrate some of functionalities of OERPLib.""" import oerplib # XMLRPC server configuration (NETRPC is also supported) SERVER = 'localhost' PROTOCOL = 'xmlrpc' PORT = 8069 # Name of the OpenERP database to use DATABASE = 'db_name' USER = 'admin' PASSWORD = 'password' try: # Login oerp = oerplib.OERP( server=SERVER, database=DATABASE, protocol=PROTOCOL, port=PORT) oerp.login(USER, PASSWORD) # ----------------------- # # -- Low level methods -- # # ----------------------- # # Execute - search user_ids = oerp.execute('res.users', 'search', [('id', '=', oerp.user.id)]) # Execute - read user_data = oerp.execute('res.users', 'read', user_ids[0]) # Execute - write oerp.execute('res.users', 'write', user_ids[0], {'name': "Administrator"}) # Execute - create new_user_id = oerp.execute('res.users', 'create', {'login': "New user"}) # --------------------- # # -- Dynamic methods -- # # --------------------- # # Get the model user_obj = oerp.get('res.users') # Search IDs of a model that match criteria user_obj.search([('name', 'ilike', "Administrator")]) # Create a record new_user_id = user_obj.create({'login': "new_user"}) # Read data of a record (just the name field) user_data = user_obj.read([new_user_id], ['name']) # Write a record user_obj.write([new_user_id], {'name': "New user"}) # Delete a record user_obj.unlink([new_user_id]) # -------------------- # # -- Browse objects -- # # -------------------- # # Browse an object user = user_obj.browse(oerp.user.id) print(user.name) print(user.company_id.name) # .. or many objects order_obj = oerp.get('sale.order') for order in order_obj.browse([68, 69]): print(order.name) print(order.partner_id.name) for line in order.order_line: print('\t{0}'.format(line.name)) # ----------------------- # # -- Download a report -- # # ----------------------- # so_pdf_path = oerp.report('sale.order', 'sale.order', 1) inv_pdf_path = oerp.report('webkitaccount.invoice', 'account.invoice', 1) # ------------------------- # # -- Databases management-- # # ------------------------- # # List databases print(oerp.db.list()) # Create a database in background oerp.db.create( 'super_admin_passwd', 'my_db', demo_data=True, lang='fr_FR', admin_passwd='admin_passwd') # ... after a while, dump it my_dump = oerp.db.dump('super_admin_password', 'my_db') # Create a new database from the dump oerp.db.restore('super_admin_password', 'my_new_db', my_dump) # Delete the old one oerp.db.drop('super_admin_password', 'my_db') except oerplib.error.Error as e: print(e) except Exception as e: print(e) python-oerplib-0.8.4/oerplib/000077500000000000000000000000001245703354300161405ustar00rootroot00000000000000python-oerplib-0.8.4/oerplib/__init__.py000066400000000000000000000030511245703354300202500ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """The `oerplib` module defines the :class:`OERP` class. The :class:`OERP` class is the entry point to manage `OpenERP/Odoo` servers. You can use this one to write `Python` programs that performs a variety of automated jobs that communicate with a `OpenERP/Odoo` server. You can load a pre-configured :class:`OERP` session with the :func:`load` function. """ __author__ = 'ABF Osiell - Sebastien Alix' __email__ = 'sebastien.alix@osiell.com' __licence__ = 'LGPL v3' __version__ = '0.8.4' #__all__ = ['OERP', 'error'] from oerplib.oerp import OERP from oerplib import error # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/error.py000066400000000000000000000025501245703354300176450ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module contains all exceptions raised by `OERPLib` when an error occurred. """ class Error(Exception): """Base class for exception.""" pass class RPCError(Error): """Exception raised when an error related to a RPC query occurred.""" pass class InternalError(Error): """Exception raised when an error occurred during an internal operation.""" pass # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/oerp.py000066400000000000000000000614351245703354300174700ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module contains the ``OERP`` class which is the entry point to manage an `OpenERP/Odoo` server. """ import os import base64 import zlib import tempfile import time from oerplib import rpc, error, tools from oerplib.tools import session, v from oerplib.service import common, db, wizard, osv, inspect class OERP(object): """Return a new instance of the :class:`OERP` class. The optional `database` parameter specifies the default database to use when the :func:`login ` method is called. If no `database` is set, the `database` parameter of the :func:`login ` method will be mandatory. `XML-RPC` and `Net-RPC` protocols are supported. Respective values for the `protocol` parameter are ``xmlrpc``, ``xmlrpc+ssl`` and ``netrpc``. >>> import oerplib >>> oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) Since the version `0.7`, `OERPLib` will try by default to detect the server version in order to adapt its requests. However, it is possible to force the version to use with the `version` parameter: >>> oerp = oerplib.OERP('localhost', version='6.0') :raise: :class:`oerplib.error.InternalError`, :class:`oerplib.error.RPCError` """ def __init__(self, server='localhost', database=None, protocol='xmlrpc', port=8069, timeout=120, version=None): if protocol not in ['xmlrpc', 'xmlrpc+ssl', 'netrpc']: txt = ("The protocol '{0}' is not supported by the OERP class. " "Please choose a protocol among these ones: {1}") txt = txt.format(protocol, ['xmlrpc', 'xmlrpc+ssl', 'netrpc']) raise error.InternalError(txt) self._server = server self._port = port self._protocol = protocol self._database = self._database_default = database self._uid = None self._password = None self._user = None self._common = common.Common(self) self._db = db.DB(self) self._wizard = wizard.Wizard(self) self._inspect = inspect.Inspect(self) # Instanciate the server connector try: self._connector = rpc.PROTOCOLS[protocol]( self._server, self._port, timeout, version) except rpc.error.ConnectorError as exc: raise error.InternalError(exc.message) # Dictionary of configuration options self._config = tools.Config( self, {'auto_context': True, 'timeout': timeout}) @property def config(self): """Dictionary of available configuration options. >>> oerp.config {'auto_context': True, 'timeout': 120} - ``auto_context``: if set to `True`, the user context will be sent automatically to every call of a :class:`model ` method (default: `True`): .. versionadded:: 0.7 .. note:: This option only works on servers in version `6.1` and above. >>> product_osv = oerp.get('product.product') >>> product_osv.name_get([3]) # Context sent by default ('lang': 'fr_FR' here) [[3, '[PC1] PC Basic']] >>> oerp.config['auto_context'] = False >>> product_osv.name_get([3]) # No context sent [[3, '[PC1] Basic PC']] - ``timeout``: set the maximum timeout in seconds for a RPC request (default: `120`): .. versionadded:: 0.6 >>> oerp.config['timeout'] = 300 """ return self._config # Readonly properties @property def user(self): """The browsable record of the user connected. >>> oerp.login('admin', 'admin') == oerp.user True """ return self._user @property def context(self): """The context of the user connected. >>> oerp.login('admin', 'admin') browse_record('res.users', 1) >>> oerp.context {'lang': 'fr_FR', 'tz': False} >>> oerp.context['lang'] = 'en_US' """ return self._context @property def version(self): """The version of the server. >>> oerp.version '7.0-20131014-231047' """ return self._connector.version server = property(lambda self: self._server, doc="The server name.") port = property(lambda self: self._port, doc="The port used.") protocol = property(lambda self: self._protocol, doc="The protocol used.") database = property(lambda self: self._database, doc="The database currently used.") common = property(lambda self: self._common, doc=(""".. versionadded:: 0.6 The common service (``/common`` RPC service). See the :class:`oerplib.service.common.Common` class.""")) db = property(lambda self: self._db, doc=(""".. versionadded:: 0.4 The database management service (``/db`` RPC service). See the :class:`oerplib.service.db.DB` class.""")) wizard = property(lambda self: self._wizard, doc=(""".. versionadded:: 0.6 The wizard service (``/wizard`` RPC service). See the :class:`oerplib.service.wizard.Wizard` class.""")) inspect = property(lambda self: self._inspect, doc=(""".. versionadded:: 0.8 The inspect service (custom service). See the :class:`oerplib.service.inspect.Inspect` class.""")) #NOTE: in the past this function was implemented as a decorator for other # methods needed to be checked, but Sphinx documentation generator is not # able to auto-document decorated methods. def _check_logged_user(self): """Check if a user is logged. Otherwise, an error is raised.""" if not self._uid or not self._password: raise error.Error(u"User login required.") def login(self, user='admin', passwd='admin', database=None): """Log in as the given `user` with the password `passwd` on the database `database` and return the corresponding user as a browsable record (from the ``res.users`` model). If `database` is not specified, the default one will be used instead. >>> user = oerp.login('admin', 'admin', database='db_name') >>> user.name u'Administrator' :return: the user connected as a browsable record :raise: :class:`oerplib.error.RPCError`, :class:`oerplib.error.Error` """ # Raise an error if no database was given self._database = database or self._database_default if not self._database: raise error.Error("No database specified") # Get the user's ID and generate the corresponding User record try: user_id = self.common.login(self._database, user, passwd) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) else: if user_id: self._uid = user_id self._password = passwd self._context = self.execute('res.users', 'context_get') self._user = self.browse('res.users', user_id, self._context) return self._user else: #FIXME: Raise an error? raise error.RPCError("Wrong login ID or password") # ------------------------- # # -- Raw XML-RPC methods -- # # ------------------------- # def execute(self, model, method, *args): """Execute the `method` of `model`. `*args` parameters varies according to the `method` used. >>> oerp.execute('res.partner', 'read', [1, 2], ['name']) [{'name': u'ASUStek', 'id': 2}, {'name': u'Your Company', 'id': 1}] :return: the result returned by the `method` called :raise: :class:`oerplib.error.RPCError` """ self._check_logged_user() # Execute the query try: return self._connector.object.execute( self._database, self._uid, self._password, model, method, *args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) def execute_kw(self, model, method, args=None, kwargs=None): """Execute the `method` of `model`. `args` is a list of parameters (in the right order), and `kwargs` a dictionary (named parameters). Both varies according to the `method` used. >>> oerp.execute_kw('res.partner', 'read', [[1, 2]], {'fields': ['name']}) [{'name': u'ASUStek', 'id': 2}, {'name': u'Your Company', 'id': 1}] .. warning:: This method only works on servers in version `6.1` and above. :return: the result returned by the `method` called :raise: :class:`oerplib.error.RPCError` """ self._check_logged_user() # Execute the query args = args or [] kwargs = kwargs or {} try: return self._connector.object.execute_kw( self._database, self._uid, self._password, model, method, args, kwargs) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) def exec_workflow(self, model, signal, obj_id): """Execute the workflow `signal` on the instance having the ID `obj_id` of `model`. :raise: :class:`oerplib.error.RPCError` """ #TODO NEED TEST self._check_logged_user() # Execute the workflow query try: self._connector.object.exec_workflow( self._database, self._uid, self._password, model, signal, obj_id) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) def report(self, report_name, model, obj_ids, report_type='pdf', context=None): """Download a report from the server and return the local path of the file. >>> oerp.report('sale.order', 'sale.order', 1) '/tmp/oerplib_uJ8Iho.pdf' >>> oerp.report('sale.order', 'sale.order', [1, 2]) '/tmp/oerplib_giZS0v.pdf' :return: the path to the generated temporary file :raise: :class:`oerplib.error.RPCError` """ #TODO report_type: what it means exactly? self._check_logged_user() # If no context was supplied, get the default one if context is None: context = self.context # Execute the report query try: if v(self.version) < v('6.1'): pdf_data = self._get_report_data_v5( report_name, model, obj_ids, report_type, context) else: pdf_data = self._get_report_data_v61( report_name, model, obj_ids, report_type, context) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return self._print_file_data(pdf_data) def _get_report_data_v61(self, report_name, model, obj_ids, report_type='pdf', context=None): """Download binary data of a report from the server. It uses the `render_report` RPC method available since OpenERP 6.1. """ if context is None: context = {} obj_ids = [obj_ids] if isinstance(obj_ids, (int, long)) else obj_ids data = { 'model': model, 'id': obj_ids[0], 'ids': obj_ids, 'report_type': report_type, } try: return self._connector.report.render_report( self._database, self.user.id, self._password, report_name, obj_ids, data, context) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) def _get_report_data_v5(self, report_name, model, obj_ids, report_type='pdf', context=None): """Download binary data of a report from the server.""" context = context or {} obj_ids = [obj_ids] if isinstance(obj_ids, (int, long)) else obj_ids data = { 'model': model, 'id': obj_ids[0], 'ids': obj_ids, 'report_type': report_type, } try: report_id = self._connector.report.report( self._database, self.user.id, self._password, report_name, obj_ids, data, context) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) state = False attempt = 0 while not state: try: pdf_data = self._connector.report.report_get( self._database, self.user.id, self._password, report_id) except rpc.error.ConnectorError as exc: raise error.RPCError("Unknown error occurred during the " "download of the report.") state = pdf_data['state'] if not state: time.sleep(1) attempt += 1 if attempt > 200: raise error.RPCError("Download time exceeded, " "the operation has been canceled.") return pdf_data @staticmethod def _print_file_data(data): """Print data in a temporary file and return the path of this one.""" if 'result' not in data: raise error.InternalError( "Invalid data, the operation has been canceled.") content = base64.decodestring(data['result']) if data.get('code') == 'zlib': content = zlib.decompress(content) if data['format'] in ['pdf', 'html', 'doc', 'xls', 'sxw', 'odt', 'tiff']: if data['format'] == 'html' and os.name == 'nt': data['format'] = 'doc' (file_no, file_path) = tempfile.mkstemp('.' + data['format'], 'oerplib_') with file(file_path, 'wb+') as fp: fp.write(content) os.close(file_no) return file_path # ------------------------- # # -- High Level methods -- # # ------------------------- # def browse(self, model, ids, context=None): """Browse one or several records (if `ids` is a list of IDs) from `model`. The fields and values for such objects are generated dynamically. >>> oerp.browse('res.partner', 1) browse_record(res.partner, 1) >>> [partner.name for partner in oerp.browse('res.partner', [1, 2])] [u'Your Company', u'ASUStek'] A list of data types used by ``browse_record`` fields are available :ref:`here `. :return: a ``browse_record`` instance :return: a generator to iterate on ``browse_record`` instances :raise: :class:`oerplib.error.RPCError` """ return self.get(model).browse(ids, context) def search(self, model, args=None, offset=0, limit=None, order=None, context=None, count=False): """Return a list of IDs of records matching the given criteria in `args` parameter. `args` must be of the form ``[('name', '=', 'John'), (...)]`` >>> oerp.search('res.partner', [('name', 'like', 'Agrolait')]) [3] :return: a list of IDs :raise: :class:`oerplib.error.RPCError` """ if args is None: args = [] context = context or self._context return self.execute(model, 'search', args, offset, limit, order, context, count) def create(self, model, vals, context=None): """Create a new `model` record with values contained in the `vals` dictionary. >>> partner_id = oerp.create('res.partner', {'name': 'Jacky Bob', 'lang': 'fr_FR'}) :return: the ID of the new record. :raise: :class:`oerplib.error.RPCError` """ context = context or self._context return self.execute(model, 'create', vals, context) def read(self, model, ids, fields=None, context=None): """Return `fields` values for each `model` record identified by `ids`. If `fields` is not specified, all fields values will be retrieved. >>> oerp.read('res.partner', [1, 2], ['name']) [{'name': u'ASUStek', 'id': 2}, {'name': u'Your Company', 'id': 1}] :return: list of dictionaries :raise: :class:`oerplib.error.RPCError` """ if fields is None: fields = [] context = context or self._context return self.execute(model, 'read', ids, fields, context) def write(self, model, ids, vals=None, context=None): """Update `model` records identified by `ids` with the given values contained in the `vals` dictionary. >>> oerp.write('res.users', [1], {'name': "Administrator"}) True :return: `True` :raise: :class:`oerplib.error.RPCError` """ #if ids is None: # ids = [] if vals is None: vals = {} context = context or self._context return self.execute(model, 'write', ids, vals, context) def unlink(self, model, ids, context=None): """Delete `model` records identified by `ids`. >>> oerp.unlink('res.partner', [1]) :return: `True` :raise: :class:`oerplib.error.RPCError` """ #if ids is None: # ids = [] context = context or self._context return self.execute(model, 'unlink', ids, context) # ---------------------- # # -- Special methods -- # # ---------------------- # def write_record(self, browse_record, context=None): """.. versionadded:: 0.4 Update the record corresponding to `browse_record` by sending its values to the server (only field values which have been changed). >>> partner = oerp.browse('res.partner', 1) >>> partner.name = "Test" >>> oerp.write_record(partner) # write('res.partner', [1], {'name': "Test"}) :return: `True` :raise: :class:`oerplib.error.RPCError` """ if not isinstance(browse_record, osv.BrowseRecord): raise ValueError("An instance of BrowseRecord is required") return osv.Model(self, browse_record.__osv__['name'])._write_record( browse_record, context) def unlink_record(self, browse_record, context=None): """.. versionadded:: 0.4 Delete the record corresponding to `browse_record` from the server. >>> partner = oerp.browse('res.partner', 1) >>> oerp.unlink_record(partner) # unlink('res.partner', [1]) :return: `True` :raise: :class:`oerplib.error.RPCError` """ if not isinstance(browse_record, osv.BrowseRecord): raise ValueError("An instance of BrowseRecord is required") return osv.Model(self, browse_record.__osv__['name'])._unlink_record( browse_record, context) def refresh(self, browse_record, context=None): """Restore original values on `browse_record` with data fetched on the server. As a result, all changes made locally on the record are canceled. :raise: :class:`oerplib.error.RPCError` """ return osv.Model(self, browse_record.__osv__['name'])._refresh( browse_record, context) def reset(self, browse_record): """Cancel all changes made locally on the `browse_record`. No request to the server is executed to perform this operation. Therefore, values restored may be outdated. """ return osv.Model(self, browse_record.__osv__['name'])._reset( browse_record) @staticmethod def get_osv_name(browse_record): """ .. deprecated:: 0.7 use the ``__osv__`` attribute instead (see :class:`BrowseRecord `). >>> partner = oerp.browse('res.partner', 1) >>> oerp.get_osv_name(partner) 'res.partner' :return: the model name of the browsable record """ if not isinstance(browse_record, osv.BrowseRecord): raise ValueError("Value is not a browse_record.") return browse_record.__osv__['name'] def get(self, model): """.. versionadded:: 0.5 Return a proxy of the `model` built from the server (see :class:`oerplib.service.osv.Model`). :return: an instance of :class:`oerplib.service.osv.Model` """ return osv.Model(self, model) def save(self, name, rc_file='~/.oerplibrc'): """.. versionadded:: 0.8 Save the session configuration under the name `name`. These informations are stored in the ``~/.oerplibrc`` file by default. >>> import oerplib >>> oerp = oerplib.OERP('localhost', protocol='xmlrpc', port=8069) >>> oerp.login('admin', 'admin', 'db_name') >>> oerp.save('foo') Such informations can be loaded with the :func:`oerplib.load` function by returning a pre-configured session of :class:`OERP `, or with the `oerp` command line tool supplied with `oerplib`. """ self._check_logged_user() data = { 'type': self.__class__.__name__, 'server': self.server, 'protocol': self.protocol, 'port': self.port, 'timeout': self.config['timeout'], 'user': self.user.login, 'passwd': self._password, 'database': self.database, } session.save(name, data, rc_file) @classmethod def load(cls, name, rc_file='~/.oerplibrc'): """.. versionadded:: 0.8 Return a :class:`OERP` session pre-configured and connected with informations identified by `name`: >>> import oerplib >>> oerp = oerplib.OERP.load('foo') Such informations are stored with the :func:`OERP.save ` method. """ data = session.get(name, rc_file) if data.get('type') != cls.__name__: raise error.Error( "'{0}' session is not of type '{1}'".format( name, cls.__name__)) oerp = cls( server=data['server'], protocol=data['protocol'], port=data['port'], timeout=data['timeout'], ) oerp.login( user=data['user'], passwd=data['passwd'], database=data['database']) return oerp @classmethod def list(cls, rc_file='~/.oerplibrc'): """.. versionadded:: 0.8 Return a list of all sessions available in the `rc_file` file: >>> import oerplib >>> oerplib.OERP.list() ['foo', 'bar'] Then, use the :func:`load` function with the desired session: >>> oerp = oerplib.OERP.load('foo') """ sessions = session.get_all(rc_file) return [name for name, data in sessions.iteritems() if data.get('type') == cls.__name__] #return session.list(rc_file) @classmethod def remove(cls, name, rc_file='~/.oerplibrc'): """.. versionadded:: 0.8 Remove the session identified by `name` from the `rc_file` file: >>> import oerplib >>> oerplib.OERP.remove('foo') True """ data = session.get(name, rc_file) if data.get('type') != cls.__name__: raise error.Error( "'{0}' session is not of type '{1}'".format( name, cls.__name__)) return session.remove(name, rc_file) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/rpc/000077500000000000000000000000001245703354300167245ustar00rootroot00000000000000python-oerplib-0.8.4/oerplib/rpc/__init__.py000066400000000000000000000250421245703354300210400ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module provides `RPC` connectors which use the `XML-RPC`, `Net-RPC` or `JSON-RPC` protocol to communicate with an `OpenERP/Odoo` server. Afterwards, `RPC` services and their associated methods can be accessed dynamically from the connector returned. `XML-RPC` and `Net-RPC` provide the same interface, such as services like ``db``, ``common`` or ``object``. On the other hand, `JSON-RPC` provides a completely different interface, with services provided by Web modules like ``web/session``, ``web/dataset`` and so on. """ from oerplib.rpc import error, service, jsonrpclib from oerplib.tools import v # XML-RPC available URL # '/xmlrpc' => 5.0, 6.0, 6.1, 7.0, 8.0 (legacy path) # '/openerp/xmlrpc/1' => 6.1, 7.0 # '/xmlrpc/2' => 8.0 XML_RPC_PATHS = ['/xmlrpc', '/openerp/xmlrpc/1', '/xmlrpc/2'] class Connector(object): """Connector base class defining the interface used to interact with a server. """ def __init__(self, server, port=8069, timeout=120, version=None): self.server = server try: int(port) except ValueError: txt = "The port '{0}' is invalid. An integer is required." txt = txt.format(port) raise error.ConnectorError(txt) else: self.port = int(port) self._timeout = timeout self.version = version self._url = None @property def timeout(self): return self._timeout @timeout.setter def timeout(self, timeout): self._timeout = timeout class ConnectorXMLRPC(Connector): """Connector class using the `XML-RPC` protocol. >>> from oerplib import rpc >>> cnt = rpc.ConnectorXMLRPC('localhost', port=8069) Login and retrieve ID of the user connected: >>> uid = cnt.common.login('database', 'user', 'passwd') Execute a query: >>> res = cnt.object.execute('database', uid, 'passwd', 'res.partner', 'read', [1]) Execute a workflow query: >>> res = cnt.object.exec_workflow('database', uid, 'passwd', 'sale.order', 'order_confirm', 4) """ def __init__(self, server, port=8069, timeout=120, version=None, scheme='http'): super(ConnectorXMLRPC, self).__init__(server, port, timeout, version) self.scheme = scheme if self.version: # Server < 6.1 if v(self.version) < v('6.1'): self._url = '{scheme}://{server}:{port}/xmlrpc'.format( scheme=self.scheme, server=self.server, port=self.port) # Server >= 6.1 and < 8.0 elif v(self.version) < v('8.0'): self._url = '{scheme}://{server}:{port}/openerp/xmlrpc/1'.format( scheme=self.scheme, server=self.server, port=self.port) # Server >= 8.0 elif v(self.version) >= v('8.0'): self._url = '{scheme}://{server}:{port}/xmlrpc/2'.format( scheme=self.scheme, server=self.server, port=self.port) # Detect the XML-RPC path to use if self._url is None: # We begin with the last known XML-RPC path to give the priority to # the last server version supported paths = XML_RPC_PATHS[:] paths.reverse() for path in paths: url = '{scheme}://{server}:{port}{path}'.format( scheme=self.scheme, server=self.server, port=self.port, path=path) try: db = service.ServiceXMLRPC( self, 'db', '{url}/{srv}'.format(url=url, srv='db')) version = db.server_version() except error.ConnectorError: continue else: self._url = url self.version = version break def __getattr__(self, service_name): url = self._url + '/' + service_name srv = service.ServiceXMLRPC(self, service_name, url) setattr(self, service_name, srv) return srv class ConnectorXMLRPCSSL(ConnectorXMLRPC): """Connector class using the `XML-RPC` protocol over `SSL`.""" def __init__(self, server, port=8069, timeout=120, version=None): super(ConnectorXMLRPCSSL, self).__init__( server, port, timeout, version, scheme='https') class ConnectorNetRPC(Connector): """ .. note:: No longer available since `OpenERP 7.0`. Connector class using the `Net-RPC` protocol. """ def __init__(self, server, port=8070, timeout=120, version=None): super(ConnectorNetRPC, self).__init__( server, port, timeout, version) if self.version is None: try: db = service.ServiceNetRPC(self, 'db', self.server, self.port) version = db.server_version() except error.ConnectorError: pass else: self.version = version def __getattr__(self, service_name): srv = service.ServiceNetRPC( self, service_name, self.server, self.port) setattr(self, service_name, srv) return srv class ConnectorJSONRPC(Connector): """Connector class using the `JSON-RPC` protocol. >>> from oerplib import rpc >>> cnt = rpc.ConnectorJSONRPC('localhost', port=8069) Open a user session: >>> cnt.proxy.web.session.authenticate(db='database', login='admin', password='admin') {u'jsonrpc': u'2.0', u'id': 202516757, u'result': {u'username': u'admin', u'user_context': {u'lang': u'fr_FR', u'tz': u'Europe/Brussels', u'uid': 1}, u'db': u'test70', u'uid': 1, u'session_id': u'308816f081394a9c803613895b988540'}} Read data of a partner: >>> cnt.proxy.web.dataset.call(model='res.partner', method='read', args=[[1]]) {u'jsonrpc': u'2.0', u'id': 454236230, u'result': [{u'id': 1, u'comment': False, u'ean13': False, u'property_account_position': False, ...}]} You can send requests this way too: >>> cnt.proxy['/web/dataset'].call(model='res.partner', method='read', args=[[1]]) {u'jsonrpc': u'2.0', u'id': 328686288, u'result': [{u'id': 1, u'comment': False, u'ean13': False, u'property_account_position': False, ...}]} Or like this: >>> cnt.proxy['web']['dataset'].call(model='res.partner', method='read', args=[[1]]) {u'jsonrpc': u'2.0', u'id': 102320639, u'result': [{u'id': 1, u'comment': False, u'ean13': False, u'property_account_position': False, ...}]} """ def __init__(self, server, port=8069, timeout=120, version=None, deserialize=True): super(ConnectorJSONRPC, self).__init__(server, port, timeout, version) self.deserialize = deserialize self._proxy = self._get_proxy(ssl=False) def _get_proxy(self, ssl=False): """Returns a :class:`Proxy ` instance corresponding to the server version used. """ # Detect the server version if self.version is None: proxy = jsonrpclib.Proxy( self.server, self.port, self._timeout, ssl=ssl, deserialize=self.deserialize) result = proxy.web.webclient.version_info()['result'] # Server 6.1 if 'version' in result: self.version = result['version'] # Server >= 7.0 elif 'server_version' in result: self.version = result['server_version'] # Select the legacy proxy for OpenERP 6.1 and 7.0 if v(self.version)[:2] <= v('7.0'): proxy = jsonrpclib.ProxyLegacy( self.server, self.port, self._timeout, ssl=ssl, deserialize=self.deserialize) return proxy @property def proxy(self): return self._proxy @property def timeout(self): return self._proxy._timeout @timeout.setter def timeout(self, timeout): self._proxy._timeout = timeout class ConnectorJSONRPCSSL(ConnectorJSONRPC): """Connector class using the `JSON-RPC` protocol over `SSL`. >>> from oerplib import rpc >>> cnt = rpc.ConnectorJSONRPCSSL('localhost', port=8069) """ def __init__(self, server, port=8069, timeout=120, version=None, deserialize=True): super(ConnectorJSONRPCSSL, self).__init__( server, port, timeout, version) self._proxy = self._get_proxy(ssl=True) PROTOCOLS = { 'xmlrpc': ConnectorXMLRPC, 'xmlrpc+ssl': ConnectorXMLRPCSSL, #'jsonrpc': ConnectorJSONRPC, #'jsonrpc+ssl': ConnectorJSONRPCSSL, 'netrpc': ConnectorNetRPC, } def get_connector(server, port=8069, protocol='xmlrpc', timeout=120, version=None): """ .. deprecated:: 0.8 Return a `RPC` connector to interact with an `OpenERP/Odoo` server. Supported protocols are: - ``xmlrpc``: Standard `XML-RPC` protocol (default), - ``xmlrpc+ssl``: `XML-RPC` protocol over `SSL`, - ``netrpc``: `Net-RPC` protocol (no longer available since `OpenERP 7.0`). If the `version` parameter is set to `None`, the last API supported will be used to send requests to the server. Otherwise, you can force the API to use with the corresponding string version (e.g.: ``'6.0', '6.1', '7.0', '8.0', ...``): >>> from oerplib import rpc >>> cnt = rpc.get_connector('localhost', 8069, 'xmlrpc', version='7.0') """ if protocol not in PROTOCOLS: txt = ("The protocol '{0}' is not supported. " "Please choose a protocol among these ones: {1}") txt = txt.format(protocol, PROTOCOLS.keys()) raise error.ConnectorError(txt) return PROTOCOLS[protocol](server, port, timeout, version) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/rpc/error.py000066400000000000000000000023131245703354300204260ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## class ConnectorError(BaseException): """Exception raised by the ``oerplib.rpc`` package.""" def __init__(self, message, oerp_traceback=None): self.message = message self.oerp_traceback = oerp_traceback # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/rpc/jsonrpclib.py000066400000000000000000000100531245703354300214420ustar00rootroot00000000000000# -*- coding: utf-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Provides the :class:`Proxy` and :class:`ProxyLegacy` classes.""" import urllib2 import cookielib import json import random class Proxy(object): """The :class:`Proxy` class provides a dynamic access to all JSON methods. """ def __init__(self, host, port, timeout=120, ssl=False, deserialize=True): self._root_url = "{http}{host}:{port}".format( http=(ssl and "https://" or "http://"), host=host, port=port) self._timeout = timeout self._deserialize = deserialize self._builder = URLBuilder(self) cookie_jar = cookielib.CookieJar() self._opener = urllib2.build_opener( urllib2.HTTPCookieProcessor(cookie_jar)) def __getattr__(self, name): return getattr(self._builder, name) def __getitem__(self, url): return self._builder[url] def __call__(self, url, params): data = json.dumps({ "jsonrpc": "2.0", "method": "call", "params": params, "id": random.randint(0, 1000000000), }) request = urllib2.Request(url='/'.join([self._root_url, url])) request.add_header('Content-Type', 'application/json') request.add_data(data) response = self._opener.open(request, timeout=self._timeout) if not self._deserialize: return response return json.load(response) class ProxyLegacy(Proxy): """The :class:`ProxyLegacy` class fixes the request handling for OpenERP 6.1 and 7.0 by adding automatically the ``session_id`` parameter once the user is authenticated. """ def __init__(self, host, port, timeout=120, ssl=False, deserialize=True): super(ProxyLegacy, self).__init__(host, port, timeout, ssl, deserialize) self._session_id = None def __call__(self, url, params): """Overloads the :func:`Proxy.__call__` method to add the 'session_id' parameter if necessary. """ if url == 'web/session/authenticate': response = super(ProxyLegacy, self).__call__(url, params) result = self._deserialize and response or json.load(response) self._session_id = result and result['result']['session_id'] return response elif self._session_id and 'session_id' not in params: params['session_id'] = self._session_id return super(ProxyLegacy, self).__call__(url, params) class URLBuilder(object): """Auto-builds an URL while getting its attributes. Used by :class:`Proxy` and :class:`ProxyLegacy` classes. """ def __init__(self, rpc, url=None): self._rpc = rpc self._url = url def __getattr__(self, path): new_url = self._url and '/'.join([self._url, path]) or path return URLBuilder(self._rpc, new_url) def __getitem__(self, path): if path and path[0] == '/': path = path[1:] if path and path[-1] == '/': path = path[:-1] return getattr(self, path) def __call__(self, **kwargs): return self._rpc(self._url, kwargs) def __str__(self): return self._url # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/rpc/netrpclib.py000066400000000000000000000065411245703354300212660ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module contains the NetRPC class which implements the NetRPC protocol. """ import socket import pickle import cStringIO class NetRPCError(BaseException): """Exception raised by the NetRPC class when an error occured.""" def __init__(self, faultCode, faultString): self.faultCode = faultCode self.faultString = faultString self.args = (faultCode, faultString) class NetRPC(object): """Low level class for NetRPC protocol.""" def __init__(self, sock=None, timeout=120): if sock is None: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) else: self.sock = sock self.sock.settimeout(timeout) def connect(self, host, port=False): if not port: buf = host.split('//')[1] host, port = buf.split(':') self.sock.connect((host, int(port))) def disconnect(self): self.sock.shutdown(socket.SHUT_RDWR) self.sock.close() def send(self, msg, exception=False, traceback=None): msg = pickle.dumps([msg, traceback]) size = len(msg) self.sock.send('%8d' % size) self.sock.send(exception and "1" or "0") totalsent = 0 while totalsent < size: sent = self.sock.send(msg[totalsent:]) if sent == 0: raise NetRPCError("RuntimeError", "Socket connection broken") totalsent = totalsent + sent def receive(self): buf = '' while len(buf) < 8: chunk = self.sock.recv(8 - len(buf)) if chunk == '': raise NetRPCError("RuntimeError", "Socket connection broken") buf += chunk size = int(buf) buf = self.sock.recv(1) if buf != "0": exception = buf else: exception = False msg = '' while len(msg) < size: chunk = self.sock.recv(size - len(msg)) if chunk == '': raise NetRPCError("RuntimeError", "Socket connection broken") msg = msg + chunk msgio = cStringIO.StringIO(msg) unpickler = pickle.Unpickler(msgio) unpickler.find_global = None res = unpickler.load() if isinstance(res[0], BaseException): if exception: raise NetRPCError(res[0], res[1]) raise res[0] else: return res[0] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/rpc/service.py000066400000000000000000000064721245703354300207470ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## import xmlrpclib from oerplib.rpc import netrpclib, xmlrpclib_custom, error class ServiceXMLRPC(object): def __init__(self, connector, name, url): self._connector = connector self._name = name self._url = url def __getattr__(self, method): def rpc_method(*args): try: self._sock = xmlrpclib_custom.TimeoutServerProxy( self._url, allow_none=True, timeout=self._connector.timeout) sock_method = getattr(self._sock, method, False) return sock_method(*args) #NOTE: exception raised with these kind of requests: # - execute('fake.model', 'search', []) # - execute('sale.order', 'fake_method') except xmlrpclib.Fault as exc: # faultCode: error message # faultString: Server traceback (following the server version # used, a bad request can produce a server traceback, or not). raise error.ConnectorError(exc.faultCode, exc.faultString) #TODO NEED TEST (when is raised this exception?) except xmlrpclib.Error as exc: raise error.ConnectorError(' - '.join(exc.args)) return rpc_method class ServiceNetRPC(object): def __init__(self, connector, name, server, port): self._connector = connector self._name = name self._server = server self._port = port def __getattr__(self, method): def rpc_method(*args): try: sock = netrpclib.NetRPC(timeout=self._connector.timeout) sock.connect(self._server, self._port) sock.send((self._name, method, ) + args) result = sock.receive() sock.disconnect() return result #NOTE: exception raised with these kind of requests: # - execute('fake.model', 'search', []) # - execute('sale.order', 'fake_method') except netrpclib.NetRPCError as exc: # faultCode: error message # faultString: Server traceback (following the server version # used, a bad request can produce a server traceback, or not). raise error.ConnectorError(exc.faultCode, exc.faultString) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/rpc/xmlrpclib_custom.py000066400000000000000000000135171245703354300226730ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2012-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## import xmlrpclib import httplib import socket import sys from urlparse import urlparse # Defined later following the version of Python used TimeoutTransport = None TimeoutSafeTransport = None class TimeoutServerProxy(xmlrpclib.ServerProxy): """xmlrpclib.ServerProxy overload to manage the timeout of the socket.""" def __init__(self, *args, **kwargs): url = args[0] https_ok = urlparse(url).scheme == 'https' t = https_ok and TimeoutSafeTransport() or TimeoutTransport() t.timeout = kwargs.get('timeout', 120) if 'timeout' in kwargs: del kwargs['timeout'] kwargs['transport'] = t xmlrpclib.ServerProxy.__init__(self, *args, **kwargs) if sys.version_info <= (2, 7): # Python 2.5 and 2.6 # -- xmlrpclib.Transport with timeout support -- class TimeoutHTTPPy26(httplib.HTTP): def __init__(self, host='', port=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): if port == 0: port = None self._setup(self._connection_class(host, port, strict, timeout)) class TimeoutTransportPy26(xmlrpclib.Transport): def __init__(self, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *args, **kwargs): xmlrpclib.Transport.__init__(self, *args, **kwargs) self.timeout = timeout def make_connection(self, host): host, extra_headers, x509 = self.get_host_info(host) conn = TimeoutHTTPPy26(host, timeout=self.timeout) return conn # -- xmlrpclib.SafeTransport with timeout support -- class TimeoutHTTPSPy26(httplib.HTTPS): def __init__(self, host='', port=None, key_file=None, cert_file=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): if port == 0: port = None self._setup(self._connection_class( host, port, key_file, cert_file, strict, timeout)) self.key_file = key_file self.cert_file = cert_file class TimeoutSafeTransportPy26(xmlrpclib.SafeTransport): def __init__(self, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *args, **kwargs): xmlrpclib.Transport.__init__(self, *args, **kwargs) self.timeout = timeout def make_connection(self, host): host, extra_headers, x509 = self.get_host_info(host) conn = TimeoutHTTPSPy26(host, timeout=self.timeout) return conn # Define the TimeTransport and TimeSafeTransport class version to use TimeoutTransport = TimeoutTransportPy26 TimeoutSafeTransport = TimeoutSafeTransportPy26 else: # Python 2.7 and 3.X # -- xmlrpclib.Transport with timeout support -- class TimeoutHTTPConnectionPy27(httplib.HTTPConnection): def __init__(self, timeout, *args, **kwargs): httplib.HTTPConnection.__init__(self, *args, **kwargs) self.timeout = timeout def connect(self): httplib.HTTPConnection.connect(self) self.sock.settimeout(self.timeout) class TimeoutTransportPy27(xmlrpclib.Transport): def __init__(self, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *args, **kwargs): xmlrpclib.Transport.__init__(self, *args, **kwargs) self.timeout = timeout def make_connection(self, host): if self._connection and host == self._connection[0]: return self._connection[1] chost, self._extra_headers, x509 = self.get_host_info(host) self._connection = host, TimeoutHTTPConnectionPy27( self.timeout, chost) return self._connection[1] # -- xmlrpclib.SafeTransport with timeout support -- class TimeoutHTTPSConnectionPy27(httplib.HTTPSConnection): def __init__(self, timeout, *args, **kwargs): httplib.HTTPSConnection.__init__(self, *args, **kwargs) self.timeout = timeout def connect(self): httplib.HTTPSConnection.connect(self) self.sock.settimeout(self.timeout) class TimeoutSafeTransportPy27(xmlrpclib.SafeTransport): def __init__(self, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *args, **kwargs): xmlrpclib.SafeTransport.__init__(self, *args, **kwargs) self.timeout = timeout def make_connection(self, host): if self._connection and host == self._connection[0]: return self._connection[1] chost, self._extra_headers, x509 = self.get_host_info(host) self._connection = host, TimeoutHTTPSConnectionPy27( self.timeout, chost) return self._connection[1] # Define the TimeTransport and TimeSafeTransport class version to use TimeoutTransport = TimeoutTransportPy27 TimeoutSafeTransport = TimeoutSafeTransportPy27 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/000077500000000000000000000000001245703354300176005ustar00rootroot00000000000000python-oerplib-0.8.4/oerplib/service/__init__.py000066400000000000000000000023411245703354300217110ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """The `service` package provides classes to request services offers by the `OpenERP/Odoo` server, such as: - '/common' (service.common), - '/db' (service.db) - '/wizard' (service.wizard) - Model, through '/object' (service.osv), """ # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/common.py000066400000000000000000000123111245703354300214400ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Provide the :class:`Common` class to manage the /common RPC service.""" from oerplib import rpc, error class Common(object): """.. versionadded:: 0.6 The `Common` class represents the ``/common`` RPC service which lets you log in on the server, and provides various utility functions. .. note:: This service have to be used through the :attr:`oerplib.OERP.common` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> oerp.common .. warning:: All methods documented below are not strictly implemented in `OERPLib` Method calls are purely dynamic, and the following documentation can be wrong if the API of the server is changed between versions. Anyway, if you know the API proposed by the server for the ``/common`` RPC service, it will work. .. method:: Common.login(db, login, password) >>> oerp.common.login('test_db', 'admin', 'admin_passwd') 1 :return: the user's ID or `False` .. method:: Common.authenticate(db, login, password, user_agent_env) >>> oerp.common.authenticate('test_db', 'admin', 'admin_passwd', {}) 1 :return: the user's ID or `False` .. method:: Common.version() >>> oerp.common.version() {'protocol_version': 1, 'server_version': '6.1'} .. method:: Common.about(extended=False) Return information about the server. >>> oerp.common.about() 'See http://openerp.com' >>> oerp.common.about(True) ['See http://openerp.com', '8.0alpha1'] :param: extended: if `True` then return version info :return: string if extended is `False` else tuple .. method:: Common.timezone_get(db, login, password) >>> oerp.common.timezone_get('test_db', 'admin', 'admin_passwd') 'UTC' .. method:: Common.get_server_environment() >>> print(oerp.common.get_server_environment()) Environment Information : System : Linux-2.6.32-5-686-i686-with-debian-6.0.4 OS Name : posix Distributor ID: Debian Description: Debian GNU/Linux 6.0.4 (squeeze) Release: 6.0.4 Codename: squeeze Operating System Release : 2.6.32-5-686 Operating System Version : #1 SMP Mon Mar 26 05:20:33 UTC 2012 Operating System Architecture : 32bit Operating System Locale : fr_FR.UTF8 Python Version : 2.6.6 OpenERP-Server Version : 5.0.16 Last revision No. & ID : .. method:: Common.login_message() >>> oerp.common.login_message() 'Welcome' .. method:: Common.set_loglevel(loglevel, logger=None) >>> oerp.common.set_loglevel('DEBUG') .. method:: Common.get_stats() >>> print(oerp.common.get_stats()) OpenERP server: 5 threads Servers started Net-RPC: running .. method:: Common.list_http_services() >>> oerp.common.list_http_services() [] .. method:: Common.check_connectivity() >>> oerp.common.check_connectivity() True .. method:: Common.get_os_time() >>> oerp.common.get_os_time() (0.01, 0.0, 0.0, 0.0, 17873633.129999999) .. method:: Common.get_sqlcount() >>> oerp.common.get_sqlcount() .. method:: Common.get_available_updates(super_admin_password, contract_id, contract_password) >>> oerp.common.get_available_updates('super_admin_passwd', 'MY_CONTRACT_ID', 'MY_CONTRACT_PASSWORD') .. method:: Common.get_migration_scripts(super_admin_password, contract_id, contract_password) >>> oerp.common.get_migration_scripts('super_admin_passwd', 'MY_CONTRACT_ID', 'MY_CONTRACT_PASSWORD') """ def __init__(self, oerp): self._oerp = oerp def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args): """Return the result of the RPC request.""" try: meth = getattr(self._oerp._connector.common, method, False) return meth(*args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/db.py000066400000000000000000000231511245703354300205410ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Provide the :class:`DB` class in order to manage the server databases.""" import time from oerplib import rpc, error class DB(object): """.. versionadded:: 0.4 The `DB` class represents the database management service. It provides functionalities such as list, create, drop, dump and restore databases. .. note:: This service have to be used through the :attr:`oerplib.OERP.db` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> oerp.db .. warning:: All methods documented below are not strictly implemented in `OERPLib` (except the :func:`create_and_wait ` method). Method calls are purely dynamic, and the following documentation can be wrong if the API of the server is changed between versions. Anyway, if you know the API proposed by the server for the ``/db`` RPC service, it will work. .. method:: DB.list() Return a list of the databases: >>> oerp.db.list() ['prod_db', 'test_db'] :return: a list of database names .. method:: DB.list_lang() Return a list of codes and names of language supported by the server: >>> oerp.db.list_lang() [['sq_AL', 'Albanian / Shqipëri'], ['ar_AR', 'Arabic / الْعَرَبيّة'], ...] :return: a list of pairs representing languages with their codes and names .. method:: DB.server_version() Return the version of the server: >>> oerp.db.server_version() '6.1' :return: the version of the server as string .. method:: DB.dump(super_admin_passwd, database) Return a dump of `database` in `base64`: >>> binary_data = oerp.db.dump('super_admin_passwd', 'prod_db') The super administrator password `super_admin_passwd` is required to perform this action. :return: the `base64` string representation of the `database` .. method:: DB.restore(super_admin_passwd, database, binary_data) Restore in `database` a dump previously created with the :func:`dump ` method: >>> oerp.db.restore('super_admin_passwd', 'test_db', binary_data) The super administrator password `super_admin_passwd` is required to perform this action. .. method:: DB.drop(super_admin_passwd, database) Drop the `database`: >>> oerp.db.drop('super_admin_passwd', 'test_db') True The super administrator password `super_admin_passwd` is required to perform this action. :return: `True` .. method:: DB.create(super_admin_passwd, database, demo_data=False, lang='en_US', admin_passwd='admin') Request the server to create a new database named `database` which will have `admin_passwd` as administrator password and localized with the `lang` parameter. You have to set the flag `demo_data` to `True` in order to insert demonstration data. As the creating process may take some time, you can execute the :func:`get_progress ` method with the database ID returned to know its current state. >>> database_id = oerp.db.create('super_admin_passwd', 'test_db', False, 'fr_FR', 'my_admin_passwd') The super administrator password `super_admin_passwd` is required to perform this action. :return: the ID of the new database .. method:: DB.get_progress(super_admin_passwd, database_id) Check the state of the creating process for the database identified by the `database_id` parameter. >>> oerp.db.get_progress('super_admin_passwd', database_id) # Just after the call to the 'create' method (0, []) >>> oerp.db.get_progress('super_admin_passwd', database_id) # Once the database is fully created (1.0, [{'login': 'admin', 'password': 'admin', 'name': 'Administrator'}, {'login': 'demo', 'password': 'demo', 'name': 'Demo User'}]) The super administrator password `super_admin_passwd` is required to perform this action. :return: A tuple with the progressing state and a list of user accounts created (once the database is fully created). .. method:: DB.create_database(super_admin_passwd, database, demo_data=False, lang='en_US', admin_passwd='admin') `Available since OpenERP 6.1` Similar to :func:`create ` but blocking. >>> oerp.db.create_database('super_admin_passwd', 'test_db', False, 'fr_FR', 'my_admin_passwd') True The super administrator password `super_admin_passwd` is required to perform this action. :return: `True` .. method:: DB.duplicate_database(super_admin_passwd, original_database, database) `Available since OpenERP 7.0` Duplicate `original_database' as `database`. >>> oerp.db.duplicate_database('super_admin_passwd', 'prod_db', 'test_db') True The super administrator password `super_admin_passwd` is required to perform this action. :return: `True` .. method:: DB.rename(super_admin_passwd, old_name, new_name) Rename the `old_name` database to `new_name`. >>> oerp.db.rename('super_admin_passwd', 'test_db', 'test_db2') True The super administrator password `super_admin_passwd` is required to perform this action. :return: `True` .. method:: DB.db_exist(database) Check if connection to database is possible. >>> oerp.db.db_exist('prod_db') True :return: `True` or `False` .. method:: DB.change_admin_password(super_admin_passwd, new_passwd) Change the administrator password by `new_passwd`. >>> oerp.db.change_admin_password('super_admin_passwd', 'new_passwd') True The super administrator password `super_admin_passwd` is required to perform this action. :return: `True` """ def __init__(self, oerp): self._oerp = oerp def create_and_wait(self, super_admin_passwd, database, demo_data=False, lang='en_US', admin_passwd='admin'): """ .. note:: This method is not part of the official API. It's just a wrapper around the :func:`create ` and :func:`get_progress ` methods. For server in version `6.1` or above, please prefer the use of the standard :func:`create_database ` method. Like the :func:`create ` method, but waits the end of the creating process by executing the :func:`get_progress ` method regularly to check its state. >>> oerp.db.create_and_wait('super_admin_passwd', 'test_db', False, 'fr_FR', 'my_admin_passwd') [{'login': 'admin', 'password': 'my_admin_passwd', 'name': 'Administrateur'}, {'login': 'demo', 'password': 'demo', 'name': 'Demo User'}] The super administrator password `super_admin_passwd` is required to perform this action. :return: a list of user accounts created :raise: :class:`oerplib.error.RPCError` """ try: db_id = self._oerp._connector.db.create( super_admin_passwd, database, demo_data, lang, admin_passwd) progress = 0.0 attempt = 0 while progress < 1.0: result = self._oerp._connector.db.get_progress( super_admin_passwd, db_id) progress = result[0] if progress < 1.0: time.sleep(1) attempt += 1 if attempt > 300: raise error.RPCError( "Too many attempts, the operation" " has been canceled.") return result[1] except rpc.error.ConnectorError as exc: #FIXME handle the exception with the UnicodeEncodeError for # the error 'the database already exists'. #print dir(exc) raise error.RPCError(exc.message, exc.oerp_traceback) def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args): """Return the result of the RPC request.""" try: meth = getattr(self._oerp._connector.db, method, False) return meth(*args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/inspect/000077500000000000000000000000001245703354300212455ustar00rootroot00000000000000python-oerplib-0.8.4/oerplib/service/inspect/__init__.py000066400000000000000000000251371245703354300233660ustar00rootroot00000000000000# -*- coding: utf-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Provide the :class:`Inspect` class which can output useful server data. oerplib.service.inspect.Inspect ''''''''''''''''''''''''''''''' """ from functools import wraps from oerplib import error class Inspect(object): """.. versionadded:: 0.8 The `Inspect` class provides methods to output useful server data. .. note:: This service have to be used through the :attr:`oerplib.OERP.inspect` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'passwd', 'database') >>> oerp.inspect .. automethod:: relations(models, maxdepth=1, whitelist=['*'], blacklist=[], attrs_whitelist=[], attrs_blacklist=[], config={}) Return a :class:`Relations ` object containing relations between data models, starting from `models` (depth = 0) and iterate recursively until reaching the `maxdepth` limit. `whitelist` and `blacklist` of models can be defined with patterns (a joker ``*`` can be used to match several models like ``account*``). The whitelist has a lower priority than the blacklist, and all models declared in `models` are automatically integrated to the `whitelist`. In the same way, displaying attributes can be defined for each model with ``attrs_whitelist`` and ``attrs_blacklist``. By default, model attributes are not displayed, unless the ``'*'`` pattern is supplied in ``attrs_whitelist``, or if only the ``attrs_blacklist`` is defined. >>> oerp.inspect.relations( ... ['res.users'], ... maxdepth=1, ... whitelist=['res*'], ... blacklist=['res.country*'], ... attrs_whitelist=['*'], ... attrs_blacklist=['res.partner', 'res.company'], ... ).write('res_users.png', format='png') `config` is a dictionary of options to override some attributes of the graph. Here the list of options and their default values: - ``relation_types: ['many2one', 'one2many', 'many2many']``, - ``show_many2many_table: False``, - ``color_many2one: #0E2548``, - ``color_one2many: #008200``, - ``color_many2many: #6E0004``, - ``model_root_bgcolor_title: #A50018``, - ``model_bgcolor_title: #64629C``, - ``model_color_title: white``, - ``model_color_subtitle': #3E3D60``, - ``model_bgcolor: white``, - ``color_normal: black``, - ``color_required: blue`` - ``color_function: #D9602E`` - ``space_between_models: 0.25``, >>> oerp.inspect.relations( ... ['res.users'], ... config={'relation_types': ['many2one']}, # Only show many2one relations ... ).write('res_users.png', format='png') .. note:: With `OpenERP` < `6.1`, `many2one` and `one2many` relationships can not be bound together. Hence, a `one2many` relationship based on a `many2one` will draw a separate arrow. .. automethod:: dependencies(modules=[], models=[], models_blacklist=[], restrict=False, config={}) Return a :class:`Dependencies ` object describing dependencies between modules. The `modules` defines a list of root nodes to reach among all dependencies (modules not related to them are not displayed). The default behaviour is to compute all dependencies between installed modules. The `models` list can be used to display all matching models among computed dependencies. `models` and `models_blacklist` parameters can be defined with patterns (a joker ``*`` can be used to match several models like ``account*``). The whitelist (`models`) has a lower priority than the blacklist (`models_blacklist`):: >>> oerp.inspect.dependencies( ... models=['res.partner*'], ... models_blacklist=['res.partner.title', 'res.partner.bank'], ... ).write('dependencies_res_partner.png', format='png') By default all installed modules are shown on the graph. To limit the result to modules related to the `base` one (its childs):: >>> oerp.inspect.dependencies( ... ['base'], ... ['res.partner*'], ... ['res.partner.title', 'res.partner.bank'], ... ).write('dependencies_res_partner_base.png', format='png') All modules related to `base` are shown on the resulting graph, and matching models are highlighted among them, but some modules remain empty. To hide these "noisy" modules and restrict the resulting graph to data models that interest you, add the ``restrict=True`` parameter:: >>> oerp.inspect.dependencies( ... ['base'], ... ['res.partner*'], ... ['res.partner.title', 'res.partner.bank'], ... restrict=True, ... ).write('dependencies_res_partner_base_restricted.png', format='png') In any case, root `modules` are always displayed on the graph in restricted mode (even if they have no matching model), and some unrelated modules may be added to satisfy dependencies. `config` is a dictionary of options to override some attributes of the graph. Here the list of options and their default values: - ``module_uninst_bgcolor_title: #DEDFDE``, - ``module_uninst_color_title: black``, - ``module_inst_bgcolor_title: #64629C``, - ``module_inst_color_title: white``, - ``module_root_bgcolor_title: #A50018``, - ``module_root_color_title: white``, - ``module_highlight_bgcolor_title: #1F931F``, - ``module_highlight_color_title: white``, - ``module_bgcolor: white``, - ``module_color_comment: grey``, - ``model_color_normal: black``, - ``model_color_transient: #7D7D7D``, - ``show_module_inst: True``, - ``show_module_uninst: False``, - ``show_model_normal: True``, - ``show_model_transient: False``, >>> oerp.inspect.dependencies( ... ['base'], ... ['res.partner*'], ... ['res.partner.title', 'res.partner.bank'], ... config={'show_model_transient': True}, # Show TransientModel/osv_memory models ... ).write('dependencies_res_partner_transient.png', format='png') .. note:: With `OpenERP` `5.0`, data models can not be bound to their related modules, and as such the `models` and `models_blacklist` parameters are ignored. """ def __init__(self, oerp): self._oerp = oerp def relations(self, models, maxdepth=1, whitelist=None, blacklist=None, attrs_whitelist=None, attrs_blacklist=None, config=None): from oerplib.service.inspect.relations import Relations return Relations( self._oerp, models, maxdepth, whitelist, blacklist, attrs_whitelist, attrs_blacklist, config) def scan_on_change(self, models): """Scan all `on_change` methods detected among views of `models`, and returns a dictionary formatted as ``{model: {on_change: {view_id: field: [args]}}}`` >>> oerp.inspect.scan_on_change(['sale.order']) {'sale.order': { 'onchange_partner_id': { 'sale.view_order_form': { 'partner_id': ['partner_id']}}, 'onchange_partner_order_id': { 'sale.view_order_form': { 'partner_order_id': ['partner_order_id', 'partner_invoice_id', 'partner_shipping_id']}}, 'onchange_pricelist_id': { 'sale.view_order_form': { 'pricelist_id': ['pricelist_id', 'order_line']}}, 'onchange_shop_id': { 'sale.view_order_form': { 'shop_id': ['shop_id']}}, 'shipping_policy_change': { 'sale.view_order_form': { 'order_policy': ['order_policy']}}}, 'sale.order.line': { 'product_id_change': { 'sale.view_order_form': { 'product_id': [ 'parent.pricelist_id', 'product_id', 'product_uom_qty', 'product_uom', 'product_uos_qty', 'product_uos', 'name', 'parent.partner_id', False, True, 'parent.date_order', 'product_packaging', 'parent.fiscal_position', False, 'context'], 'product_uom_qty': [ 'parent.pricelist_id', 'product_id', 'product_uom_qty', 'product_uom', 'product_uos_qty', 'product_uos', 'name', 'parent.partner_id', False, False, 'parent.date_order', 'product_packaging', 'parent.fiscal_position', True, 'context']}}, ... }} """ from oerplib.service.inspect.on_change import scan_on_change return scan_on_change(self._oerp, models) def dependencies(self, modules=None, models=None, models_blacklist=None, restrict=False, config=None): from oerplib.service.inspect.dependencies import Dependencies return Dependencies( self._oerp, modules, models, models_blacklist, restrict, config) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/inspect/dependencies.py000066400000000000000000000437651245703354300242640ustar00rootroot00000000000000# -*- coding: utf-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Implements the :class:`Dependencies` class used to compute dependencies between server modules. """ import copy from oerplib import error from oerplib.tools import v TPL_MODULE = """< {models} {comment}
{name}
>""" TPL_MODULE_MODEL = """ - {model} """ TPL_MODULE_COMMENT = """ {comment} """ def pattern2oerp(pattern): """Return a SQL expression corresponding to `pattern` (simpler representation of the SQL string matching). """ return pattern.replace('*', '%') class Dependencies(object): """Draw dependencies between modules. Models can be displayed in their respecting modules as well. """ def __init__(self, oerp, modules=None, models=None, models_blacklist=None, restrict=False, config=None): self.oerp = oerp self._restrict = restrict self._root_modules = modules or [] # Configuration options self._config = { 'module_uninst_bgcolor_title': '#DEDFDE', 'module_uninst_color_title': 'black', 'module_inst_bgcolor_title': '#64629C', 'module_inst_color_title': 'white', 'module_root_bgcolor_title': '#A50018', 'module_root_color_title': 'white', 'module_highlight_bgcolor_title': '#1F931F', 'module_highlight_color_title': 'white', 'module_bgcolor': 'white', 'module_color_comment': 'grey', 'model_color_normal': 'black', 'model_color_transient': '#7D7D7D', 'show_module_inst': True, 'show_module_uninst': False, 'show_model_normal': True, 'show_model_transient': False, } self._config.update(config or {}) # Check if root modules exist self._check_root_modules() # List of data models self._models = self._get_models_data( models or [], models_blacklist or []) # List of modules computed according to the `restrict` parameter # (display all modules or only modules related to data models) self._modules, self._modules_full = self._get_modules( self._models, keep=not bool(self._root_modules)) # Fetch dependencies between modules self._scan_module_dependencies() #@property #def models(self): # """Returns a dictionary of all models used to draw the graph.""" # return self._models #@property #def modules(self): # """Returns a dictionary of all modules used to draw the graph.""" # return self._modules def _check_root_modules(self): """Check if `root` modules exist, raise an error if not.""" module_obj = self.oerp.get('ir.module.module') for module in self._root_modules: if not module_obj.search([('name', 'ilike', module)]): raise error.InternalError( "'{0}' module does not exist".format(module)) def _get_models_data(self, models, models_blacklist): """Returns a dictionary `{MODEL: DATA, ...}` of models corresponding to `models - models_blacklist` patterns (whitelist substracted by a blacklist). """ res = {} # OpenERP v5 does not have the 'modules' field on 'ir.model' used to # bound a data model and its related modules. if v(self.oerp.version) <= v('6.0'): return res models_patterns = \ [pattern2oerp(model) for model in (models)] models_blacklist_patterns = \ [pattern2oerp(model) for model in (models_blacklist)] if models: model_obj = self.oerp.get('ir.model') args = [('model', '=ilike', model) for model in models_patterns] for _ in range(len(args) - 1): args.insert(0, '|') for model in models_blacklist_patterns: args.append('!') args.append(('model', '=ilike', model)) ids = model_obj.search(args) for data in model_obj.read(ids, ['model', 'modules', 'osv_memory']): if not self._config['show_model_transient'] \ and data['osv_memory']: continue if not self._config['show_model_normal'] \ and not data['osv_memory']: continue res[data['model']] = { 'model': data['model'], 'modules': data['modules'] and data['modules'].split(', ') or [], 'transient': data['osv_memory'], } return res def _get_modules(self, models=None, keep=False): """Returns a dictionary `{MODULE: DATA, ...}` with all modules installed (`restrict=False`) or only with modules related to data models (`restrict=True`). """ if models is None: models = {} modules = {} modules_full = {} # Fetch all the modules installed on the server module_obj = self.oerp.get('ir.module.module') states_inst = ['installed', 'to upgrade', 'to remove'] states_uninst = ['uninstalled', 'uninstallable', 'to install'] states = [] if self._config['show_module_inst'] \ and not self._config['show_module_uninst']: states = states_inst[:] elif not self._config['show_module_inst'] \ and self._config['show_module_uninst']: states = states_uninst[:] elif self._config['show_module_inst'] \ and self._config['show_module_uninst']: states = [] args = states and [('state', 'in', states)] or [] module_ids = module_obj.search(args) for data in module_obj.read(module_ids, ['name', 'state']): modules_full[data['name']] = { 'models': [], 'depends': [], 'keep': keep, 'installed': data['state'] in states_inst } # Dispatch data models in their related modules for model, data in models.iteritems(): for module in data['modules']: if module in modules_full \ and model not in modules_full[module]['models']: modules_full[module]['models'].append(model) # Compute the list of modules related to data models if self._restrict: for model, data in models.iteritems(): for module in data['modules']: if module not in modules: modules[module] = { 'models': [], 'depends': [], 'keep': keep, 'installed': modules_full[module]['installed'], } if model not in modules[module]['models']: modules[module]['models'].append(model) # Root modules are included by default, even if they don't contain # any of the matching models for module in self._root_modules: if module not in modules: modules[module] = { 'models': [], 'depends': [], 'keep': keep, 'installed': modules_full[module]['installed'], } # Otherwise, just take the full list of modules else: modules = copy.deepcopy(modules_full) return modules, modules_full def _scan_module_dependencies(self): """Scan dependencies of modules, until reaching each node in `root_modules`. If `root_modules` is empty, dependencies of all installed modules will be computed. """ module_obj = self.oerp.get('ir.module.module') # Compute dependencies of all installed modules for name in self._modules_full: module_ids = module_obj.search([('name', '=', name)]) module = module_obj.browse(module_ids[0]) for dependency in module.dependencies_id: if dependency.name in self._modules_full: self._modules_full[name]['depends'].append(dependency.name) if name in self._modules and dependency.name in self._modules: self._modules[name]['depends'].append(dependency.name) # In restrict mode, fix modules similar to "root" module (with no direct # dependency) while they may have indirect dependencies if self._restrict: for name, data in self._modules.items(): # Avoid iter modification # Detect fake "root" module if not data['depends'] and self._modules_full[name]['depends']: self._fix_fake_root_module(name) # Mark modules to keep in the graph if they belong to a path # leading to one of the root modules for module in self._modules: queue = [] queue.append(module) # If the module is a root module, we keep it regardless of its # dependencies if module in self._root_modules: self._modules[module]['keep'] = True # Recursive function to scan the graph and keep modules def process_keep(queue, module): for depend in self._modules[module]['depends']: queue.append(depend) # Found? Keep modules concerned by this path if depend in self._root_modules: for mod in queue: self._modules[mod]['keep'] = True break else: process_keep(queue, depend) queue.pop() process_keep(queue, module) def _fix_fake_root_module(self, module): """Fix the fake root `module` by finding its indirect dependencies.""" known_paths = [] def find_path(path, mod, common_model): """Try to found a path from the module `mod` among all installed modules to reach any 'restricted' module. """ if set(path) not in known_paths: known_paths.append(set(path)) path.append(mod) for depend in self._modules_full[mod]['depends']: path.append(depend) if set(path) in known_paths: continue if depend in self._modules: if common_model: # Has the 'head' module a common data model with # the 'tail' one? mod_tail = self._modules[module]['models'] mod_head = self._modules[depend]['models'] if list(set(mod_tail) & set(mod_head)): return True else: return True path.pop() res = find_path(path, depend, common_model) if res: return res path.pop() return False path = [] # Modules in the path should preferably have a common data model found_ok = find_path(path, module, common_model=True) # If not, we try again without the rule of the common model if not found_ok: known_paths = [] path = [] found_ok = find_path(path, module, common_model=False) # Update the graph by adding required modules to satisfy the # indirect dependency if found_ok: for index, mod in enumerate(path): if mod not in self._modules: # Add the required module, but without its dependencies self._modules[mod] = copy.deepcopy(self._modules_full[mod]) for depend in self._modules[mod]['depends'][:]: if depend not in self._modules: self._modules[mod]['depends'].remove(depend) self._modules[mod]['comment'] = \ "Indirect dependency" # Add the current module as a dependency to the previous one previous_mod = path[index - 1] if mod not in self._modules[previous_mod]['depends']: self._modules[previous_mod]['depends'].append(mod) @staticmethod def _draw_graph_node(module, tpl): """Generates a Graphviz node named `module`.""" import pydot return pydot.Node(module, margin='0', shape='none', label=tpl) @staticmethod def _draw_graph_edge(parent, child): """Generates a Graphviz edge between `parent` and `child` modules.""" import pydot return pydot.Edge( parent, child, dir='back') def make_dot(self): """Returns a `pydot.Dot` object representing dependencies between modules. >>> graph = oerp.inspect.dependencies(['base'], ['res.partner']) >>> graph.make_dot() See the `pydot `_ documentation for details. """ try: import pydot except ImportError: raise error.InternalError("'pydot' module not found") output = pydot.Dot() def get_template(module, data): """Generate the layout of the module.""" root = all(not self._modules[depend]['keep'] for depend in data['depends']) # Model lines tpl_models = [] for model in sorted(data['models']): color_model = self._config['model_color_normal'] if self._models[model]['transient']: color_model = self._config['model_color_transient'] tpl_models.append( TPL_MODULE_MODEL.format( color_model=color_model, model=model)) # Module comment tpl_comment = None if data.get('comment'): tpl_comment = " " tpl_comment += TPL_MODULE_COMMENT.format( module_color_comment=self._config['module_color_comment'], comment=data['comment']) # Module module_color_title = self._config['module_inst_color_title'] module_bgcolor_title = self._config['module_inst_bgcolor_title'] if root: module_color_title = self._config['module_root_color_title'] module_bgcolor_title = self._config['module_root_bgcolor_title'] if not root and tpl_models: module_color_title = \ self._config['module_highlight_color_title'] module_bgcolor_title = \ self._config['module_highlight_bgcolor_title'] if not data.get('installed'): module_color_title = self._config['module_uninst_color_title'] module_bgcolor_title = \ self._config['module_uninst_bgcolor_title'] tpl = TPL_MODULE.format( module_color_title=module_color_title, module_bgcolor_title=module_bgcolor_title, module_bgcolor=self._config['module_bgcolor'], name=module.lower(), models=''.join(tpl_models), comment=tpl_comment or '') return tpl for module, data in self._modules.iteritems(): if not data['keep']: continue # Add the module as node tpl = get_template(module, data) node = self._draw_graph_node(module, tpl) output.add_node(node) for dependency in data['depends']: if not self._modules[dependency]['keep']: continue # Add edge between the module and it's dependency edge = self._draw_graph_edge(dependency, module) output.add_edge(edge) return output def write(self, *args, **kwargs): """Write the resulting graph in a file. It is just a wrapper around the :func:`pydot.Dot.write` method (see the `pydot `_ documentation for details). Below a common way to use it:: >>> graph = oerp.inspect.dependencies(['base'], ['res.partner']) >>> graph.write('dependencies_res_partner.png', format='png') About supported formats, consult the `Graphviz documentation `_. """ output = self.make_dot() return output.write(*args, **kwargs) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/inspect/on_change.py000066400000000000000000000077051245703354300235510ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Provides the :func:`scan_on_change` function.""" import xml.etree.ElementTree import re ON_CHANGE_RE = re.compile('^(.*?)\((.*)\)$') def scan_on_change(oerp, models): """Scan all `on_change` methods detected among views of `models`.""" result = {} view_obj = oerp.get('ir.ui.view') model_data_obj = oerp.get('ir.model.data') for model in models: # Get all model views view_ids = view_obj.search( [('model', '=', model), ('type', 'in', ['form', 'tree'])]) model_data_ids = model_data_obj.search( [('res_id', 'in', view_ids), ('model', '=', 'ir.ui.view')]) model_data = model_data_obj.read( model_data_ids, ['name', 'module', 'res_id']) for data in model_data: # For each view, find all `on_change` methods view_name = "{0}.{1}".format(data['module'], data['name']) view_data = oerp.execute( model, 'fields_view_get', data['res_id'], 'form') _scan_view(model, view_name, view_data, result) return result def _scan_view(model, view_name, view_data, result): """Update `result` with all `on_change` methods detected on the view described by `view_data`. """ if model not in result: result[model] = {} # Scan the main view description xml_root = xml.etree.ElementTree.fromstring(view_data['arch']) # NOTE: Python 2.6 does not support full XPath, it is # why the ".//field" pattern is used instead of ".//field[@on_change]" for elt in xml_root.findall(".//field"): if 'on_change' not in elt.attrib: continue match = ON_CHANGE_RE.match(elt.attrib['on_change']) if match: func = match.group(1) args = [arg.strip() for arg in match.group(2).split(',')] field = elt.attrib['name'] if func not in result[model]: result[model][func] = {} if view_name not in result[model][func]: result[model][func][view_name] = {} if field not in result[model][func][view_name]: result[model][func][view_name][field] = [] if args and args not in result[model][func][view_name][field]: args = map(_clean_arg, args) result[model][func][view_name][field] = args # Scan recursively all other sub-descriptions defined in the view for field_name, field_data in view_data['fields'].iteritems(): if field_data.get('views') and field_data['views'].get('form'): model = field_data['relation'] if field_data['views'].get('form'): _scan_view( model, view_name, field_data['views']['form'], result) if field_data['views'].get('tree'): _scan_view( model, view_name, field_data['views']['tree'], result) return result def _clean_arg(arg): return { 'False': False, 'True': True, 'None': None, }.get(arg, arg) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/inspect/relations.py000066400000000000000000000436641245703354300236340ustar00rootroot00000000000000# -*- coding: utf-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## import re from oerplib import error TPL_MODEL = """< {attrs} {relations_r}
{name}
>""" TPL_MODEL_SUBTITLE = """ [{title}] """ TPL_MODEL_ATTR = """ - {name} {flags} {type_} """ TPL_MODEL_REL = """ - {name} {flags} {type_} """ def pattern2regex(pattern): """Return a regular expression corresponding to `pattern` (simpler representation of the regular expression). """ pattern = "^{0}$".format(pattern.replace('*', '.*')) return re.compile(pattern) def match_in(elt, lst): """Return `True` if `elt` is matching one of a pattern in `lst`.""" for regex in lst: if regex.match(elt): return True return False class Relations(object): """Draw relations between models with `Graphviz`.""" def __init__(self, oerp, models, maxdepth=1, whitelist=None, blacklist=None, attrs_whitelist=None, attrs_blacklist=None, config=None): self.oerp = oerp self._models = models self._maxdepth = maxdepth self._whitelist = [pattern2regex(model) for model in (models)] self._whitelist.extend( [pattern2regex(model) for model in (whitelist or ['*'])]) self._blacklist = [pattern2regex(model) for model in (blacklist or [])] self._attrs_whitelist = [pattern2regex(model) for model in (attrs_whitelist or [])] self._attrs_blacklist = [pattern2regex(model) for model in (attrs_blacklist or [])] # Configuration options self._config = { 'relation_types': ['many2one', 'one2many', 'many2many'], 'show_many2many_table': False, 'color_many2one': '#0E2548', 'color_one2many': '#008200', 'color_many2many': '#6E0004', 'model_root_bgcolor_title': '#A50018', 'model_bgcolor_title': '#64629C', 'model_color_title': 'white', 'model_color_subtitle': '#3E3D60', 'model_bgcolor': 'white', 'color_normal': 'black', 'color_required': 'blue', 'color_function': '#7D7D7D', 'space_between_models': 0.25, } self._config.update(config or {}) # Store relations between data models: self._relations = {} self._stack = {'o2m': {}} # Build and draw relations for each model for model in models: self._build_relations(self.oerp.get(model), 0) def _build_relations(self, obj, depth): """Build all relations of `obj` recursively: - many2one - one2many (will be bound to the related many2one) - many2many (will be bound with the eventual many2many from the other side) """ # Stop scanning when the maxdepth is reached, or when the data model # has already been scanned if obj._name in self._models: depth = 0 if depth > self._maxdepth or obj._name in self._relations: return # Check the whitelist, then the blacklist if obj._name not in self._models: if self._whitelist: if not match_in(obj._name, self._whitelist): return if self._blacklist: if match_in(obj._name, self._blacklist): return # Only increments depth for data models which are not already scanned if obj._name not in self._relations: depth += 1 # Scan relational fields of the data model fields = obj.fields_get() if obj._name not in self._relations: self._relations[obj._name] = { 'relations': {}, 'relations_r': {}, # Recursive relations 'fields': dict((k, v) for k, v in fields.iteritems() if not v.get('relation')), } for name, data in fields.iteritems(): if 'relation' in data \ and data['type'] in self._config['relation_types']: rel = data['relation'] # where to store the relation? store_type = obj._name == rel and 'relations_r' or 'relations' # flags flags = { 'required': data.get('required'), 'function': data.get('function'), 'fnct_inv': data.get('fnct_inv'), 'fnct_search': data.get('fnct_search'), } # many2one if data['type'] == 'many2one': # Check if related one2many fields have been registered # for the current many2one relation o2m_fields = obj._name in self._stack['o2m'] \ and rel in self._stack['o2m'][obj._name] \ and name in self._stack['o2m'][obj._name][rel] \ and self._stack['o2m'][obj._name][rel][name] \ or [] # Add the field self._relations[obj._name][store_type][name] = { 'type': 'many2one', 'relation': rel, 'name': name, 'o2m_fields': o2m_fields, } self._relations[obj._name][store_type][name].update(flags) # one2many elif data['type'] == 'one2many': # 'relation_field' key may be missing for 'one2many' # generated by 'fields.function' rel_f = data.get('relation_field', None) # If it is a normal o2m field (with a relation field), it # will be attached to its corresponding m2o field if rel_f: # Case where the related m2o field has already been # registered if rel in self._relations \ and rel_f in self._relations[rel][store_type]: if name not in self._relations[ rel][store_type][rel_f]: self._relations[ rel][store_type][ rel_f]['o2m_fields'].append(name) # Otherwise, we will process the field later (when the # m2o field will be scanned) else: if rel not in self._stack['o2m']: self._stack['o2m'][rel] = {} if obj._name not in self._stack['o2m'][rel]: self._stack['o2m'][rel][obj._name] = {} if rel_f not in self._stack['o2m'][rel][obj._name]: self._stack['o2m'][rel][obj._name][rel_f] = [] self._stack[ 'o2m'][rel][obj._name][rel_f].append(name) # If the o2m field has no relation field available # (calculated by a function, or a related field) the # relation is stored as a standalone one2many else: self._relations[obj._name][store_type][name] = { 'type': 'one2many', 'relation': rel, 'name': name, } self._relations[obj._name][store_type][name].update( flags) # many2many elif data['type'] == 'many2many': #rel_columns = data.get('related_columns') \ # or data.get('m2m_join_columns') #rel_columns = rel_columns and tuple(rel_columns) or None self._relations[obj._name][store_type][name] = { 'type': 'many2many', 'relation': rel, 'name': name, 'third_table': data.get('third_table') or data.get('m2m_join_table'), 'related_columns': None, } self._relations[obj._name][store_type][name].update(flags) # Scan relations recursively rel_obj = self.oerp.get(rel) self._build_relations(rel_obj, depth) def make_dot(self): """Returns a `pydot.Dot` object representing relations between models. >>> graph = oerp.inspect.relations(['res.partner']) >>> graph.make_dot() See the `pydot `_ documentation for details. """ try: import pydot except ImportError: raise error.InternalError("'pydot' module not found") output = pydot.Dot( graph_type='digraph', overlap='scalexy', splines='true', nodesep=str(self._config['space_between_models'])) for model, data in self._relations.iteritems(): # Generate attributes of the model attrs_ok = False attrs = [] if self._attrs_whitelist \ and match_in(model, self._attrs_whitelist): attrs_ok = True if self._attrs_blacklist \ and match_in(model, self._attrs_blacklist): attrs_ok = False if attrs_ok: subtitle = TPL_MODEL_SUBTITLE.format( color=self._config['model_color_subtitle'], title="Attributes") attrs.append(subtitle) for k, v in sorted(data['fields'].iteritems()): color_name = self._config['color_normal'] if v.get('function'): color_name = self._config['color_function'] if v.get('fnct_inv'): color_name = self._config['color_normal'] #if v.get('required'): # color_name = self._config['color_required'] attr = TPL_MODEL_ATTR.format( name=k, type_=v['type'], color_name=color_name, flags=self._generate_flags_label(v)) attrs.append(attr) # Generate recursive relations of the model relations_r = [] if data['relations_r']: subtitle = TPL_MODEL_SUBTITLE.format( color=self._config['model_color_subtitle'], title="Recursive relations") relations_r.append(subtitle) for data2 in data['relations_r'].itervalues(): label = self._generate_relation_label(data2) flags = self._generate_flags_label(data2) rel_r = TPL_MODEL_REL.format( name=label, flags=flags, color_name=self._config['color_normal'], type_=data2['type']) relations_r.append(rel_r) # Generate the layout of the model model_bgcolor_title = self._config['model_bgcolor_title'] if model in self._models: model_bgcolor_title = self._config['model_root_bgcolor_title'] tpl = TPL_MODEL.format( model_color_title=self._config['model_color_title'], model_bgcolor_title=model_bgcolor_title, model_bgcolor=self._config['model_bgcolor'], name=model, attrs=''.join(attrs), relations_r=''.join(relations_r)) # Add the model to the graph node = self._create_node(model, 'relation', tpl) output.add_node(node) # Draw relations of the model for data2 in data['relations'].itervalues(): if data2['relation'] in self._relations: edge = self._create_edge(model, data2['relation'], data2) output.add_edge(edge) return output def _create_node(self, name, type_, tpl=None): """Generate a `pydot.Node` object. `type_` can take one of these values: ``relation``, ``m2m_table``. If a HTML `tpl` is supplied, it will be used as layout for the node. """ import pydot types = { 'relation': { 'margin': '0', 'shape': tpl and 'none' or 'record', 'label': tpl or name, }, 'm2m_table': { 'margin': '0', 'shape': tpl and 'none' or 'record', 'color': self._config['color_many2many'], 'fontcolor': self._config['color_many2many'], 'label': tpl or name, }, } return pydot.Node(name, **types[type_]) def _create_edge(self, model1, model2, data): """Generate a `pydot.Edge` object, representing a relation between `model1` and `model2`. """ import pydot label = self._generate_relation_label(data, space=6, on_arrow=True) return pydot.Edge( model1, model2, label=label, labeldistance='10.0', color=self._config['color_{0}'.format(data['type'])], fontcolor=self._config['color_{0}'.format(data['type'])]) #arrowhead=(data['type'] == 'many2many' and 'none' or 'normal'), def _generate_flags_label(self, data): """Generate a HTML label for status flags of a field described by `data`. """ flags = [] if data.get('required'): flags.append("R".format( color=self._config['color_required'])) if data.get('function'): name = data.get('fnct_inv') and "Fw" or "F" if data.get('fnct_search'): name += "s" flags.append("{name}".format( name=name, color=self._config['color_function'])) if flags: return " [{0}]".format(' '.join(flags)) return "" def _generate_relation_label(self, data, space=0, on_arrow=False): """Generate a HTML label based for the relation described by `data`.""" name_color = self._config['color_{0}'.format(data['type'])] label = "{space}{name}".format( color=name_color, name=data['name'], space=' ' * space) # many2one arrow if data['type'] == 'many2one' and data['o2m_fields']: label = "{label} ← {o2m}".format( label=label, color=self._config['color_one2many'], o2m=', '.join(data['o2m_fields'])) # one2many "standalone" arrow if data['type'] == 'one2many': pass # many2many arrow if data['type'] == 'many2many': m2m_table = '' if self._config['show_many2many_table']: if data.get('third_table'): m2m_table = '({table})'.format( table=data.get('third_table')) label = "{space}{name} {m2m_t}".format( color=name_color, name=data['name'], m2m_t=m2m_table, space=' ' * space) # flags if on_arrow: label += self._generate_flags_label(data) # add space on the right label = label + "{space}".format(space=' ' * space) # closing tag if on_arrow: label = "<{label}>".format(label=label) return label def write(self, *args, **kwargs): """Write the resulting graph in a file. It is just a wrapper around the :func:`pydot.Dot.write` method (see the `pydot `_ documentation for details). Below a common way to use it:: >>> graph = oerp.inspect.relations(['res.partner']) >>> graph.write('relations_res_partner.png', format='png') About supported formats, consult the `Graphviz documentation `_. """ output = self.make_dot() return output.write(*args, **kwargs) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/osv/000077500000000000000000000000001245703354300204075ustar00rootroot00000000000000python-oerplib-0.8.4/oerplib/service/osv/__init__.py000066400000000000000000000021061245703354300225170ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## from oerplib.service.osv.browse import BrowseRecord from oerplib.service.osv.osv import Model # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/osv/browse.py000066400000000000000000000140531245703354300222650ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module provides the BrowseRecord class.""" from oerplib import error class BrowseRecord(object): """Base class that all browsable records inherit from. No attributes should be defined in this class (except ``_id``/``id``, ``__oerp__``, ``__osv__``, ``__data__`` and Python magic methods) in order to not be conflicted with the fields defined in the model class on the server. A reference to the :class:`OERP ` object used to instanciate a ``browse_record`` is available through the ``__oerp__`` attribute:: >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'admin', 'db_name') >>> user.__oerp__ == oerp True The ``__data__`` attribute is used to store some data related to the record (it is not recommended to edit them):: >>> user.__data__ {'updated_values': {}, 'raw_data': {'action_id': False, 'active': True, 'company_id': [1, 'Your Company'], ...}, 'values': {'action_id': False, 'active': True, 'company_id': [1, 'Your Company'], ...}} In the same way, information about the model class and its columns may be obtained via the ``__osv__`` attribute:: >>> user.__osv__ {'columns': {'action_id': , 'active': , 'company_id': , ...}, 'name': 'res.users'} """ __oerp__ = None __osv__ = None def __init__(self, o_id): self._id = o_id self.__data__ = {'values': {}, 'raw_data': {}, 'updated_values': {}} @property def id(self): """ID of the record.""" return self._id def __repr__(self): return "browse_record(%r, %r)" % (self.__osv__['name'], self._id) def __getitem__(self, key): return getattr(self, key) def __int__(self): return self._id def __eq__(self, other): """Compare two browse records. Return ``True`` if their ID and model name are equals. NOTE: the comparison is made this way because their model classes can be differents objects. """ return isinstance(other, BrowseRecord) and \ self.id == other.id and \ self.__osv__['name'] == other.__osv__['name'] def __ne__(self, other): if not isinstance(other, BrowseRecord): return True return isinstance(other, BrowseRecord)\ and (self.__osv__['name'], self.id) !=\ (other.__osv__['name'], other.id) class BrowseRecordIterator(object): """Iterator of browsable records. In fact, it is a generator to return records one by one, and able to increment/decrement records by overriding '+=' and '-=' operators. """ def __init__(self, model, ids, context=None, parent=None, parent_field=None): self.model = model self.ids = ids self.context = context self.index = None if self.ids: self.index = 0 self.parent = parent self.parent_field = parent_field def __len__(self): return len(self.ids) def __iter__(self): return self def next(self): if self.index is None or self.index >= len(self.ids): raise StopIteration else: id_ = self.ids[self.index] self.index += 1 return self.model.browse(id_, context=self.context) def __iadd__(self, records): if not self.parent or not self.parent_field: raise error.InternalError("No parent record to update") try: list(records) except TypeError: records = [records] updated_values = self.parent.__data__['updated_values'] res = [] if updated_values.get(self.parent_field.name): res = updated_values[self.parent_field.name][:] # Copy from oerplib.service.osv import fields for id_ in fields.records2ids(records): if (3, id_) in res: res.remove((3, id_)) if (4, id_) not in res: res.append((4, id_)) return res def __isub__(self, records): if not self.parent or not self.parent_field: raise error.InternalError("No parent record to update") try: list(records) except TypeError: records = [records] updated_values = self.parent.__data__['updated_values'] res = [] if updated_values.get(self.parent_field.name): res = updated_values[self.parent_field.name][:] # Copy from oerplib.service.osv import fields for id_ in fields.records2ids(records): if (4, id_) in res: res.remove((4, id_)) if (3, id_) not in res: res.append((3, id_)) return res # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/osv/fields.py000066400000000000000000000504671245703354300222430ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module contains classes representing the fields supported by OpenObject. A field is a Python descriptor which defines getter/setter methods for its related attribute. """ import datetime from oerplib import error from oerplib.service.osv import browse def is_int(value): """Return `True` if ``value`` is an integer.""" if isinstance(value, bool): return False try: int(value) return True except ValueError: return False def oerp_tuple_in(iterable): """Return `True` if `iterable` contains an expected tuple like ``(6, 0, IDS)`` (and so on). >>> oerp_tuple_in([0, 1, 2]) # Simple list False >>> oerp_tuple_in([(6, 0, [42])]) # List of tuples True >>> oerp_tuple_in([[1, 42]]) # List of lists True """ if not iterable: return False def is_oerp_tuple(elt): try: return elt[:1][0] in [1, 2, 3, 4, 5] \ or elt[:2] in [(6, 0), [6, 0], (0, 0), [0, 0]] except: return False return any(is_oerp_tuple(elt) for elt in iterable) def records2ids(iterable): """Replace `browse_records` contained in `iterable` by their corresponding IDs: >>> groups = list(oerp.user.groups_id) >>> records2ids(groups) [1, 2, 3, 14, 17, 18, 19, 7, 8, 9, 5, 20, 21, 22, 23] """ def record2id(elt): if isinstance(elt, browse.BrowseRecord): return elt.id return elt return [record2id(elt) for elt in iterable] class BaseField(object): """Field which all other fields inherit. Manage common metadata. """ def __init__(self, osv, name, data): self.osv = osv self.name = name self.type = 'type' in data and data['type'] or False self.string = 'string' in data and data['string'] or False self.size = 'size' in data and data['size'] or False self.required = 'required' in data and data['required'] or False self.readonly = 'readonly' in data and data['readonly'] or False self.help = 'help' in data and data['help'] or False self.states = 'states' in data and data['states'] or False def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __str__(self): """Return a human readable string representation of the field.""" attrs = ['string', 'relation', 'required', 'readonly', 'size', 'domain'] attrs_rep = [] for attr in attrs: if hasattr(self, attr): value = getattr(self, attr) if value: if isinstance(value, basestring): attrs_rep.append("{0}='{1}'".format(attr, value)) else: attrs_rep.append("{0}={1}".format(attr, value)) attrs_rep = ", ".join(attrs_rep) return "{0}({1})".format(self.type, attrs_rep) def check_value(self, value): """Check the validity of a value for the field.""" #if self.readonly: # raise error.Error( # "'{field_name}' field is readonly".format( # field_name=self.name)) if value and self.size: if not isinstance(value, basestring): raise ValueError("Value supplied has to be a string") if len(value) > self.size: raise ValueError( "Lenght of the '{field_name}' is limited to {size}".format( field_name=self.name, size=self.size)) if not value and self.required: raise ValueError( "'{field_name}' field is required".format( field_name=self.name)) return value class SelectionField(BaseField): """Represent the OpenObject 'fields.selection'""" def __init__(self, osv, name, data): super(SelectionField, self).__init__(osv, name, data) self.selection = 'selection' in data and data['selection'] or False def __get__(self, instance, owner): if self.name in instance.__data__['updated_values']: return instance.__data__['updated_values'][self.name] return instance.__data__['values'][self.name] def __set__(self, instance, value): value = self.check_value(value) instance.__data__['updated_values'][self.name] = value def check_value(self, value): super(SelectionField, self).check_value(value) selection = [val[0] for val in self.selection] if value and value not in selection: raise ValueError( "The value '{value}' supplied doesn't match with the possible " "values '{selection}' for the '{field_name}' field".format( value=value, selection=selection, field_name=self.name, )) return value class Many2ManyField(BaseField): """Represent the OpenObject 'fields.many2many'""" def __init__(self, osv, name, data): super(Many2ManyField, self).__init__(osv, name, data) self.relation = 'relation' in data and data['relation'] or False self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False def __get__(self, instance, owner): """Return a generator to iterate on ``browse_record`` instances.""" ids = None if instance.__data__['values'][self.name]: ids = instance.__data__['values'][self.name][:] # None value => get the value on the fly if ids is None: orig_ids = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = orig_ids ids = orig_ids and orig_ids[:] or [] # Take updated values into account if self.name in instance.__data__['updated_values']: ids = ids or [] values = instance.__data__['updated_values'][self.name] # Handle OERP tuples to update 'ids' for value in values: if value[0] == 6 and value[2]: ids = value[2] elif value[0] == 5: ids = [] elif value[0] == 4 and value[1] and value[1] not in ids: ids.append(value[1]) elif value[0] == 3 and value[1] and value[1] in ids: ids.remove(value[1]) context = instance.__data__['context'].copy() context.update(self.context) return browse.BrowseRecordIterator( model=instance.__oerp__.get(self.relation), ids=ids, context=context, parent=instance, parent_field=self) def __set__(self, instance, value): value = self.check_value(value) if value and not oerp_tuple_in(value): value = [(6, 0, records2ids(value))] elif not value: value = [(5, )] instance.__data__['updated_values'][self.name] = value def check_value(self, value): if value: if not isinstance(value, list): raise ValueError( "The value supplied has to be a list or 'False'") return super(Many2ManyField, self).check_value(value) class Many2OneField(BaseField): """Represent the OpenObject 'fields.many2one'""" def __init__(self, osv, name, data): super(Many2OneField, self).__init__(osv, name, data) self.relation = 'relation' in data and data['relation'] or False self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False def __get__(self, instance, owner): id_ = instance.__data__['values'][self.name] if self.name in instance.__data__['updated_values']: id_ = instance.__data__['updated_values'][self.name] # FIXME if id_ is a browse_record # None value => get the value on the fly if id_ is None: id_ = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = id_ if id_: context = instance.__data__['context'].copy() context.update(self.context) return instance.__class__.__oerp__.browse( self.relation, id_[0], context) return False def __set__(self, instance, value): if isinstance(value, browse.BrowseRecord): o_rel = value elif is_int(value): o_rel = instance.__class__.__oerp__.browse(self.relation, value) elif value in [None, False]: o_rel = False else: raise ValueError("Value supplied has to be an integer, " "a browse_record object or False.") o_rel = self.check_value(o_rel) instance.__data__['updated_values'][self.name] = \ o_rel and [o_rel.id, False] def check_value(self, value): super(Many2OneField, self).check_value(value) if value and value.__osv__['name'] != self.relation: raise ValueError( ("Instance of '{model}' supplied doesn't match with the " + "relation '{relation}' of the '{field_name}' field.").format( model=value.__osv__['name'], relation=self.relation, field_name=self.name)) return value class One2ManyField(BaseField): """Represent the OpenObject 'fields.one2many'""" def __init__(self, osv, name, data): super(One2ManyField, self).__init__(osv, name, data) self.relation = 'relation' in data and data['relation'] or False self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False def __get__(self, instance, owner): """Return a generator to iterate on ``browse_record`` instances.""" ids = None if instance.__data__['values'][self.name]: ids = instance.__data__['values'][self.name][:] # None value => get the value on the fly if ids is None: orig_ids = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = orig_ids ids = orig_ids and orig_ids[:] or [] # Take updated values into account if self.name in instance.__data__['updated_values']: ids = ids or [] values = instance.__data__['updated_values'][self.name] # Handle OERP tuples to update 'ids' for value in values: if value[0] == 6 and value[2]: ids = value[2] elif value[0] == 5: ids = [] elif value[0] == 4 and value[1] and value[1] not in ids: ids.append(value[1]) elif value[0] == 3 and value[1] and value[1] in ids: ids.remove(value[1]) context = instance.__data__['context'].copy() context.update(self.context) return browse.BrowseRecordIterator( model=instance.__oerp__.get(self.relation), ids=ids, context=context, parent=instance, parent_field=self) def __set__(self, instance, value): value = self.check_value(value) if value and not oerp_tuple_in(value): value = [(6, 0, records2ids(value))] elif not value: value = [(5, )] instance.__data__['updated_values'][self.name] = value def check_value(self, value): if value: if not isinstance(value, list): raise ValueError( "The value supplied has to be a list or 'False'") return super(One2ManyField, self).check_value(value) class ReferenceField(BaseField): """.. versionadded:: 0.6 Represent the OpenObject 'fields.reference'. """ def __init__(self, osv, name, data): super(ReferenceField, self).__init__(osv, name, data) self.context = 'context' in data and data['context'] or {} self.domain = 'domain' in data and data['domain'] or False self.selection = 'selection' in data and data['selection'] or False def __get__(self, instance, owner): value = instance.__data__['values'][self.name] if self.name in instance.__data__['updated_values']: value = instance.__data__['updated_values'][self.name] # None value => get the value on the fly if value is None: value = instance.__oerp__.read( instance.__osv__['name'], [instance.id], [self.name])[0][self.name] instance.__data__['values'][self.name] = value if value: relation, sep, o_id = value.rpartition(',') relation = relation.strip() o_id = int(o_id.strip()) if relation and o_id: context = instance.__data__['context'].copy() context.update(self.context) return instance.__class__.__oerp__.browse( relation, o_id, context) return False def __set__(self, instance, value): value = self.check_value(value) instance.__data__['updated_values'][self.name] = value def _check_relation(self, relation): selection = [val[0] for val in self.selection] if relation not in selection: raise ValueError( ("The value '{value}' supplied doesn't match with the possible" " values '{selection}' for the '{field_name}' field").format( value=relation, selection=selection, field_name=self.name, )) return relation def check_value(self, value): if isinstance(value, browse.BrowseRecord): relation = value.__class__.__osv__['name'] self._check_relation(relation) value = "%s,%s" % (relation, value.id) super(ReferenceField, self).check_value(value) elif isinstance(value, basestring): super(ReferenceField, self).check_value(value) relation, sep, o_id = value.rpartition(',') relation = relation.strip() o_id = o_id.strip() #o_rel = instance.__class__.__oerp__.browse(relation, o_id) if not relation or not is_int(o_id): raise ValueError("String not well formatted, expecting " "'{relation},{id}' format") self._check_relation(relation) else: raise ValueError("Value supplied has to be a string or" " a browse_record object.") return value class DateField(BaseField): """Represent the OpenObject 'fields.data'""" pattern = "%Y-%m-%d" def __init__(self, osv, name, data): super(DateField, self).__init__(osv, name, data) def __get__(self, instance, owner): value = instance.__data__['values'][self.name] if self.name in instance.__data__['updated_values']: value = instance.__data__['updated_values'][self.name] try: res = datetime.datetime.strptime(value, self.pattern).date() except Exception: # ValueError, TypeError res = value return res def __set__(self, instance, value): value = self.check_value(value) instance.__data__['updated_values'][self.name] = value def check_value(self, value): super(DateField, self).check_value(value) if isinstance(value, datetime.date): value = value.strftime("%Y-%m-%d") elif isinstance(value, basestring): try: datetime.datetime.strptime(value, self.pattern) except: raise ValueError( "String not well formatted, expecting '{0}' format".format( self.pattern)) elif isinstance(value, bool): return value else: raise ValueError( "Expecting a datetime.date object or basestring") return value class DateTimeField(BaseField): """Represent the OpenObject 'fields.datetime'""" pattern = "%Y-%m-%d %H:%M:%S" def __init__(self, osv, name, data): super(DateTimeField, self).__init__(osv, name, data) def __get__(self, instance, owner): value = instance.__data__['values'][self.name] if self.name in instance.__data__['updated_values']: value = instance.__data__['updated_values'][self.name] try: res = datetime.datetime.strptime(value, self.pattern) except Exception: # ValueError, TypeError res = value return res def __set__(self, instance, value): value = self.check_value(value) instance.__data__['updated_values'][self.name] = value def check_value(self, value): super(DateTimeField, self).check_value(value) if isinstance(value, datetime.datetime): value = value.strftime("%Y-%m-%d %H:%M:%S") elif isinstance(value, basestring): try: datetime.datetime.strptime(value, self.pattern) except: raise ValueError( "Value not well formatted, expecting '{0}' format".format( self.pattern)) elif isinstance(value, bool): return value else: raise ValueError( "Expecting a datetime.datetime object or basestring") return value class ValueField(BaseField): """Represent simple OpenObject fields: - 'fields.char', - 'fields.float', - 'fields.integer', - 'fields.boolean', - 'fields.text', - 'fields.binary', """ def __init__(self, osv, name, data): super(ValueField, self).__init__(osv, name, data) def __get__(self, instance, owner): if self.name in instance.__data__['updated_values']: return instance.__data__['updated_values'][self.name] return instance.__data__['values'][self.name] def __set__(self, instance, value): value = self.check_value(value) instance.__data__['updated_values'][self.name] = value def generate_field(osv, name, data): """Generate a well-typed field according to the data dictionary supplied (obtained via 'fields_get' XML-RPC/NET-RPC method). """ assert 'type' in data field = None if data['type'] == 'selection': field = SelectionField(osv, name, data) elif data['type'] == 'many2many': field = Many2ManyField(osv, name, data) elif data['type'] == 'many2one': field = Many2OneField(osv, name, data) elif data['type'] == 'one2many': field = One2ManyField(osv, name, data) elif data['type'] == 'reference': field = ReferenceField(osv, name, data) elif data['type'] == 'date': field = DateField(osv, name, data) elif data['type'] == 'datetime': field = DateTimeField(osv, name, data) elif data['type'] in ['char', 'float', 'integer', 'integer_big', 'boolean', 'text', 'binary', 'html']: field = ValueField(osv, name, data) else: field = ValueField(osv, name, data) return field # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/osv/osv.py000066400000000000000000000244341245703354300215770ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Provide the :class:`Model` class which allow to access dynamically to all methods proposed by a data model.""" import sys # to check Python version at runtime import collections from oerplib.tools import v from oerplib import error from oerplib.service.osv import fields, browse class Model(object): """.. versionadded:: 0.5 Represent a data model. .. note:: This class have to be used through the :func:`oerplib.OERP.get` method. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'passwd', 'database') >>> user_obj = oerp.get('res.users') >>> user_obj >>> user_obj.name_get(user.id) # Use any methods from the model instance [[1, 'Administrator']] .. warning:: The only method implemented in this class is ``browse``. Except this one, method calls are purely dynamic. As long as you know the signature of the model method targeted, you will be able to use it (see the :ref:`tutorial `). """ fields_reserved = ['id', '__oerp__', '__osv__', '__data__'] def __init__(self, oerp, model): super(Model, self).__init__() self._oerp = oerp self._name = model self._browse_class = self._generate_browse_class() def browse(self, ids, context=None): """Browse one or several records (if `ids` is a list of IDs) from `model`. The fields and values for such objects are generated dynamically. >>> oerp.get('res.partner').browse(1) browse_record(res.partner, 1) >>> [partner.name for partner in oerp.get('res.partner').browse([1, 2])] [u'Your Company', u'ASUStek'] A list of data types used by ``browse_record`` fields are available :ref:`here `. :return: a ``browse_record`` instance :return: a generator to iterate on ``browse_record`` instances :raise: :class:`oerplib.error.RPCError` """ if isinstance(ids, list): return browse.BrowseRecordIterator(self, ids, context=context) #return browse.BrowseRecordIterator( # model=self, # ids=ids, # context=context) else: obj = self._browse_class(ids) self._refresh(obj, context) return obj #return self.browse(ids, context) def _generate_browse_class(self): """Generate a class with all its fields corresponding to the model name supplied and return them. """ # Retrieve server fields info and generate corresponding local fields fields_get = self._oerp.execute(self._name, 'fields_get') cls_name = self._name.replace('.', '_') # Encode the class name for the Python2 'type()' function. # No need to do this for Python3. if type(cls_name) == unicode and sys.version_info < (3,): cls_name = cls_name.encode('utf-8') cls_fields = {} for field_name, field_data in fields_get.items(): if field_name not in Model.fields_reserved: cls_fields[field_name] = fields.generate_field( self, field_name, field_data) # Case where no field 'name' exists, we generate one (which will be # in readonly mode) in purpose to be filled with the 'name_get' method if 'name' not in cls_fields: field_data = {'type': 'text', 'string': 'Name', 'readonly': True} cls_fields['name'] = fields.generate_field(self, 'name', field_data) cls = type(cls_name, (browse.BrowseRecord,), {}) cls.__oerp__ = self._oerp cls.__osv__ = {'name': self._name, 'columns': cls_fields} slots = ['__oerp__', '__osv__', '__dict__', '__data__'] slots.extend(cls_fields.keys()) cls.__slots__ = slots return cls def _write_record(self, obj, context=None): """Send values of fields updated to the server.""" context = context or self._oerp.context obj_data = obj.__data__ vals = {} for field_name in obj_data['updated_values']: if field_name in obj_data['raw_data']: field = self._browse_class.__osv__['columns'][field_name] field_value = obj.__data__['updated_values'][field_name] # Many2One fields if isinstance(field, fields.Many2OneField): vals[field_name] = field_value and field_value[0] # All other fields else: vals[field_name] = field_value try: if v(self._oerp.version) < v('6.1'): res = self.write([obj.id], vals, context) else: res = self.write([obj.id], vals, context=context) except error.Error as exc: raise exc else: # Update raw_data dictionary # FIXME: make it optional to avoid a RPC request? self._refresh(obj, context) return res def _refresh(self, obj, context=None): """Retrieve field values from the server. May be used to restore the original values in the purpose to cancel all changes made. """ context = context or self._oerp.context obj_data = obj.__data__ obj_data['context'] = context # Get basic fields (no relational ones) basic_fields = [] for field_name, field in obj.__osv__['columns'].iteritems(): if not getattr(field, 'relation', False): basic_fields.append(field_name) else: obj_data['raw_data'][field_name] = None # Fill fields with values of the record if obj.id: if v(self._oerp.version) < v('6.1'): data = self.read([obj.id], basic_fields, context) if data: obj_data['raw_data'].update(data[0]) else: obj_data['raw_data'] = False else: data = self.read([obj.id], basic_fields, context=context) if data: obj_data['raw_data'].update(data[0]) else: obj_data['raw_data'] = False if obj_data['raw_data'] is False: raise error.RPCError( "There is no '{model}' record with ID {obj_id}.".format( model=obj.__class__.__osv__['name'], obj_id=obj.id)) # No ID: fields filled with default values else: if v(self._oerp.version) < v('6.1'): default_get = self.default_get( obj.__osv__['columns'].keys(), context) else: default_get = self.default_get( obj.__osv__['columns'].keys(), context=context) obj_data['raw_data'] = {} for field_name in obj.__osv__['columns']: obj_data['raw_data'][field_name] = False obj_data['raw_data'].update(default_get) self._reset(obj) def _reset(self, obj): """Cancel all changes by restoring field values with original values obtained during the last refresh (object instanciation or last call to _refresh() method). """ obj_data = obj.__data__ obj_data['updated_values'] = {} # Load fields and their values for field in self._browse_class.__osv__['columns'].values(): if field.name in obj_data['raw_data']: obj_data['values'][field.name] = \ obj_data['raw_data'][field.name] setattr(obj.__class__, field.name, field) def _unlink_record(self, obj, context=None): """Delete the object from the server.""" context = context or self._oerp.context if v(self._oerp.version) < v('6.1'): return self.unlink([obj.id], context) else: return self.unlink([obj.id], context=context) def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args, **kwargs): """Return the result of the RPC request.""" if v(self._oerp.version) < v('6.1'): if kwargs: raise error.RPCError( "Named parameters are not supported by the version " "of this server.") result = self._oerp.execute( self._browse_class.__osv__['name'], method, *args) else: if self._oerp.config['auto_context'] \ and 'context' not in kwargs: kwargs['context'] = self._oerp.context result = self._oerp.execute_kw( self._browse_class.__osv__['name'], method, args, kwargs) return result return rpc_method def __repr__(self): return "Model(%r)" % (self._browse_class.__osv__['name']) # ---------------------------- # # -- MutableMapping methods -- # # ---------------------------- # def __getitem__(self, obj_id): return self.browse(obj_id) def __iter__(self): ids = self.search([]) return browse.BrowseRecordIterator(self, ids) def __len__(self): return self._oerp.search(self._browse_class.__osv__['name'], count=True) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/service/wizard.py000066400000000000000000000053561245703354300214630ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2011-2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """Provide the :class:`Wizard` class in order to access the old-style wizards. """ from oerplib import rpc, error class Wizard(object): """.. versionadded:: 0.6 The `Wizard` class represents the ``/wizard`` RPC service which lets you access to the old-style wizards. .. note:: This service have to be used through the :attr:`oerplib.OERP.wizard` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> user = oerp.login('admin', 'passwd', 'database') >>> oerp.wizard .. warning:: All methods documented below are not strictly implemented in `OERPLib` Method calls are purely dynamic, and the following documentation can be wrong if the API of `OpenERP` is changed between versions. Anyway, if you know the API proposed by the server for the ``/wizard`` RPC service, it will work. .. method:: Wizard.create(wiz_name, datas=None) >>> oerp.wizard.create('wiz_name') 1 :return: the wizard's instance ID .. method:: Wizard.execute(wiz_id, datas, action='init', context=None) >>> oerp.wizard.execute(1, {'one_field': "Value"}) """ def __init__(self, oerp): self._oerp = oerp def __getattr__(self, method): """Provide a dynamic access to a RPC method.""" def rpc_method(*args): """Return the result of the RPC request.""" try: meth = getattr(self._oerp._connector.wizard, method, False) return meth(self._oerp.database, self._oerp.user.id, *args) except rpc.error.ConnectorError as exc: raise error.RPCError(exc.message, exc.oerp_traceback) return rpc_method # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/tools/000077500000000000000000000000001245703354300173005ustar00rootroot00000000000000python-oerplib-0.8.4/oerplib/tools/__init__.py000066400000000000000000000100071245703354300214070ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module contains the :class:`Config ` class which manage the configuration related to an instance of :class:`OERP `, and some useful helper functions used internally in `OERPLib`. """ import collections import re MATCH_VERSION = re.compile(r'[^\d.]') class Config(collections.MutableMapping): """Class which manage the configuration of an :class:`OERP ` instance. .. note:: This class have to be used through the :attr:`oerplib.OERP.config` property. >>> import oerplib >>> oerp = oerplib.OERP('localhost') >>> type(oerp.config) """ def __init__(self, oerp, options): super(Config, self).__init__() self._oerp = oerp self._options = options or {} def __getitem__(self, key): return self._options[key] def __setitem__(self, key, value): """Handle ``timeout`` option to set the timeout on the connector.""" if key == 'timeout': self._oerp._connector.timeout = value self._options[key] = value def __delitem__(self, key): # TODO raise exception pass def __iter__(self): return self._options.__iter__() def __len__(self): return len(self._options) def __str__(self): return self._options.__str__() def __repr__(self): return self._options.__repr__() def clean_version(version): """Clean a version string. >>> from oerplib.tools import clean_version >>> clean_version('7.0alpha-20121206-000102') '7.0' :return: a cleaner version string """ version = MATCH_VERSION.sub('', version.split('-')[0]) return version def detect_version(server, protocol, port, timeout=120): """ .. deprecated:: 0.8 Try to detect the server version. >>> from oerplib.tools import detect_version >>> detect_version('localhost', 'xmlrpc', 8069) '7.0' :return: the version as string """ from oerplib import rpc # Try to request the server with the last API supported try: con = rpc.PROTOCOLS[protocol]( server, port, protocol, timeout, version=None) version = con.db.server_version() except: # Try with the API of server < 6.1 try: con = rpc.PROTOCOLS[protocol]( server, port, protocol, timeout, version='6.0') version = con.db.server_version() except: # No version detected? Use the magic number in order to ensure the # use of the last API supported version = '42' finally: return clean_version(version) def v(version): """Convert a version string to a tuple. The tuple can be use to compare versions between them. >>> from oerplib.tools import v >>> v('7.0') [7, 0] >>> v('6.1') [6, 1] >>> v('7.0') < v('6.1') False :return: the version as tuple """ return [int(x) for x in clean_version(version).split(".")] # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/oerplib/tools/session.py000066400000000000000000000107741245703354300213460ustar00rootroot00000000000000# -*- coding: UTF-8 -*- ############################################################################## # # OERPLib # Copyright (C) 2013 Sébastien Alix. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ############################################################################## """This module contains some helper functions used to save and load sessions in `OERPLib`. """ import os import stat from ConfigParser import SafeConfigParser from oerplib import error def get_all(rc_file='~/.oerplibrc'): """Return all session configurations from the `rc_file` file. >>> import oerplib >>> oerplib.tools.session.get_all() {'foo': {'protocol': 'xmlrpc', 'user': 'admin', 'timeout': 120, 'database': 'db_name', 'passwd': 'admin', 'type': 'OERP', 'port': 8069, 'server': 'localhost'}} """ conf = SafeConfigParser() conf.read([os.path.expanduser(rc_file)]) sessions = {} for name in conf.sections(): sessions[name] = { 'type': conf.get(name, 'type'), 'server': conf.get(name, 'server'), 'protocol': conf.get(name, 'protocol'), 'port': conf.getint(name, 'port'), 'timeout': conf.getint(name, 'timeout'), 'user': conf.get(name, 'user'), 'passwd': conf.get(name, 'passwd'), 'database': conf.get(name, 'database'), } return sessions def get(name, rc_file='~/.oerplibrc'): """Return the session configuration identified by `name` from the `rc_file` file. >>> import oerplib >>> oerplib.tools.session.get('foo') {'protocol': 'xmlrpc', 'user': 'admin', 'timeout': 120, 'database': 'db_name', 'passwd': 'admin', 'type': 'OERP', 'port': 8069, 'server': 'localhost'} :raise: :class:`oerplib.error.Error` """ conf = SafeConfigParser() conf.read([os.path.expanduser(rc_file)]) if not conf.has_section(name): raise error.Error( "'{0}' session does not exist".format(name)) return { 'type': conf.get(name, 'type'), 'server': conf.get(name, 'server'), 'protocol': conf.get(name, 'protocol'), 'port': conf.getint(name, 'port'), 'timeout': conf.getint(name, 'timeout'), 'user': conf.get(name, 'user'), 'passwd': conf.get(name, 'passwd'), 'database': conf.get(name, 'database'), } #def list(rc_file='~/.oerplibrc'): # """Return a list of all sessions available in the # `rc_file` file. # """ # conf = SafeConfigParser() # conf.read([os.path.expanduser(rc_file)]) # # TODO # return conf.sections() def save(name, data, rc_file='~/.oerplibrc'): """Save the `data` session configuration under the name `name` in the `rc_file` file. >>> import oerplib >>> oerplib.tools.session.save('foo', {'type': 'OERP', 'server': 'localhost', 'protocol': 'xmlrpc', 'port': 8069, 'timeout': 120, 'user': 'admin', 'passwd': 'admin', 'database': 'db_name'}) """ conf = SafeConfigParser() conf.read([os.path.expanduser(rc_file)]) if not conf.has_section(name): conf.add_section(name) for k, v in data.iteritems(): conf.set(name, k, str(v)) with open(os.path.expanduser(rc_file), 'wb') as file_: os.chmod(os.path.expanduser(rc_file), stat.S_IREAD | stat.S_IWRITE) conf.write(file_) def remove(name, rc_file='~/.oerplibrc'): """Remove the session configuration identified by `name` from the `rc_file` file. >>> import oerplib >>> oerplib.tools.session.remove('foo') :raise: :class:`oerplib.error.Error` """ conf = SafeConfigParser() conf.read([os.path.expanduser(rc_file)]) if not conf.has_section(name): raise error.Error( "'{0}' session does not exist".format(name)) conf.remove_section(name) with open(os.path.expanduser(rc_file), 'wb') as file_: conf.write(file_) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/setup.py000066400000000000000000000047121245703354300162220ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- import os from distutils.core import setup name = 'OERPLib' version = '0.8.4' description = ("OERPLib is a Python module providing an easy way to " "pilot your OpenERP and Odoo servers through RPC.") keywords = ("openerp odoo server client xml-rpc xmlrpc net-rpc netrpc " "oerplib communication lib library python service web webservice") author = "ABF Osiell - Sebastien Alix" author_email = 'sebastien.alix@osiell.com' url = 'http://pythonhosted.org/OERPLib/' download_url = 'http://pypi.python.org/packages/source/O/OERPLib/OERPLib-%s.tar.gz' % version license = 'LGPL v3' doc_build_dir = 'doc/build' doc_source_dir = 'doc/source' cmdclass = {} command_options = {} # 'build_doc' option try: from sphinx.setup_command import BuildDoc if not os.path.exists(doc_build_dir): os.mkdir(doc_build_dir) cmdclass.update({'build_doc': BuildDoc}) command_options.update({ 'build_doc': { #'project': ('setup.py', name), 'version': ('setup.py', version), 'release': ('setup.py', version), 'source_dir': ('setup.py', doc_source_dir), 'build_dir': ('setup.py', doc_build_dir), 'builder': ('setup.py', 'html'), }}) except Exception: print("No Sphinx module found. You have to install Sphinx " "to be able to generate the documentation.") setup(name=name, version=version, description=description, long_description=open('README.rst').read(), keywords=keywords, author=author, author_email=author_email, url=url, download_url=download_url, packages=['oerplib', 'oerplib.rpc', 'oerplib.service', 'oerplib.service.osv', 'oerplib.service.inspect', 'oerplib.tools'], license=license, cmdclass=cmdclass, command_options=command_options, classifiers=[ "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: Implementation :: CPython", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Topic :: Software Development :: Libraries :: Python Modules", ], ) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/000077500000000000000000000000001245703354300156465ustar00rootroot00000000000000python-oerplib-0.8.4/tests/args.py000066400000000000000000000035001245703354300171520ustar00rootroot00000000000000# -*- coding: UTF-8 -*- import argparse _parser = argparse.ArgumentParser(description="Run tests for OERPLib.") _parser.add_argument('--server', default='localhost', help="Host") #_parser.add_argument('--protocol', default='xmlrpc', # help="Protocol") #_parser.add_argument('--port', default='8069', # help="Port") _parser.add_argument('--test_xmlrpc', action='store_true', help="Test the XML-RPC protocol") _parser.add_argument('--xmlrpc_port', default='8069', help="Port to use with the XML-RPC protocol") _parser.add_argument('--test_netrpc', action='store_true', help="Test the NET-RPC protocol") _parser.add_argument('--netrpc_port', default='8070', help="Port to use with the NET-RPC protocol") _parser.add_argument('--version', default=None, help="OpenERP version used") _parser.add_argument('--super_admin_passwd', default='admin', help="Super administrator password") _parser.add_argument('--database', default='oerplib_test', help="Name of the database") _parser.add_argument('--user', default='admin', help="User login") _parser.add_argument('--passwd', default='admin', help="User password") _parser.add_argument('--create_db', action='store_true', help="Create a database for tests") _parser.add_argument('--drop_db', action='store_true', help="Drop the created database. " "Works only with --create_db") _parser.add_argument('--verbosity', default=2, type=int, help="Output verbosity of unittest") ARGS = _parser.parse_args() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/runtests.py000077500000000000000000000063361245703354300201220ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS from test_tools import TestTools from test_init import TestInit from test_login import TestLogin from test_db_create import TestDBCreate from test_db import TestDB from test_db_drop import TestDBDrop from test_execute import TestExecute from test_execute_kw import TestExecuteKw from test_browse import TestBrowse from test_osv import TestOSV from test_timeout import TestTimeout from test_session import TestSession from test_inspect import TestInspect from oerplib.tools import v if __name__ == '__main__': suite = unittest.TestSuite() #--------------- #- First Tests - #--------------- # 1) Test oerplib.tools loader = unittest.TestLoader().loadTestsFromTestCase(TestTools) suite.addTest(loader) # 2) Test OERP.__init__ loader = unittest.TestLoader().loadTestsFromTestCase(TestInit) suite.addTest(loader) # 3) Test OERP.db (create the database) if ARGS.create_db: loader = unittest.TestLoader().loadTestsFromTestCase(TestDBCreate) suite.addTest(loader) else: print("-- TestDBCreate skipped --") # 4) Test OERP.login loader = unittest.TestLoader().loadTestsFromTestCase(TestLogin) suite.addTest(loader) #--------- #- Tests - #--------- # Test OERP.db loader = unittest.TestLoader().loadTestsFromTestCase(TestDB) suite.addTest(loader) # Test OERP.execute and OERP.execute_kw loader = unittest.TestLoader().loadTestsFromTestCase(TestExecute) suite.addTest(loader) loader = unittest.TestLoader().loadTestsFromTestCase(TestExecuteKw) suite.addTest(loader) # Test OERP.browse loader = unittest.TestLoader().loadTestsFromTestCase(TestBrowse) suite.addTest(loader) # Test OERP.get loader = unittest.TestLoader().loadTestsFromTestCase(TestOSV) suite.addTest(loader) # Test socket timeout loader = unittest.TestLoader().loadTestsFromTestCase(TestTimeout) suite.addTest(loader) # Test session management loader = unittest.TestLoader().loadTestsFromTestCase(TestSession) suite.addTest(loader) # Test inspect service loader = unittest.TestLoader().loadTestsFromTestCase(TestInspect) suite.addTest(loader) #--------------- #- Final Tests - #--------------- # Test OERP.db (drop the database) if ARGS.create_db and ARGS.drop_db: loader = unittest.TestLoader().loadTestsFromTestCase(TestDBDrop) suite.addTest(loader) else: print("-- TestDBDrop skipped --") # Run all tests if ARGS.test_xmlrpc: print("-- RUN (XMLRPC) --") ARGS.protocol = 'xmlrpc' ARGS.port = int(ARGS.xmlrpc_port) unittest.TextTestRunner(verbosity=ARGS.verbosity).run(suite) if ARGS.test_netrpc: print("-- RUN (NETRPC) --") ARGS.protocol = 'netrpc' ARGS.port = int(ARGS.netrpc_port) unittest.TextTestRunner(verbosity=ARGS.verbosity).run(suite) if not ARGS.test_xmlrpc and not ARGS.test_netrpc: print("-- NO TEST --") print("Please use '--test_xmlrpc' and/or '--test_netrpc' option.") # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_browse.py000066400000000000000000000323651245703354300205710ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except ImportError: import unittest import datetime from args import ARGS import oerplib from oerplib.tools import v class TestBrowse(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) def test_browse_with_one_id(self): # Check the result returned result = self.oerp.browse('res.users', self.user.id) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(self.user, result) # With context context = self.oerp.execute('res.users', 'context_get') result = self.oerp.browse('res.users', self.user.id, context) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(self.user, result) def test_browse_with_ids(self): # Iteration for result in self.oerp.browse('res.users', [self.user.id]): self.assertEqual(self.user, result) user_ids = self.oerp.search('res.users', []) for result in self.oerp.browse('res.users', user_ids): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) # With context context = self.oerp.execute('res.users', 'context_get') for result in self.oerp.browse('res.users', user_ids, context): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) def test_browse_with_id_false(self): # Check the result returned result = self.oerp.browse('res.users', False) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(False, result.id) # With context context = self.oerp.execute('res.users', 'context_get') result = self.oerp.browse('res.users', False, context) self.assertIsInstance(result, oerplib.service.osv.browse.BrowseRecord) self.assertEqual(False, result.id) def test_browse_with_wrong_id(self): # Handle exception (execute a 'browse' with wrong ID) self.assertRaises( oerplib.error.RPCError, self.oerp.browse, 'res.users', 999999999) def test_browse_without_args(self): # Handle exception (execute a 'browse' without args) self.assertRaises( TypeError, self.oerp.browse, 'res.users') def test_browse_with_wrong_args(self): # Handle exception (execute a 'browse' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.browse, 'res.users', "wrong_arg") # Wrong arg def test_reset(self): # Check the result returned self.user.name = "Charly" self.oerp.reset(self.user) self.assertEqual(self.user.name, "Administrator") def test_refresh(self): # Check the result returned self.user.name = "Charly" self.oerp.refresh(self.user) self.assertEqual(self.user.name, "Administrator") def test_write_record_char(self): backup_name = self.user.name self.user.name = "Charly" self.oerp.write_record(self.user) self.assertEqual(self.user.name, "Charly") self.user.name = backup_name self.oerp.write_record(self.user) self.assertEqual(self.user.name, backup_name) def test_write_record_boolean(self): self.user.active = False self.user.active = True self.oerp.write_record(self.user) self.assertEqual(self.user.active, True) def test_write_record_float(self): partner = self.user.company_id.partner_id partner.credit_limit = False self.oerp.write_record(partner) self.assertEqual(partner.credit_limit, 0.0) partner.credit_limit = 0.0 self.oerp.write_record(partner) self.assertEqual(partner.credit_limit, 0.0) def test_write_record_integer(self): cur = self.oerp.browse('res.currency', 1) backup_accuracy = cur.accuracy cur.accuracy = False self.oerp.write_record(cur) self.assertEqual(cur.accuracy, 0) cur.accuracy = backup_accuracy self.oerp.write_record(cur) self.assertEqual(cur.accuracy, backup_accuracy) def test_write_record_selection(self): self.user.context_tz = False self.oerp.write_record(self.user) self.assertEqual(self.user.context_tz, False) self.user.context_tz = 'Europe/Paris' self.oerp.write_record(self.user) self.assertEqual(self.user.context_tz, 'Europe/Paris') def test_write_record_date(self): partner = self.user.company_id.partner_id partner.date = False self.oerp.write_record(partner) self.assertEqual(partner.date, False) partner.date = '2012-01-01' self.oerp.write_record(partner) self.assertEqual(partner.date, datetime.date(2012, 1, 1)) partner.date = datetime.date(2012, 1, 1) self.oerp.write_record(partner) self.assertEqual(partner.date, datetime.date(2012, 1, 1)) #def test_write_record_datetime(self): # # No common model found in every versions of OpenERP with a # # fields.datetime writable # pass def test_write_record_many2many(self): backup_groups = list(self.user.groups_id) # False self.user.groups_id = False self.assertEqual(list(self.user.groups_id), []) self.oerp.reset(self.user) # [] self.user.groups_id = [] self.assertEqual(list(self.user.groups_id), []) self.oerp.reset(self.user) # [(6, 0, IDS)] self.user.groups_id = [(6, 0, [1, 2])] self.assertIn(1, [grp.id for grp in self.user.groups_id]) self.assertIn(2, [grp.id for grp in self.user.groups_id]) self.oerp.write_record(self.user) self.assertIn(1, [grp.id for grp in self.user.groups_id]) self.assertIn(2, [grp.id for grp in self.user.groups_id]) # Operator += grp_obj = self.oerp.get('res.groups') self.user.groups_id += 4 # ID self.assertIn(4, [grp.id for grp in self.user.groups_id]) self.oerp.reset(self.user) self.user.groups_id += grp_obj.browse(4) # Browse record self.assertIn(4, [grp.id for grp in self.user.groups_id]) self.oerp.reset(self.user) self.user.groups_id += [4, 5] # List of IDs group_ids = [grp.id for grp in self.user.groups_id] self.assertIn(4, group_ids) self.assertIn(5, group_ids) self.oerp.reset(self.user) self.user.groups_id += list(grp_obj.browse([4, 5])) # List of browse records group_ids = [grp.id for grp in self.user.groups_id] self.assertIn(4, group_ids) self.assertIn(5, group_ids) self.oerp.write_record(self.user) self.assertIn(4, group_ids) self.assertIn(5, group_ids) self.user.groups_id = False # Starting with no value self.user.groups_id += 4 self.user.groups_id += [5] self.assertIn(4, [grp.id for grp in self.user.groups_id]) self.assertIn(5, [grp.id for grp in self.user.groups_id]) # Operator -= self.user.groups_id -= 1 # ID self.assertNotIn(1, [grp.id for grp in self.user.groups_id]) self.oerp.reset(self.user) self.user.groups_id -= grp_obj.browse(1) # Browse record self.assertNotIn(1, [grp.id for grp in self.user.groups_id]) self.oerp.reset(self.user) self.user.groups_id -= [1, 2] # List of IDs group_ids = [grp.id for grp in self.user.groups_id] self.assertNotIn(1, group_ids) self.assertNotIn(2, group_ids) self.oerp.reset(self.user) self.user.groups_id -= list(grp_obj.browse([1, 2])) # List of browse records group_ids = [grp.id for grp in self.user.groups_id] self.assertNotIn(1, group_ids) self.assertNotIn(2, group_ids) self.oerp.write_record(self.user) self.assertNotIn(1, group_ids) self.assertNotIn(2, group_ids) self.user.groups_id = False # Starting with no value self.user.groups_id -= 1 self.user.groups_id -= [5] self.assertEqual([], [grp.id for grp in self.user.groups_id]) # Restore the original value self.user.groups_id = backup_groups self.assertEqual(list(self.user.groups_id), backup_groups) self.oerp.write_record(self.user) self.assertEqual(list(self.user.groups_id), backup_groups) def test_write_record_many2one(self): self.user.action_id = 1 self.assertEqual(self.user.action_id.id, 1) self.oerp.write_record(self.user) self.assertEqual(self.user.action_id.id, 1) action = self.oerp.get('ir.actions.actions').browse(1) self.user.action_id = action self.oerp.write_record(self.user) self.assertEqual(self.user.action_id.id, 1) # False self.user.action_id = False self.oerp.write_record(self.user) if v(self.oerp.version)[:2] == v('5.0'): self.assertEqual(self.user.action_id.id, 1) elif v(self.oerp.version)[:2] == v('6.0'): self.assertEqual(self.user.action_id, False) elif v(self.oerp.version)[:2] == v('6.1'): self.assertEqual(self.user.action_id, False) else: self.assertEqual(self.user.action_id, False) def test_write_record_one2many(self): partner_obj = self.oerp.get('res.partner') partner = partner_obj.browse(1) backup_childs = [acc for acc in partner.child_ids] p1_id = partner_obj.create({'name': "Test 1"}) p2_id = partner_obj.create({'name': "Test 2"}) # False partner.child_ids = False self.assertEqual(list(partner.child_ids), []) self.oerp.write_record(partner) self.assertEqual(list(partner.child_ids), []) # [] partner.child_ids = [] self.assertEqual(list(partner.child_ids), []) self.oerp.write_record(partner) self.assertEqual(list(partner.child_ids), []) # [(6, 0, IDS)] partner.child_ids = [(6, 0, [p1_id])] self.assertEqual([acc.id for acc in partner.child_ids], [p1_id]) self.oerp.write_record(partner) self.assertEqual([acc.id for acc in partner.child_ids], [p1_id]) # Operator += partner.child_ids += p2_id # ID self.assertIn(p2_id, [pt.id for pt in partner.child_ids]) self.oerp.reset(partner) partner.child_ids += partner_obj.browse(p2_id) # Browse record self.assertIn(p2_id, [pt.id for pt in partner.child_ids]) self.oerp.reset(partner) partner.child_ids += [p1_id, p2_id] # List of IDs partner_ids = [pt.id for pt in partner.child_ids] self.assertIn(p1_id, partner_ids) self.assertIn(p2_id, partner_ids) self.oerp.reset(partner) partner.child_ids += list(partner_obj.browse([p1_id, p2_id])) # List of browse records partner_ids = [pt.id for pt in partner.child_ids] self.assertIn(p1_id, partner_ids) self.assertIn(p2_id, partner_ids) self.oerp.write_record(partner) self.assertIn(p1_id, partner_ids) self.assertIn(p2_id, partner_ids) partner.child_ids = False # Starting with no value partner.child_ids += p1_id partner.child_ids += [p2_id] self.assertIn(p1_id, [pt.id for pt in partner.child_ids]) self.assertIn(p2_id, [pt.id for pt in partner.child_ids]) # Operator -= partner.child_ids -= p1_id # ID self.assertNotIn(p1_id, [pt.id for pt in partner.child_ids]) self.oerp.reset(partner) partner.child_ids -= partner_obj.browse(p1_id) # Browse record self.assertNotIn(p1_id, [pt.id for pt in partner.child_ids]) self.oerp.reset(partner) partner.child_ids -= [p1_id, p2_id] # List of IDs partner_ids = [pt.id for pt in partner.child_ids] self.assertNotIn(p1_id, partner_ids) self.assertNotIn(p2_id, partner_ids) self.oerp.reset(partner) partner.child_ids -= list(partner_obj.browse([p1_id, p2_id])) # List of browse records partner_ids = [pt.id for pt in partner.child_ids] self.assertNotIn(p1_id, partner_ids) self.assertNotIn(p2_id, partner_ids) self.oerp.write_record(partner) self.assertNotIn(p1_id, partner_ids) self.assertNotIn(p2_id, partner_ids) partner.child_ids = False # Starting with no value partner.child_ids -= p1_id partner.child_ids -= [p2_id] self.assertEqual([], [pt.id for pt in partner.child_ids]) # Restore the original value partner.child_ids = backup_childs self.assertEqual(list(partner.child_ids), backup_childs) self.oerp.write_record(partner) self.assertEqual(list(partner.child_ids), backup_childs) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_db.py000066400000000000000000000031251245703354300176450ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from datetime import datetime from args import ARGS import oerplib class TestDB(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.databases = [] def test_db_list(self): res = self.oerp.db.list() self.assertIsInstance(res, list) def test_db_list_lang(self): res = self.oerp.db.list_lang() self.assertIsInstance(res, list) def test_db_dump(self): dump = self.oerp.db.dump(ARGS.super_admin_passwd, ARGS.database) self.assertIsNotNone(dump) def test_db_restore_new_database(self): dump = self.oerp.db.dump(ARGS.super_admin_passwd, ARGS.database) date = datetime.strftime(datetime.today(), '%Y-%m-%d_%Hh%Mm%S') new_database = "%s_%s" % (ARGS.database, date) self.oerp.db.restore(ARGS.super_admin_passwd, new_database, dump) self.databases.append(new_database) def test_db_restore_existing_database(self): dump = self.oerp.db.dump(ARGS.super_admin_passwd, ARGS.database) self.assertRaises( oerplib.error.RPCError, self.oerp.db.restore, ARGS.super_admin_passwd, ARGS.database, dump) def tearDown(self): for db in self.databases: try: self.oerp.db.drop(ARGS.super_admin_passwd, db) except: pass # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_db_create.py000066400000000000000000000024551245703354300211750ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib from oerplib.tools import v class TestDBCreate(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) def test_db_create(self): if ARGS.database not in self.oerp.db.list(): if v(self.oerp.version) >= v('8.0'): res = self.oerp.db.create_database( ARGS.super_admin_passwd, ARGS.database, False, 'en_US', ARGS.passwd) self.assertTrue(res) else: res = self.oerp.db.create_and_wait( ARGS.super_admin_passwd, ARGS.database, False, 'en_US', ARGS.passwd) self.assertIsInstance(res, list) self.assertNotEqual(res, list()) self.assertEqual(res[0]['login'], 'admin') self.assertEqual(res[0]['password'], ARGS.passwd) import time time.sleep(10) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_db_drop.py000066400000000000000000000020151245703354300206660ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib from oerplib.tools import v class TestDBDrop(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) def test_db_drop_existing_database(self): res = self.oerp.db.drop(ARGS.super_admin_passwd, ARGS.database) self.assertTrue(res) db_list = self.oerp.db.list() self.assertNotIn(ARGS.database, db_list) def test_db_drop_no_existing_database(self): if v(self.oerp.version) >= v('6.1'): res = self.oerp.db.drop(ARGS.super_admin_passwd, 'fake_db_name') self.assertFalse(res) else: self.assertRaises( oerplib.error.RPCError, self.oerp.db.drop, ARGS.super_admin_passwd, 'fake_db_name') # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_execute.py000066400000000000000000000062771245703354300207350ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import numbers import time from args import ARGS import oerplib class TestExecute(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) # ------ # Search # ------ def test_execute_search_with_good_args(self): # Check the result returned result = self.oerp.execute('res.users', 'search', []) self.assertIsInstance(result, list) self.assertIn(self.user.id, result) result = self.oerp.execute('res.users', 'search', [('id', '=', self.user.id)]) self.assertIn(self.user.id, result) self.assertEqual(result[0], self.user.id) def test_execute_search_without_args(self): # Handle exception (execute a 'search' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'search') def test_execute_search_with_wrong_args(self): # Handle exception (execute a 'search' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'search', False) # Wrong arg def test_execute_search_with_wrong_model(self): # Handle exception (execute a 'search' with a wrong model) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'wrong.model', # Wrong model 'search', []) def test_execute_search_with_wrong_method(self): # Handle exception (execute a 'search' with a wrong method) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'wrong_method', # Wrong method []) # ------ # Create # ------ def test_execute_create_with_good_args(self): login = "%s_%s" % ("foobar", time.time()) # Check the result returned result = self.oerp.execute( 'res.users', 'create', {'name': login, 'login': login}) self.assertIsInstance(result, numbers.Number) # Handle exception (create another user with the same login) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'create', {'name': login, 'login': login}) def test_execute_create_without_args(self): # Handle exception (execute a 'create' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'create') def test_execute_create_with_wrong_args(self): # Handle exception (execute a 'create' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute, 'res.users', 'create', False) # Wrong arg # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_execute_kw.py000066400000000000000000000067671245703354300214420ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import numbers import time from args import ARGS import oerplib from oerplib.tools import v class TestExecuteKw(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) if v(self.oerp.version) < v('6.1'): raise unittest.SkipTest( "The targetted OpenERP server does not support the " "'execute_kw()' method.") self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) # ------ # Search # ------ def test_execute_kw_search_with_good_args(self): # Check the result returned result = self.oerp.execute_kw('res.users', 'search', [[]], {}) self.assertIsInstance(result, list) self.assertIn(self.user.id, result) result = self.oerp.execute_kw( 'res.users', 'search', [[('id', '=', self.user.id)]], {'order': 'name'}) self.assertIn(self.user.id, result) self.assertEqual(result[0], self.user.id) def test_execute_kw_search_without_args(self): # Handle exception (execute a 'search' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'search') def test_execute_kw_search_with_wrong_args(self): # Handle exception (execute a 'search' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'search', False, False) # Wrong args def test_execute_kw_search_with_wrong_model(self): # Handle exception (execute a 'search' with a wrong model) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'wrong.model', # Wrong model 'search', [[]], {}) def test_execute_kw_search_with_wrong_method(self): # Handle exception (execute a 'search' with a wrong method) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'wrong_method', # Wrong method [[]], {}) # ------ # Create # ------ def test_execute_kw_create_with_good_args(self): login = "%s_%s" % ("foobar", time.time()) # Check the result returned result = self.oerp.execute_kw( 'res.users', 'create', [{'name': login, 'login': login}]) self.assertIsInstance(result, numbers.Number) # Handle exception (create another user with the same login) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'create', [{'name': login, 'login': login}]) def test_execute_kw_create_without_args(self): # Handle exception (execute a 'create' without args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'create') def test_execute_kw_create_with_wrong_args(self): # Handle exception (execute a 'create' with wrong args) self.assertRaises( oerplib.error.RPCError, self.oerp.execute_kw, 'res.users', 'create', True, True) # Wrong args # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_init.py000066400000000000000000000023451245703354300202260ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib class TestInit(unittest.TestCase): def test_init1(self): # Server + Database + Protocol + Port oerp = oerplib.OERP(ARGS.server, ARGS.database, ARGS.protocol, ARGS.port) self.assertIsInstance(oerp, oerplib.OERP) self.assertIsNotNone(oerp) self.assertEqual(oerp.server, ARGS.server) self.assertEqual(oerp.database, ARGS.database) self.assertEqual(oerp.protocol, ARGS.protocol) self.assertEqual(oerp.port, ARGS.port) def test_init2(self): # Server + Database + Protocol + Port + Timeout oerp = oerplib.OERP(ARGS.server, ARGS.database, ARGS.protocol, ARGS.port, 42) self.assertIsInstance(oerp, oerplib.OERP) self.assertIsNotNone(oerp) self.assertEqual(oerp.server, ARGS.server) self.assertEqual(oerp.database, ARGS.database) self.assertEqual(oerp.protocol, ARGS.protocol) self.assertEqual(oerp.port, ARGS.port) self.assertEqual(oerp.config['timeout'], 42) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_inspect.py000066400000000000000000000063541245703354300207340ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib class TestInspect(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) def test_relations(self): graph = self.oerp.inspect.relations( ['res.users'], maxdepth=1, whitelist=['*'], blacklist=[], attrs_whitelist=['*'], attrs_blacklist=[]) self.assertIsInstance(graph, oerplib.service.inspect.relations.Relations) def test_relations_maxdepth_null(self): graph = self.oerp.inspect.relations(['res.users'], maxdepth=0) self.assertIsInstance(graph, oerplib.service.inspect.relations.Relations) def test_relations_maxdepth_negative(self): graph = self.oerp.inspect.relations(['res.users'], maxdepth=-1) self.assertIsInstance(graph, oerplib.service.inspect.relations.Relations) def test_dependencies(self): graph = self.oerp.inspect.dependencies() self.assertIsInstance( graph, oerplib.service.inspect.dependencies.Dependencies) def test_dependencies_with_module(self): graph = self.oerp.inspect.dependencies(['base']) self.assertIsInstance( graph, oerplib.service.inspect.dependencies.Dependencies) def test_dependencies_with_models(self): graph = self.oerp.inspect.dependencies(models=['res.partner*']) self.assertIsInstance( graph, oerplib.service.inspect.dependencies.Dependencies) def test_dependencies_with_wrong_module(self): self.assertRaises( oerplib.error.InternalError, self.oerp.inspect.dependencies, ['wrong_module']) def test_dependencies_with_module_and_models(self): graph = self.oerp.inspect.dependencies(['base'], ['res.partner*']) self.assertIsInstance( graph, oerplib.service.inspect.dependencies.Dependencies) def test_dependencies_with_module_and_models_restricted(self): graph = self.oerp.inspect.dependencies( ['base'], ['res.partner*'], restrict=True) self.assertIsInstance( graph, oerplib.service.inspect.dependencies.Dependencies) def test_dependencies_with_module_and_models_blacklist(self): graph = self.oerp.inspect.dependencies( ['base'], ['res.partner*'], ['res.partner.bank']) self.assertIsInstance( graph, oerplib.service.inspect.dependencies.Dependencies) def test_dependencies_with_module_and_models_blacklist_restricted(self): graph = self.oerp.inspect.dependencies( ['base'], ['res.partner*'], ['res.partner.bank'], restrict=True) self.assertIsInstance( graph, oerplib.service.inspect.dependencies.Dependencies) def test_scan_on_change(self): res = self.oerp.inspect.scan_on_change(['res.users']) self.assertIsInstance(res, dict) res = self.oerp.inspect.scan_on_change(['res.users', 'res.partner']) self.assertIsInstance(res, dict) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_login.py000066400000000000000000000036351245703354300203760ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib from oerplib.service import osv class TestLogin(unittest.TestCase): def test_oerp_no_db_login_db(self): # OERP no database + login database oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) user = oerp.login(ARGS.user, ARGS.passwd, ARGS.database) self.assertIsNotNone(user) self.assertIsInstance(user, osv.BrowseRecord) self.assertEqual(oerp.user, user) self.assertEqual(oerp.database, ARGS.database) def test_oerp_no_db_login_no_db(self): # OERP no database + login no database => Error oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.assertRaises( oerplib.error.Error, oerp.login, ARGS.user, ARGS.passwd) def test_oerp_db_login_no_db(self): # OERP database + login no database oerp = oerplib.OERP( ARGS.server, ARGS.database, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) user = oerp.login(ARGS.user, ARGS.passwd) self.assertIsNotNone(user) self.assertIsInstance(user, osv.BrowseRecord) self.assertEqual(oerp.user, user) def test_oerp_db_login_db(self): # OERP database + login database oerp = oerplib.OERP( ARGS.server, ARGS.database, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) user = oerp.login(ARGS.user, ARGS.passwd, ARGS.database) self.assertIsNotNone(user) self.assertIsInstance(user, osv.BrowseRecord) self.assertEqual(oerp.user, user) self.assertEqual(oerp.database, ARGS.database) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_osv.py000066400000000000000000000042111245703354300200640ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS import oerplib class TestOSV(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) def test_model(self): # Check the result returned self.oerp.get('res.users') def test_model_method(self): # Check the result returned model = self.oerp.get('res.users') model.name_get(self.user.id) self.oerp.get('ir.sequence').get('fake.code') # Return False def test_model_method_without_args(self): # Handle exception (execute a 'name_get' with without args) model = self.oerp.get('res.users') self.assertRaises( oerplib.error.RPCError, model.name_get) def test_model_method_with_wrong_args(self): # Handle exception (execute a 'search' with wrong args) model = self.oerp.get('res.users') self.assertRaises( oerplib.error.RPCError, model.search, False) # Wrong arg def test_model_browse_with_one_id(self): # Check the result returned model = self.oerp.get('res.users') user = model.browse(self.user.id) self.assertEqual(user, self.user) def test_model_browse_with_ids(self): # Iteration for result in self.oerp.get('res.users').browse([self.user.id]): self.assertEqual(self.user, result) user_ids = self.oerp.search('res.users', []) for result in self.oerp.get('res.users').browse(user_ids): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) # With context context = self.oerp.execute('res.users', 'context_get') for result in self.oerp.get('res.users').browse(user_ids, context): self.assertIsInstance( result, oerplib.service.osv.browse.BrowseRecord) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_session.py000066400000000000000000000064161245703354300207510ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import tempfile import os from args import ARGS import oerplib class TestSession(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) self.session_name = ARGS.database self.file_path = tempfile.mkstemp(suffix='.cfg', prefix='oerplib_')[1] def tearDown(self): os.remove(self.file_path) def test_session_oerp_list(self): result = oerplib.OERP.list(rc_file=self.file_path) self.assertIsInstance(result, list) other_file_path = tempfile.mkstemp()[1] result = oerplib.OERP.list(rc_file=other_file_path) self.assertIsInstance(result, list) def test_session_oerp_save_and_remove(self): self.oerp.save(self.session_name, rc_file=self.file_path) result = oerplib.OERP.list(rc_file=self.file_path) self.assertIn(self.session_name, result) oerplib.OERP.remove(self.session_name, rc_file=self.file_path) def test_session_oerp_load(self): self.oerp.save(self.session_name, rc_file=self.file_path) oerp = oerplib.OERP.load(self.session_name, rc_file=self.file_path) self.assertIsInstance(oerp, oerplib.OERP) self.assertEqual(self.oerp.server, oerp.server) self.assertEqual(self.oerp.port, oerp.port) self.assertEqual(self.oerp.database, oerp.database) self.assertEqual(self.oerp.protocol, oerp.protocol) self.assertEqual(self.oerp.user, oerp.user) oerplib.OERP.remove(self.session_name, rc_file=self.file_path) def test_session_tools_get(self): self.oerp.save(self.session_name, rc_file=self.file_path) data = { 'type': self.oerp.__class__.__name__, 'server': self.oerp.server, 'protocol': self.oerp.protocol, 'port': int(self.oerp.port), 'timeout': self.oerp.config['timeout'], 'user': self.oerp.user.login, 'passwd': self.oerp._password, 'database': self.oerp.database, } result = oerplib.tools.session.get( self.session_name, rc_file=self.file_path) self.assertEqual(data, result) oerplib.OERP.remove(self.session_name, rc_file=self.file_path) def test_session_tools_get_all(self): self.oerp.save(self.session_name, rc_file=self.file_path) data = { self.session_name: { 'type': self.oerp.__class__.__name__, 'server': self.oerp.server, 'protocol': self.oerp.protocol, 'port': int(self.oerp.port), 'timeout': self.oerp.config['timeout'], 'user': self.oerp.user.login, 'passwd': self.oerp._password, 'database': self.oerp.database, } } result = oerplib.tools.session.get_all(rc_file=self.file_path) self.assertIn(self.session_name, result) self.assertEqual(data, result) oerplib.OERP.remove(self.session_name, rc_file=self.file_path) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_timeout.py000066400000000000000000000020731245703354300207470ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest import socket from args import ARGS import oerplib class TestTimeout(unittest.TestCase): def setUp(self): self.oerp = oerplib.OERP( ARGS.server, protocol=ARGS.protocol, port=ARGS.port, version=ARGS.version) self.user = self.oerp.login(ARGS.user, ARGS.passwd, ARGS.database) def test_reduced_timeout(self): ids = self.oerp.search('ir.module.module', []) # Set the timeout self.oerp.config['timeout'] = 0.1 # Execute a time consuming query: handle exception self.assertRaises( socket.timeout, self.oerp.write, 'ir.module.module', ids, {}) def test_increased_timeout(self): # Set the timeout self.oerp.config['timeout'] = 120 # Execute a time consuming query: no exception ids = self.oerp.search('ir.module.module', []) self.oerp.write('ir.module.module', ids, {}) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: python-oerplib-0.8.4/tests/test_tools.py000066400000000000000000000026541245703354300204260ustar00rootroot00000000000000# -*- coding: UTF-8 -*- try: import unittest2 as unittest except: import unittest from args import ARGS from oerplib import tools class TestTools(unittest.TestCase): def test_clean_version_numeric(self): version = tools.clean_version('6.1') self.assertEqual(version, '6.1') def test_clean_version_alphanumeric(self): version = tools.clean_version('7.0alpha-20121206-000102') self.assertEqual(version, '7.0') def test_detect_version(self): version = tools.detect_version( ARGS.server, ARGS.protocol, ARGS.port) self.assertIsInstance(version, str) def test_v_numeric(self): self.assertEqual(tools.v('7.0'), [7, 0]) def test_v_alphanumeric(self): self.assertEqual(tools.v('7.0alpha'), [7, 0]) def test_v_cmp(self): # [(v1, v2, is_inferior), ...] versions = [ ('7.0', '6.1', False), ('6.1', '7.0', True), ('7.0alpha', '6.1', False), ('6.1beta', '7.0', True), ('6.1beta', '5.0.16', False), ('5.0.16alpha', '6.1', True), ('8.0dev-20131102-000101', '7.0-20131014-231047', False), ] for v1, v2, is_inferior in versions: result = tools.v(v1) < tools.v(v2) if is_inferior: self.assertTrue(result) else: self.assertFalse(result) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: