├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── etl.uml ├── etl ├── README ├── __init__.py ├── __openerp__.py ├── action.py ├── data │ ├── README │ ├── action_properties.xml │ ├── action_track.xml │ ├── external_model_properties.xml │ ├── external_model_record_properties.xml │ ├── external_model_record_track.xml │ ├── external_model_track.xml │ ├── field_mapping_properties.xml │ ├── field_mapping_track.xml │ ├── field_properties.xml │ ├── field_track.xml │ ├── manager_properties.xml │ ├── manager_track.xml │ ├── value_mapping_field_detail_properties.xml │ ├── value_mapping_field_detail_track.xml │ ├── value_mapping_field_properties.xml │ ├── value_mapping_field_track.xml │ ├── value_mapping_field_value_properties.xml │ └── value_mapping_field_value_track.xml ├── external_model.py ├── external_model_record.py ├── field.py ├── field_mapping.py ├── i18n │ ├── README │ └── pt.po ├── images │ └── README ├── manager.py ├── report │ ├── README │ └── __init__.py ├── security │ ├── README │ ├── etl_group.xml │ └── ir.model.access.csv ├── static │ └── description │ │ └── icon.png ├── test │ └── README ├── value_mapping_field.py ├── value_mapping_field_detail.py ├── value_mapping_field_value.py ├── view │ ├── action_view.xml │ ├── etl_menuitem.xml │ ├── external_model_record_view.xml │ ├── external_model_view.xml │ ├── field_mapping_view.xml │ ├── field_view.xml │ ├── manager_view.xml │ ├── value_mapping_field_detail_view.xml │ ├── value_mapping_field_value_view.xml │ └── value_mapping_field_view.xml └── wizard │ ├── README │ └── __init__.py └── requirements.txt /.coveragerc: -------------------------------------------------------------------------------- 1 | # Config file .coveragerc 2 | 3 | [report] 4 | include = 5 | */ingadhoc/odoo-etl/* 6 | 7 | omit = 8 | */scenario/* 9 | */scenarios/* 10 | */test/* 11 | */tests/* 12 | *__init__.py 13 | *__openerp__.py 14 | 15 | # Regexes for lines to exclude from consideration 16 | exclude_lines = 17 | # Have to re-enable the standard pragma 18 | pragma: no cover 19 | # Don't complain about null context checking 20 | if context is None: 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | addons: 2 | apt: 3 | packages: 4 | - expect-dev # provides unbuffer utility 5 | - python-lxml # because pip installation is slow 6 | 7 | language: python 8 | 9 | # new options suggested by Alexandre Fayolle 10 | sudo: false 11 | cache: pip 12 | 13 | python: 14 | - "2.7" 15 | 16 | env: 17 | # - VERSION="8.0" LINT_CHECK="1" 18 | - VERSION="8.0" ODOO_REPO="odoo/odoo" LINT_CHECK="0" 19 | - VERSION="8.0" UNIT_TEST="1" LINT_CHECK="0" 20 | 21 | virtualenv: 22 | system_site_packages: true 23 | 24 | install: 25 | # repository requirements 26 | - pip install -r requirements.txt 27 | 28 | - git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools 29 | - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH} 30 | - travis_install_nightly 31 | 32 | script: 33 | - travis_run_tests 34 | 35 | after_success: 36 | coveralls 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: addons 2 | 3 | design/etl.xmi: design/etl.zargo 4 | -echo "REBUILD etl.xmi from etl.zargo. I cant do it" 5 | 6 | addons: etl 7 | 8 | etl: design/etl.uml 9 | xmi2odoo -r -i $< -t addons -v 2 -V 8.0 10 | 11 | clean: 12 | sleep 1 13 | touch design/etl.uml 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ingadhoc/odoo-etl.svg?branch=8.0)](https://travis-ci.org/ingadhoc/odoo-etl) 2 | [![Coverage Status](https://coveralls.io/repos/ingadhoc/odoo-etl/badge.png?branch=8.0)](https://coveralls.io/r/ingadhoc/odoo-etl?branch=8.0) 3 | 4 | odoo-etl (DEPRECEATED) 5 | 6 | IMPORTANT: This project is deprecteade and anymore supported by adhoc 7 | 8 | Odoo data manipulation, like an small ETL (Extract, Transform and Load) for odoo databases. 9 | 10 | The main idea of the project is to give functional users the availability to move data from one odoo database to another odoo database. The design is quite simple, it use native odoo methods (primarily load and export_data). 11 | 12 | The aim of this project is different from odoo migration or OpenUpgrade, it allows to start from a clean database, merging different odoo databases into a single multicompany db, etc. 13 | 14 | This project was developed by a non developer so for sure you are gonna find lot of ugly statements and ideas. 15 | 16 | It was developed using xmi2oerp tool, thanks Cristian Sebastian Rocha for that great work! 17 | 18 | You can see an ugly example video in the following links, in this example we are going to show how to move data from a v6.1 database with demo data to a trunk database without demo data. Links: 19 | * https://www.youtube.com/watch?v=HZQQaNQ9k7U 20 | * https://www.youtube.com/watch?v=VmScwCM3whg 21 | * https://www.youtube.com/watch?v=PS2ShlY1gLI 22 | 23 | Any feedback is welcome, if someone likes the idea, please don't hesitate to contact us so we can work together. 24 | 25 | You can find some kind of tutorial on: https://docs.google.com/document/d/1HWERCUp9rMHqEibT7B_mfu9jePV-FBkYbo--OB0jXqA 26 | 27 | 28 | ## Installation 29 | 30 | ### Dependencies 31 | 32 | sudo pip install -r requirements.txt 33 | 34 | If you don't have Pip, find it here: http://pypi.python.org/pypi/pip 35 | 36 | ## Contributing 37 | We follow some guidelines and advice than [Odoo Argentina](https://github.com/ingadhoc/odoo-argentina/wiki). In summary: 38 | 39 | 1. Fork it! 40 | 2. Create your feature branch: `git checkout -b my-new-feature` 41 | 3. Commit your changes: `git commit -am 'Add some feature'` 42 | 4. Push to the branch: `git push origin my-new-feature` 43 | 5. Submit a pull request :D 44 | 45 | ## Credits 46 |

47 | ADHOC 48 |

49 | **Adhoc SA** - www.adhoc.com.ar 50 | 51 | ## License 52 | 53 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 54 | 55 | 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 Affero General Public License for more details. 56 | 57 | You should have received a copy of the GNU Affero General Public License along with this program. If not, see http://www.gnu.org/licenses/. 58 | -------------------------------------------------------------------------------- /etl/README: -------------------------------------------------------------------------------- 1 | # 2 | # odoo ETL 3 | # Copyright (C) 2015 Ingenieria ADHOC 4 | # No email 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as 8 | # published by the Free Software Foundation, either version 3 of the 9 | # License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | 21 | etl 22 | 23 | odoo ETL 24 | -------------------------------------------------------------------------------- /etl/__init__.py: -------------------------------------------------------------------------------- 1 | import value_mapping_field 2 | import external_model 3 | import external_model_record 4 | import field_mapping 5 | import field 6 | import manager 7 | import value_mapping_field_value 8 | import action 9 | import value_mapping_field_detail 10 | import wizard 11 | import report 12 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 13 | -------------------------------------------------------------------------------- /etl/__openerp__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # 4 | # Copyright (C) 2015 ADHOC SA (http://www.adhoc.com.ar) 5 | # All Rights Reserved. 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as 9 | # published by the Free Software Foundation, either version 3 of the 10 | # License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################## 21 | { 22 | 'active': False, 23 | 'author': u'ADHOC SA', 24 | 'category': u'base.module_category_knowledge_management', 25 | 'data': [ 26 | u'security/etl_group.xml', 27 | u'view/etl_menuitem.xml', 28 | u'view/value_mapping_field_view.xml', 29 | u'view/external_model_view.xml', 30 | u'view/external_model_record_view.xml', 31 | u'view/field_mapping_view.xml', 32 | u'view/field_view.xml', 33 | u'view/action_view.xml', 34 | u'view/manager_view.xml', 35 | u'view/value_mapping_field_value_view.xml', 36 | u'view/value_mapping_field_detail_view.xml', 37 | u'data/value_mapping_field_properties.xml', 38 | u'data/external_model_properties.xml', 39 | u'data/external_model_record_properties.xml', 40 | u'data/field_mapping_properties.xml', 41 | u'data/field_properties.xml', 42 | u'data/manager_properties.xml', 43 | u'data/value_mapping_field_value_properties.xml', 44 | u'data/action_properties.xml', 45 | u'data/value_mapping_field_detail_properties.xml', 46 | u'data/value_mapping_field_track.xml', 47 | u'data/external_model_track.xml', 48 | u'data/external_model_record_track.xml', 49 | u'data/field_mapping_track.xml', 50 | u'data/field_track.xml', 51 | u'data/manager_track.xml', 52 | u'data/value_mapping_field_value_track.xml', 53 | u'data/action_track.xml', 54 | u'data/value_mapping_field_detail_track.xml', 55 | 'security/ir.model.access.csv', 56 | ], 57 | 'depends': ['base'], 58 | 'description': """ 59 | odoo ETL 60 | ======== 61 | Usefull Notes: 62 | -------------- 63 | * It is recommendend to delete all external identifiers on source database for model "res_partner" because when creating a user, odoo simulates partner creation and raise a unique constraint (excepto admin user) 64 | * Also could be recommendend to delete external identifiers related to product and product_temlate (excepto to service product) 65 | * Advisable to configure xmlrpc users to timezone cero to avoid errors 66 | """, 67 | 'installable': True, 68 | 'application': True, 69 | 'license': 'AGPL-3', 70 | 'name': u'odoo ETL', 71 | 'test': [], 72 | 'version': u'1.1', 73 | 'website': 'www.adhoc.com.ar'} 74 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 75 | -------------------------------------------------------------------------------- /etl/action.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, SUPERUSER_ID 7 | import sys 8 | import pytz 9 | from ast import literal_eval 10 | from datetime import datetime 11 | from dateutil import relativedelta 12 | from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT 13 | import logging 14 | _logger = logging.getLogger(__name__) 15 | 16 | 17 | class action(models.Model): 18 | """""" 19 | 20 | _name = 'etl.action' 21 | _description = 'action' 22 | 23 | _order = "sequence" 24 | 25 | blocked = fields.Boolean( 26 | string='Blocked', 27 | copy=False, 28 | ) 29 | sequence = fields.Integer( 30 | string='Sequence' 31 | ) 32 | state = fields.Selection( 33 | [(u'to_analyze', 'to_analyze'), (u'enabled', 'enabled'), (u'disabled', 'disabled'), (u'no_records', 'no_records')], 34 | string='State', 35 | required=True 36 | ) 37 | name = fields.Char( 38 | string='Name', 39 | required=True 40 | ) 41 | source_domain = fields.Char( 42 | string='Source Domain', 43 | required=True, 44 | default='[]' 45 | ) 46 | log = fields.Text( 47 | string='Log' 48 | ) 49 | note = fields.Html( 50 | string='Notes' 51 | ) 52 | repeating_action = fields.Boolean( 53 | string='Repeating Action?', 54 | store=True, 55 | compute='_get_repeating_action', 56 | ) 57 | source_id_exp = fields.Char( 58 | string='source_id_exp', 59 | required=True, 60 | default='id' 61 | ) 62 | target_id_type = fields.Selection( 63 | [(u'source_id', 'source_id'), (u'builded_id', 'builded_id')], 64 | string='Target ID Type', 65 | required=True, 66 | related='manager_id.target_id_type' 67 | ) 68 | from_rec_id = fields.Integer( 69 | string='From Record' 70 | ) 71 | to_rec_id = fields.Integer( 72 | string='To Record' 73 | ) 74 | target_id_prefix = fields.Char( 75 | string='target_id_prefix', 76 | compute='_get_action_prefix' 77 | ) 78 | manager_id = fields.Many2one( 79 | 'etl.manager', 80 | ondelete='cascade', 81 | string='Manager', 82 | required=True 83 | ) 84 | field_mapping_ids = fields.One2many( 85 | 'etl.field_mapping', 86 | 'action_id', 87 | string='Fields Mapping', 88 | copy=False, 89 | ) 90 | source_model_id = fields.Many2one( 91 | 'etl.external_model', 92 | string='Source Model', 93 | required=True, 94 | ondelete='cascade', 95 | ) 96 | target_model_id = fields.Many2one( 97 | 'etl.external_model', 98 | string='Target Model', 99 | ondelete='cascade', 100 | ) 101 | source_records = fields.Integer( 102 | related='source_model_id.records', 103 | readonly=True, 104 | string='Source Records', 105 | ) 106 | target_records = fields.Integer( 107 | related='target_model_id.records', 108 | readonly=True, 109 | string='Target Records', 110 | ) 111 | 112 | _constraints = [ 113 | ] 114 | 115 | @api.one 116 | @api.depends( 117 | 'source_model_id.model','target_id_type' 118 | ) 119 | def _get_action_prefix(self): 120 | value = False 121 | if self.target_id_type == 'builded_id': 122 | value = self.manager_id.name + '_' + self.source_model_id.model.replace('.','_') 123 | self.target_id_prefix = value 124 | 125 | @api.one 126 | @api.depends( 127 | 'field_mapping_ids.state' 128 | ) 129 | def _get_repeating_action(self): 130 | repeating_action = False 131 | repeating_field_mapps = self.field_mapping_ids.search([ 132 | ('state', '=', 'on_repeating'), 133 | ('action_id', '=', self.id), 134 | ]) 135 | if repeating_field_mapps: 136 | repeating_action = True 137 | self.repeating_action = repeating_action 138 | 139 | @api.multi 140 | def action_block(self): 141 | return self.write({'blocked': True}) 142 | 143 | @api.one 144 | def match_fields(self): 145 | ''' Match fields''' 146 | _logger.info("Matching fields on action %s" % self.name) 147 | migrator_field = self.env['etl.field'] 148 | field_mapping = self.env['etl.field_mapping'] 149 | 150 | # Get disabled and to analize words and fields 151 | field_disable_default = [] 152 | field_analyze_default = [] 153 | field_disable_words = [] 154 | if self.manager_id.field_disable_default: 155 | field_disable_default = literal_eval( 156 | self.manager_id.field_disable_default) 157 | if self.manager_id.field_analyze_default: 158 | field_analyze_default = literal_eval( 159 | self.manager_id.field_analyze_default) 160 | if self.manager_id.field_disable_words: 161 | field_disable_words = literal_eval( 162 | self.manager_id.field_disable_words) 163 | 164 | # get source fields thar are not functions ore one2many 165 | # Function in False or in '_fnct_read' (aparentemente _fnct_read es para campos related y los queremos mapear) 166 | source_domain = [ 167 | ('model_id.id', '=', self.source_model_id.id), 168 | ('ttype', 'not in', ['one2many']), 169 | '|', ('function', 'in', [False, '_fnct_read']), 170 | ('required', '=', 'True')] 171 | source_fields = migrator_field.search(source_domain) 172 | 173 | mapping_data = [] 174 | action_has_active_field = False 175 | for field in source_fields: 176 | # If nothing asserts, choose expresion 177 | mapping_type = 'expression' 178 | 179 | # build source_field with or not /id 180 | source_field_name = field.name 181 | if field.ttype in ['many2one', 'many2many']: 182 | source_field_name += '/id' 183 | 184 | # look for a target field 185 | target_domain = [ 186 | ('model_id.id', '=', self.target_model_id.id), 187 | ('name', '=', field.name)] 188 | target_fields = migrator_field.search(target_domain) 189 | 190 | # check the state 191 | state = 'enabled' 192 | if field.name in field_analyze_default or not target_fields: 193 | state = 'to_analyze' 194 | if field.name in field_disable_default: 195 | state = 'disabled' 196 | else: 197 | for field_disable_word in field_disable_words: 198 | if field.name.find(field_disable_word) == 0: 199 | state = 'disabled' 200 | 201 | # check if is active field 202 | if field.name == 'active': 203 | action_has_active_field = True 204 | 205 | # depending on the target field properties, set some other values 206 | target_field = '' 207 | target_field_name = False 208 | if target_fields: 209 | mapping_type = 'field' 210 | target_field = target_fields[0] 211 | 212 | target_field_name = target_field.name 213 | if target_field.ttype in ['many2one', 'many2many']: 214 | target_field_name += '/id' 215 | if target_field.ttype == 'many2many': 216 | relation = target_field.relation 217 | previus_actions = self.search([ 218 | ('manager_id', '=', self.manager_id.id), 219 | ('sequence', '<', self.sequence), 220 | ('target_model_id.model', '=', relation)]) 221 | if not previus_actions: 222 | state = 'other_class' 223 | elif field.ttype == 'datetime' and target_field.ttype == 'date' or field.ttype == 'date' and target_field.ttype == 'datetime': 224 | mapping_type = 'date_adapt' 225 | elif field.ttype == 'reference': 226 | mapping_type = 'reference' 227 | 228 | # Check if there is any value mapping for current field 229 | value_mapping_field = False 230 | value_mappings = self.env['etl.value_mapping_field'].search([ 231 | ('source_model_id.model', '=', field.relation), 232 | ('manager_id', '=', self.manager_id.id)]) 233 | if value_mappings: 234 | mapping_type = 'value_mapping' 235 | value_mapping_field = value_mappings[0] 236 | 237 | # If field name = 'state' then we upload it on a repeating action so we are sure we can upload all the related data 238 | if field.name == 'state': 239 | state = 'on_repeating' 240 | vals = [ 241 | 'field_mapping_' + str(self.id) + '_' + str(field.id), 242 | state, 243 | field.id, 244 | source_field_name, 245 | mapping_type, 246 | target_field and target_field.id or False, 247 | target_field_name, 248 | self.id, 249 | value_mapping_field and value_mapping_field.id or False] 250 | 251 | # See if mappings have already a blocked mapping created 252 | blocked_fields = field_mapping.search([ 253 | ('blocked', '=', True), 254 | ('action_id', '=', self.id)]) 255 | blocked_field_ext_ids = blocked_fields.export_data( 256 | ['id'])['datas'] 257 | if [vals[0]] in blocked_field_ext_ids: 258 | continue 259 | mapping_data.append(vals) 260 | 261 | # load mapping 262 | mapping_fields = [ 263 | 'id', 264 | 'state', 265 | 'source_field_id/.id', 266 | 'source_field', 267 | 'type', 268 | 'target_field_id/.id', 269 | 'target_field', 270 | 'action_id/.id', 271 | 'value_mapping_field_id/.id'] 272 | _logger.info("Loading mapping fields for action %s" % self.name) 273 | import_result = field_mapping.load(mapping_fields, mapping_data) 274 | vals = {'log': import_result} 275 | 276 | if action_has_active_field and self.source_domain == '[]': 277 | vals['source_domain'] = "['|',('active','=',False),('active','=',True)]" 278 | # write log and domain if active field exist 279 | self.write(vals) 280 | # TODO, si algo anda lento o mal hay que borrar esto. No puedo hacer el check m2o depends ants de tenerlas ordenadas 281 | # return self.check_m2o_depends(cr, uid, ids, context=context) 282 | # return True 283 | 284 | @api.one 285 | def check_m2o_depends(self): 286 | ''' Check if there are fields that should be load in a repeating action 287 | If there is at least one mapping field with repeating, 288 | make the action repeating ''' 289 | data = [] 290 | 291 | # Look for enabled or to analize future actions of this manager and 292 | # this action 293 | future_actions = self.search([ 294 | ('manager_id', '=', self.manager_id.id), 295 | ('sequence', '>=', self.sequence), 296 | ('state', 'in', ['enabled', 'to_analyze'])]) 297 | future_models = [] 298 | for future_action in future_actions: 299 | future_models.append(future_action.source_model_id.model) 300 | 301 | # Look for active fields of this action 302 | field_mapping_domain = [ 303 | ('blocked', '!=', True), 304 | ('action_id', '=', self.id), 305 | ('source_field_id.ttype', '=', 'many2one'), 306 | ('state', 'in', ['enabled', 'to_analyze', 'on_repeating']), 307 | ('type', '=', 'field')] 308 | field_mappings = self.env['etl.field_mapping'].search( 309 | field_mapping_domain) 310 | 311 | # If there are mappings with future depends make them 'on_repeating' 312 | for mapping in field_mappings: 313 | dependency = mapping.source_field_id.relation 314 | if dependency in future_models: 315 | state = 'on_repeating' 316 | vals = [ 317 | 'field_mapping_%s_%s' % ( 318 | str(self.id), 319 | str(mapping.source_field_id.id)), 320 | state] 321 | data.append(vals) 322 | fields = ['id', 'state'] 323 | 324 | # if there is any repeating mapping field, then make action 325 | # 'repeating action' 326 | import_result = self.env['etl.field_mapping'].load(fields, data) 327 | vals = { 328 | 'log': import_result, 329 | } 330 | self.write(vals) 331 | 332 | @api.one 333 | def updata_records_number( 334 | self, source_connection=False, target_connection=False): 335 | if not source_connection or not target_connection: 336 | (source_connection, target_connection) = self.manager_id.open_connections() 337 | self.source_model_id.get_records(source_connection) 338 | self.target_model_id.get_records(target_connection) 339 | 340 | @api.multi 341 | def run_repeated_action( 342 | self, source_connection=False, target_connection=False, 343 | repeated_action=True): 344 | return self.run_action(repeated_action=True) 345 | 346 | @api.multi 347 | def read_source_model( 348 | self, source_connection=False, target_connection=False, 349 | repeated_action=False, context=None): 350 | readed_model = [] 351 | for action in self: 352 | if action.source_model_id.id in readed_model: 353 | continue 354 | _logger.info('Reading model %s' % action.source_model_id.model) 355 | if not source_connection: 356 | (source_connection, target_connection) = action.manager_id.open_connections() 357 | source_model_obj = source_connection.model(action.source_model_id.model) 358 | domain = [] 359 | active_field = action.env['etl.field'].search([ 360 | ('model_id', '=', action.source_model_id.id), 361 | ('name', '=', 'active'), 362 | ], limit=1) 363 | if active_field: 364 | domain = [('active', 'in', [True, False])] 365 | source_model_ids = source_model_obj.search(domain) 366 | source_model_obj.export_data(source_model_ids, ['id']) 367 | readed_model.append(action.source_model_id.id) 368 | 369 | @api.one 370 | def run_action( 371 | self, source_connection=False, target_connection=False, 372 | repeated_action=False): 373 | _logger.info('Actions to run: %i' % len(self.ids)) 374 | action_obj = self.env['etl.action'] 375 | model_obj = self.env['etl.external_model'] 376 | field_mapping_obj = self.env['etl.field_mapping'] 377 | value_mapping_field_detail_obj = self.env['etl.value_mapping_field_detail'] 378 | value_mapping_field_obj = self.env['etl.value_mapping_field'] 379 | if not source_connection or not target_connection: 380 | (source_connection, target_connection) = self.manager_id.open_connections() 381 | # add language to connections context 382 | source_connection.context = {'lang': self.manager_id.source_lang} 383 | target_connection.context = {'lang': self.manager_id.target_lang} 384 | _logger.info('Running action external_model_id.type %s' % self.name) 385 | 386 | domain = literal_eval(self.source_domain) 387 | if self.from_rec_id > 0: 388 | domain.append(('id', '>=', self.from_rec_id)) 389 | if self.to_rec_id > 0: 390 | domain.append(('id', '<=', self.to_rec_id)) 391 | 392 | source_model_obj = source_connection.model(self.source_model_id.model) 393 | target_model_obj = target_connection.model(self.target_model_id.model) 394 | 395 | source_model_ids = source_model_obj.search(domain) 396 | _logger.info('Records to import %i' % len(source_model_ids)) 397 | 398 | _logger.info('Building source data...') 399 | # Empezamos con los campos que definimos como id 400 | source_fields = ['.id', self.source_id_exp] 401 | target_fields = ['id'] 402 | 403 | if repeated_action: 404 | state = 'on_repeating' 405 | else: 406 | state = 'enabled' 407 | 408 | # source fields = enabled (or repeating) and type field 409 | source_fields.extend([x.source_field for x in self.field_mapping_ids if x.state==state and x.type == 'field' and x.source_field_id.ttype != 'many2many' and x.source_field_id.ttype != 'many2one']) 410 | #print source_fields 411 | # target fields = enabled and field then expression then migrated_id 412 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'field' and x.source_field_id.ttype != 'many2many' and x.source_field_id.ttype != 'many2one']) 413 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'field' and x.source_field_id.ttype == 'many2one']) 414 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'field' and x.source_field_id.ttype == 'many2many']) 415 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'value_mapping']) 416 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'date_adapt']) 417 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'expression']) 418 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'migrated_id']) 419 | target_fields.extend([x.target_field for x in self.field_mapping_ids if x.state==state and x.type == 'reference']) 420 | 421 | # Read and append source values of type 'field' and type not m2m 422 | _logger.info('Building none m2m field mapping...') 423 | source_model_data = source_model_obj.export_data( 424 | source_model_ids, source_fields)['datas'] 425 | 426 | _logger.info('Building m2o field mapping...') 427 | # Read and append source values of type 'field' and type m2m 428 | source_fields_m2o = [x.id for x in self.field_mapping_ids if x.state==state and x.type == 'field' and x.source_field_id.ttype == 'many2one'] 429 | for field_id in source_fields_m2o: 430 | field = field_mapping_obj.browse(field_id) 431 | field_model = field.source_field_id.relation 432 | model_id = model_obj.search([('model','=',field_model),('type','ilike','source')]) 433 | field_action = False 434 | if model_id: 435 | field_action = action_obj.search([('source_model_id','=',model_id[0].id)]) 436 | if field_action: 437 | field_action = field_action[0] 438 | for source_data_record in source_model_data: 439 | source_data_m2o = source_model_obj.export_data([int(source_data_record[0])], ['.id', field.source_field, field.source_field.replace('/','.')])['datas'] 440 | new_field_value = False 441 | if field_action.target_id_type == 'source_id' and source_data_m2o[0][1]: 442 | new_field_value = source_data_m2o[0][1] 443 | elif field_action.target_id_type == 'builded_id' and source_data_m2o[0][2]: 444 | new_field_value = '%s_%s' % (field_action.target_id_prefix, str(source_data_m2o[0][2])) 445 | source_data_record.append(new_field_value) 446 | 447 | _logger.info('Building m2m field mapping...') 448 | # Read and append source values of type 'field' and type m2m 449 | source_fields_m2m = [x.id for x in self.field_mapping_ids if x.state==state and x.type == 'field' and x.source_field_id.ttype == 'many2many'] 450 | for field_id in source_fields_m2m: 451 | field = field_mapping_obj.browse(field_id) 452 | field_model = field.source_field_id.relation 453 | model_id = model_obj.search([('model','=',field_model),('type','ilike','source')]) 454 | field_action = False 455 | if model_id: 456 | field_action = action_obj.search([('source_model_id','=',model_id[0].id)]) 457 | if field_action: 458 | field_action = field_action[0] 459 | model_data_obj = source_connection.model('ir.model.data') 460 | for source_data_record in source_model_data: 461 | source_data_m2m = source_model_obj.export_data([int(source_data_record[0])], ['.id', field.source_field])['datas'] 462 | new_field_value = False 463 | for readed_record in source_data_m2m: 464 | if readed_record[1]: 465 | for value in readed_record[1].split(','): 466 | value_id = model_data_obj.search([('model','ilike',field.source_field_id.relation),('name','ilike',value.split('.')[-1])]) 467 | if value_id: 468 | value_id = model_data_obj.export_data([value_id[0]], ['.id', 'res_id'])['datas'] 469 | value_id = value_id[0][1] 470 | if field_action.target_id_type == 'source_id' and value: 471 | new_field_value = value 472 | elif field_action.target_id_type == 'builded_id' and value_id: 473 | if new_field_value: 474 | new_field_value = new_field_value + ',' + '%s_%s' % (field_action.target_id_prefix, str(value_id)) 475 | else: 476 | new_field_value = '%s_%s' % (field_action.target_id_prefix, str(value_id)) 477 | source_data_record.append(new_field_value) 478 | 479 | _logger.info('Building value mapping mapping...') 480 | # Read and append source values of type 'value_mapping' 481 | source_fields_value_mapping = [x.source_field for x in self.field_mapping_ids if x.state==state and x.type == 'value_mapping'] 482 | #print 'source_fields_value_mapping', source_fields_value_mapping 483 | source_data_value_mapping = source_model_obj.export_data(source_model_ids, source_fields_value_mapping)['datas'] 484 | #print 'source_data_value_mapping', source_data_value_mapping 485 | source_value_mapping_id = [x.value_mapping_field_id.id for x in self.field_mapping_ids if x.state==state and x.type == 'value_mapping'] 486 | #print 'source_value_mapping_id', source_value_mapping_id 487 | for readed_record, source_data_record in zip(source_data_value_mapping, source_model_data): 488 | target_record = [] 489 | for field_value, value_mapping_id in zip(readed_record, source_value_mapping_id): 490 | new_field_value = False 491 | value_mapping = value_mapping_field_obj.browse( 492 | value_mapping_id) 493 | # TODO mejorar esta cosa horrible, no hace falta guardar en dos clases separadas, deberia usar una sola para selection y para id 494 | if value_mapping.type == 'id': 495 | new_field = value_mapping_field_detail_obj.search([ 496 | ('source_external_model_record_id.ext_id', '=', field_value), 497 | ('value_mapping_field_id', '=', value_mapping_id)], 498 | limit=1) 499 | # if new_fields: 500 | new_field_value = new_field.target_external_model_record_id.ext_id 501 | elif value_mapping.type == 'selection': 502 | new_field = value_mapping_field_detail_obj.search([ 503 | ('source_value_id.ext_id', '=', field_value), 504 | ('value_mapping_field_id', '=', value_mapping_id)], 505 | limit=1) 506 | new_field_value = new_field.target_value_id.ext_id 507 | # Convertimos a false todos aquellos mapeos al que no se les asigno pareja 508 | # Si el modelo permite valores false va a andar bien, si no va a dar el error y debera mapearse 509 | if new_field_value is None: 510 | new_field_value = False 511 | target_record.append(new_field_value) 512 | source_data_record.extend(target_record) 513 | 514 | _logger.info('Building date adapt...') 515 | # Read and append source values of type 'date_adapt' 516 | source_fields_date_adapt = [x.source_field for x in self.field_mapping_ids if x.state==state and x.type == 'date_adapt'] 517 | source_data_date_adapt = source_model_obj.export_data(source_model_ids, source_fields_date_adapt)['datas'] 518 | source_mapping_date_adapt = [x for x in self.field_mapping_ids if x.state==state and x.type == 'date_adapt'] 519 | for readed_record, source_data_record in zip(source_data_date_adapt, source_model_data): 520 | target_record = [] 521 | for field_value, source_mapping in zip(readed_record, source_mapping_date_adapt): 522 | if source_mapping.source_field_id.ttype == 'datetime' and field_value: 523 | if source_mapping.target_field_id.ttype == 'date': 524 | # TODO, no estoy seguro si esta forma de truncarlo funciona bien 525 | field_value = field_value[:10] 526 | if source_mapping.source_field_id.ttype == 'date' and field_value: 527 | if source_mapping.target_field_id.ttype == 'datetime': 528 | field_value = self.date_to_datetime(field_value) 529 | target_record.append(field_value) 530 | source_data_record.extend(target_record) 531 | 532 | _logger.info('Building expressions...') 533 | field_mapping_expression_ids = [x.id for x in self.field_mapping_ids if x.state==state and x.type == 'expression'] 534 | if field_mapping_expression_ids: 535 | for rec in source_model_data: 536 | rec_id = rec[0] 537 | expression_results = field_mapping_obj.browse( 538 | field_mapping_expression_ids).run_expressions( 539 | int(rec_id), 540 | source_connection, 541 | target_connection) 542 | rec.extend(expression_results) 543 | 544 | _logger.info('Building migrated ids...') 545 | field_mapping_migrated_id_ids = [x.id for x in self.field_mapping_ids if x.state==state and x.type == 'migrated_id'] 546 | if field_mapping_migrated_id_ids: 547 | for rec in source_model_data: 548 | rec_id = rec[0] 549 | migrated_id_results = field_mapping_obj.browse( 550 | field_mapping_migrated_id_ids).get_migrated_id( 551 | int(rec_id), 552 | source_connection, 553 | target_connection) 554 | rec.extend(migrated_id_results) 555 | 556 | _logger.info('Building reference fields...') 557 | field_mapping_reference_ids = [x.id for x in self.field_mapping_ids if x.state==state and x.type == 'reference'] 558 | if field_mapping_reference_ids: 559 | for rec in source_model_data: 560 | rec_id = rec[0] 561 | reference_results = field_mapping_obj.browse( 562 | field_mapping_reference_ids).get_reference( 563 | int(rec_id), source_connection, target_connection) 564 | _logger.info('Reference_results: %s' % reference_results) 565 | rec.extend(reference_results) 566 | 567 | _logger.info('Removing auxliaria .id') 568 | target_model_data = [] 569 | #print source_model_data 570 | #print '' 571 | for record in source_model_data: 572 | if self.target_id_type == 'source_id': 573 | target_model_data.append(record[1:]) 574 | elif self.target_id_type == 'builded_id': 575 | target_model_data.append(['%s_%s' % ( 576 | self.target_id_prefix, str(record[0]))] + record[2:]) 577 | 578 | try: 579 | _logger.info('Loadding Data...') 580 | import_result = target_model_obj.load( 581 | target_fields, target_model_data) 582 | vals = {'log': import_result} 583 | except: 584 | error = sys.exc_info() 585 | print error 586 | vals = {'log': error} 587 | 588 | self.write(vals) 589 | self.target_model_id.get_records(target_connection) 590 | 591 | @api.multi 592 | def order_actions(self, exceptions=None): 593 | _logger.info('Lines to order %i' % len(self.ids)) 594 | if exceptions is None: 595 | exceptions = [] 596 | # field_mapping_obj = self.pool.get('etl.field_mapping') 597 | ordered_actions = [] 598 | ordered_ids = [] 599 | 600 | # We exclude de exceptions 601 | unordered_ids = self.search([ 602 | ('id', 'in', self.ids), 603 | ('source_model_id.model', 'not in', exceptions)]).ids 604 | _logger.info('Request IDS: %s' % str(self.ids)) 605 | _logger.info('Request IDS without exceptions: %s' % str(unordered_ids)) 606 | 607 | actions_to_order = [ 608 | x.source_model_id.model for x in self.browse(unordered_ids)] 609 | _logger.info('Actions_to_order %s' % actions_to_order) 610 | count = 0 611 | count_max = len(self) * 2 612 | while unordered_ids and (count < count_max): 613 | count += 1 614 | rec = self.browse(unordered_ids[0]) 615 | action_clean_dependecies = [] 616 | many2one_mappings = self.env['etl.field_mapping'].search([ 617 | ('action_id', '=', rec.id), 618 | ('source_field_id.ttype', '=', 'many2one'), 619 | ('state', 'in', ['to_analyze', 'enabled', 'on_repeating'])]) 620 | for mapping in many2one_mappings: 621 | if (mapping.source_field_id.relation not in action_clean_dependecies) and (mapping.source_field_id.relation in actions_to_order): 622 | if not(mapping.source_field_id.relation == rec.source_model_id.model): 623 | action_clean_dependecies.append(mapping.source_field_id.relation) 624 | # else: 625 | # TODO usar este dato para algo! para macar la clase por ejemplo 626 | _logger.info('Model: %s, depenencias: %s' % ( 627 | rec.source_model_id.model, action_clean_dependecies)) 628 | dependecies_ok = True 629 | for action_dependecy in action_clean_dependecies: 630 | if (action_dependecy not in ordered_actions) and (action_dependecy not in exceptions): 631 | dependecies_ok = False 632 | break 633 | unordered_ids.remove(rec.id) 634 | if dependecies_ok: 635 | _logger.info('Dependency ok!') 636 | ordered_ids.append(rec.id) 637 | ordered_actions.append(rec.source_model_id.model) 638 | else: 639 | _logger.info('Break, dependency false!') 640 | unordered_ids.append(rec.id) 641 | 642 | _logger.info('Unordered Models: %s' % str(unordered_ids)) 643 | _logger.info('New Order: %s' % str(ordered_actions)) 644 | 645 | # Add sequence to exception actions 646 | sequence = 0 647 | for exception in exceptions: 648 | exception_action_ids = self.search([ 649 | ('id', 'in', self.ids), 650 | ('source_model_id.model', '=', exception)]) 651 | sequence += 10 652 | vals = { 653 | 'sequence': sequence, 654 | } 655 | exception_action_ids.write(vals) 656 | 657 | # Add sequence to ordered actions 658 | sequence = 500 659 | for ordered_action in self.browse(ordered_ids): 660 | sequence += 10 661 | vals = { 662 | 'sequence': sequence, 663 | } 664 | ordered_action.write(vals) 665 | return [unordered_ids, ordered_ids] 666 | 667 | @api.model 668 | def date_to_datetime(self, userdate): 669 | """ Convert date values expressed in user's timezone to 670 | server-side UTC timestamp, assuming a default arbitrary 671 | time of 12:00 AM - because a time is needed. 672 | 673 | :param str userdate: date string in in user time zone 674 | :return: UTC datetime string for server-side use 675 | """ 676 | # TODO: move to fields.datetime in server after 7.0 677 | user_date = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT) 678 | context = self._context 679 | if context and context.get('tz'): 680 | tz_name = context['tz'] 681 | else: 682 | tz_name = self.env['res.users'].browse(SUPERUSER_ID).tz 683 | print tz_name 684 | #tz_name = tz_name[0] 685 | if tz_name: 686 | utc = pytz.timezone('UTC') 687 | context_tz = pytz.timezone(tz_name) 688 | #user_datetime = user_date + relativedelta(hours=12.0) 689 | local_timestamp = context_tz.localize(user_date, is_dst=False) 690 | user_datetime = local_timestamp.astimezone(utc) 691 | return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT) 692 | return user_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT) 693 | 694 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 695 | -------------------------------------------------------------------------------- /etl/data/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingadhoc/odoo-etl/d5f54db11c5f916da79e23c52037ea36a12640a4/etl/data/README -------------------------------------------------------------------------------- /etl/data/action_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/action_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/external_model_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/external_model_record_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/external_model_record_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/external_model_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/field_mapping_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/field_mapping_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/field_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/field_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/manager_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/manager_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/value_mapping_field_detail_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/value_mapping_field_detail_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/value_mapping_field_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/value_mapping_field_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/data/value_mapping_field_value_properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /etl/data/value_mapping_field_value_track.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /etl/external_model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | from ast import literal_eval 9 | import logging 10 | _logger = logging.getLogger(__name__) 11 | 12 | 13 | class external_model(models.Model): 14 | """""" 15 | 16 | _name = 'etl.external_model' 17 | _description = 'external_model' 18 | 19 | _order = "sequence" 20 | 21 | sequence = fields.Integer( 22 | string='Sequence', 23 | readonly=True 24 | ) 25 | type = fields.Selection( 26 | [(u'source', u'Source'), (u'target', u'Target')], 27 | string='Type', 28 | readonly=True, 29 | required=True 30 | ) 31 | name = fields.Char( 32 | string='Name', 33 | readonly=True, 34 | required=True 35 | ) 36 | model = fields.Char( 37 | string='Model', 38 | readonly=True, 39 | required=True 40 | ) 41 | order = fields.Integer( 42 | string='Order', 43 | readonly=True 44 | ) 45 | records = fields.Integer( 46 | string='Records', 47 | readonly=True 48 | ) 49 | fields_to_read = fields.Char( 50 | string='Fields to read', 51 | default=['name'] 52 | ) 53 | field_ids = fields.One2many( 54 | 'etl.field', 55 | 'model_id', 56 | string='Fields', 57 | readonly=True 58 | ) 59 | source_action_ids = fields.One2many( 60 | 'etl.action', 61 | 'source_model_id', 62 | string='source_action_ids' 63 | ) 64 | target_action_ids = fields.One2many( 65 | 'etl.action', 66 | 'target_model_id', 67 | string='target_action_ids' 68 | ) 69 | manager_id = fields.Many2one( 70 | 'etl.manager', 71 | ondelete='cascade', 72 | string='Manager', 73 | readonly=True, 74 | required=True 75 | ) 76 | external_model_record_ids = fields.One2many( 77 | 'etl.external_model_record', 78 | 'external_model_id', 79 | string='external_model_record_ids' 80 | ) 81 | 82 | _constraints = [ 83 | ] 84 | 85 | def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None): 86 | if args is None: 87 | args = [] 88 | domain = args + ['|', ('model', operator, name), ('name', operator, name)] 89 | return self.name_get(cr, name_get_uid or uid, 90 | super(external_model, self).search(cr, uid, domain, limit=limit, context=context), 91 | context=context) 92 | 93 | @api.one 94 | def read_records(self): 95 | '''Function that reads external id and name field from an external 96 | model and save them in migrator database''' 97 | (source_connection, target_connection) = self.manager_id.open_connections() 98 | if self.type == 'source': 99 | connection = source_connection 100 | else: 101 | connection = target_connection 102 | 103 | fields_to_read = [] 104 | if self.fields_to_read: 105 | fields_to_read = literal_eval(self.fields_to_read) 106 | 107 | record_fields = ['.id', 'id'] 108 | record_fields.extend(fields_to_read) 109 | 110 | external_model_obj = connection.model(self.model) 111 | external_model_record_ids = external_model_obj.search([]) 112 | external_model_record_data = external_model_obj.export_data( 113 | external_model_record_ids, record_fields)['datas'] 114 | 115 | new_external_model_record_data = [] 116 | for record in external_model_record_data: 117 | # take out item o and init new_record with our own ext id 118 | new_record = [ 119 | 'model%i_record_%s' % (self.id, record.pop(0))] 120 | # append readed external id 'id' to new record 121 | new_record.append(record.pop(0)) 122 | # buid name wit readed fields 123 | name = '' 124 | # record = record.decode("utf-8") 125 | name = '; '.join([x for x in record if x]) 126 | new_record.append(name) 127 | # append model id 128 | new_record.append(self.id) 129 | new_external_model_record_data.append(new_record) 130 | external_model_record_fields = [ 131 | 'id', 132 | 'ext_id', 133 | 'name', 134 | 'external_model_id/.id'] 135 | # load records 136 | self.env['etl.external_model_record'].load( 137 | external_model_record_fields, new_external_model_record_data) 138 | 139 | @api.multi 140 | def read_fields_button(self): 141 | return self.read_fields(False) 142 | 143 | @api.multi 144 | def read_fields(self, connection=False): 145 | ''' Get fields for external models''' 146 | field_fields = [ 147 | 'id', 148 | 'model_id/.id', 149 | 'field_description', 150 | 'name', 151 | 'relation', 152 | 'required', 153 | 'ttype', 154 | 'function'] 155 | model_field_data = [] 156 | for model in self: 157 | _logger.info('Reading fields %s database, model: %s' % ( 158 | model.type, model.name)) 159 | if not connection: 160 | (source_connection, target_connection) = model.manager_id.open_connections() 161 | if model.type == 'source': 162 | connection = source_connection 163 | elif model.type == 'target': 164 | connection = target_connection 165 | else: 166 | raise Warning(_('Error getting connection')) 167 | external_model_obj = connection.model(model.model) 168 | try: 169 | external_model_fields = external_model_obj.fields_get() 170 | except: 171 | continue 172 | else: 173 | for field in external_model_fields: 174 | field_dic = external_model_fields[field] 175 | name = field 176 | string = field_dic.get('string' or False) 177 | function = field_dic.get('function' or False) 178 | ttype = field_dic.get('type' or False) 179 | relation = field_dic.get('relation' or False) 180 | required = field_dic.get('required' or False) 181 | 182 | field_data = [ 183 | 'field_model_%s_%s' % (str(model.id), name), 184 | model.id, 185 | string, 186 | name, 187 | relation, 188 | required, 189 | ttype, 190 | function 191 | ] 192 | model_field_data.append(field_data) 193 | _logger.info('Writing fields data...') 194 | self.env['etl.field'].load(field_fields, model_field_data) 195 | 196 | @api.one 197 | def get_records(self, connection): 198 | try: 199 | model_obj = connection.model(self.model) 200 | model_ids = model_obj.search([]) 201 | vals = {'records': len(model_ids)} 202 | _logger.info('%i records on model %s' % ( 203 | len(model_ids), self.name)) 204 | self.write(vals) 205 | except: 206 | _logger.error('Error getting records') 207 | 208 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 209 | -------------------------------------------------------------------------------- /etl/external_model_record.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | 9 | 10 | class external_model_record(models.Model): 11 | """""" 12 | 13 | _name = 'etl.external_model_record' 14 | _description = 'external_model_record' 15 | 16 | ext_id = fields.Char( 17 | string='Value To be Mapped', 18 | required=True 19 | ) 20 | name = fields.Char( 21 | string='Help Name', 22 | required=True 23 | ) 24 | external_model_id = fields.Many2one( 25 | 'etl.external_model', 26 | ondelete='cascade', 27 | string='external_model_id', 28 | required=True 29 | ) 30 | 31 | _constraints = [ 32 | ] 33 | 34 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 35 | -------------------------------------------------------------------------------- /etl/field.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | 9 | 10 | class field(models.Model): 11 | """""" 12 | 13 | _name = 'etl.field' 14 | _description = 'field' 15 | 16 | name = fields.Char( 17 | string='Name', 18 | required=True 19 | ) 20 | field_description = fields.Char( 21 | string='Field Description', 22 | required=True 23 | ) 24 | relation = fields.Char( 25 | string='Relation' 26 | ) 27 | relation_field = fields.Char( 28 | string='Relation Field' 29 | ) 30 | ttype = fields.Char( 31 | string='Type', 32 | required=True 33 | ) 34 | required = fields.Char( 35 | string='Required' 36 | ) 37 | function = fields.Char( 38 | string='Function' 39 | ) 40 | model_id = fields.Many2one( 41 | 'etl.external_model', 42 | ondelete='cascade', 43 | string='Model' 44 | ) 45 | type = fields.Selection( 46 | related='model_id.type', 47 | string='Type', 48 | readonly=True, 49 | ) 50 | 51 | _constraints = [ 52 | ] 53 | 54 | def _name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None): 55 | if args is None: 56 | args = [] 57 | domain = args + ['|', ('field_description', operator, name), ('name', operator, name)] 58 | return self.name_get(cr, name_get_uid or uid, 59 | super(field, self).search(cr, uid, domain, limit=limit, context=context), 60 | context=context) 61 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 62 | -------------------------------------------------------------------------------- /etl/field_mapping.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | import time 9 | from openerp.tools.safe_eval import safe_eval as eval 10 | import logging 11 | _logger = logging.getLogger(__name__) 12 | 13 | 14 | class field_mapping(models.Model): 15 | """""" 16 | 17 | _name = 'etl.field_mapping' 18 | _description = 'field_mapping' 19 | 20 | blocked = fields.Boolean( 21 | string='Blocked', 22 | default=False 23 | ) 24 | state = fields.Selection( 25 | [(u'on_repeating', 'on_repeating'), (u'to_analyze', 'to_analyze'), (u'enabled', 'enabled'), (u'disabled', 'disabled'), (u'other_class', 'other_class')], 26 | string='State', 27 | required=True 28 | ) 29 | type = fields.Selection( 30 | [(u'field', 'field'), (u'expression', 'expression'), (u'migrated_id', u'Migrated ID'), (u'value_mapping', u'Value Mapping'), (u'date_adapt', u'Date Adapt'), (u'reference', 'reference')], 31 | string='Source Type', 32 | default='field' 33 | ) 34 | source_field_id = fields.Many2one( 35 | 'etl.field', 36 | string='Source Field' 37 | ) 38 | source_field = fields.Char( 39 | string='Source Exp.' 40 | ) 41 | target_field_id = fields.Many2one( 42 | 'etl.field', 43 | string='Target Field' 44 | ) 45 | target_field = fields.Char( 46 | string='Target Exp.' 47 | ) 48 | expression = fields.Text( 49 | string='Expression', 50 | default="context['result']= False" 51 | ) 52 | value_mapping_field_id = fields.Many2one( 53 | 'etl.value_mapping_field', 54 | string='Value Mapping Field' 55 | ) 56 | model_field_id = fields.Many2one( 57 | 'etl.field', 58 | string='Model Field' 59 | ) 60 | model_field = fields.Char( 61 | string='Model Field Exp.' 62 | ) 63 | note = fields.Html( 64 | string='Notes' 65 | ) 66 | action_id = fields.Many2one( 67 | 'etl.action', 68 | ondelete='cascade', 69 | string='Action', 70 | required=True 71 | ) 72 | target_model_id = fields.Many2one( 73 | related='action_id.target_model_id', 74 | relation='etl.external_model', 75 | string='Target Model', 76 | ) 77 | source_model_id = fields.Many2one( 78 | related='action_id.source_model_id', 79 | relation='etl.external_model', 80 | string='Source Model', 81 | ) 82 | source_field_ttype = fields.Char( 83 | related='source_field_id.ttype', 84 | readonly=True, 85 | string='Source Type' 86 | ) 87 | target_field_ttype = fields.Char( 88 | related='target_field_id.ttype', 89 | readonly=True, 90 | string='Target Type' 91 | ) 92 | manager_id = fields.Many2one( 93 | related='action_id.manager_id', 94 | relation='etl.manager', 95 | readonly=True, 96 | string='Manager' 97 | ) 98 | 99 | _constraints = [ 100 | ] 101 | 102 | @api.onchange('source_field_id') 103 | def onchange_source_field(self): 104 | source_field = False 105 | if self.source_field_id: 106 | source_field = self.source_field_id.name 107 | if self.source_field_id.ttype in ( 108 | 'many2one', 'many2many', 'one2many'): 109 | source_field += '/id' 110 | self.source_field = source_field 111 | 112 | @api.onchange('target_field_id') 113 | def onchange_target_field(self): 114 | target_field = False 115 | if self.target_field_id: 116 | target_field = self.target_field_id.name 117 | if self.target_field_id.ttype in ( 118 | 'many2one', 'many2many', 'one2many'): 119 | target_field += '/id' 120 | self.target_field = target_field 121 | 122 | @api.multi 123 | def action_block(self): 124 | return self.write({'blocked': True}) 125 | 126 | @api.multi 127 | def get_migrated_id( 128 | self, rec_id, source_connection=False, target_connection=False): 129 | '''Get migrated id for field ids and one rec_id (from source database) 130 | For example, for field mapping ids''' 131 | result = [] 132 | 133 | for field_mapping in self: 134 | if not source_connection or not target_connection: 135 | (source_connection, target_connection) = field_mapping.action_id.manager_id.open_connections() 136 | source_model_obj = source_connection.model( 137 | field_mapping.action_id.source_model_id.model) 138 | target_ir_model_data_obj = target_connection.model('ir.model.data') 139 | source_fields = [ 140 | 'id', 141 | field_mapping.source_field_id.name, 142 | field_mapping.model_field] 143 | _logger.info('Source_fields: %s' % source_fields) 144 | source_model_data = source_model_obj.export_data( 145 | [rec_id], source_fields)['datas'] 146 | _logger.info('Source_model_data: %s' % source_model_data) 147 | target_id = False 148 | if source_model_data: 149 | source_id = source_model_data[0][1] 150 | try: 151 | source_resource_obj = source_connection.model( 152 | source_model_data[0][2]) 153 | except: 154 | target_id = False 155 | else: 156 | source_reference = source_resource_obj.export_data( 157 | [int(source_id)], ['id'])['datas'] 158 | if source_reference[0]: 159 | source_reference_splited = source_reference[0][0].split( 160 | '.', 1) 161 | print 'source_reference_splited', source_reference_splited 162 | if len(source_reference_splited) == 1: 163 | module = False 164 | external_ref = source_reference_splited[0] 165 | else: 166 | module = source_reference_splited[0] 167 | external_ref = source_reference_splited[1] 168 | try: 169 | # cambiamos a esta manera fea porque el metodo de abajo no andaba 170 | target_ids = target_ir_model_data_obj.search([( 171 | 'module', '=', module), ('name', '=', external_ref)]) 172 | target_ids = target_ir_model_data_obj.read(target_ids, ['res_id']) 173 | if target_ids: 174 | target_id = target_ids[0].get('res_id', False) 175 | # target_id = target_ir_model_data_obj.get_object_reference( 176 | # module, external_ref)[1] 177 | except: 178 | target_id = False 179 | result.append(target_id) 180 | return result 181 | 182 | @api.multi 183 | def get_reference( 184 | self, rec_id, source_connection=False, target_connection=False): 185 | '''Get reference for field ids and one rec_id (from source database) 186 | For example, for field mapping ids''' 187 | 188 | result = [] 189 | for field_mapping in self: 190 | if not source_connection or not target_connection: 191 | (source_connection, target_connection) = field_mapping.action_id.manager_id.open_connections() 192 | source_model_obj = source_connection.model( 193 | field_mapping.action_id.source_model_id.model) 194 | target_ir_model_data_obj = target_connection.model('ir.model.data') 195 | source_fields = [field_mapping.source_field_id.name] 196 | source_model_data = source_model_obj.read( 197 | [rec_id], source_fields)[0] 198 | target_id = False 199 | if source_model_data: 200 | source_reference = source_model_data[ 201 | field_mapping.source_field_id.name] 202 | if source_reference: 203 | model, res_id = source_reference.split(',', 1) 204 | try: 205 | source_resource_obj = source_connection.model(model) 206 | except: 207 | target_id = False 208 | else: 209 | source_ext_id = source_resource_obj.export_data( 210 | [res_id], ['id'])['datas'] 211 | if source_ext_id[0]: 212 | source_ext_id_splited = source_ext_id[0][0].split( 213 | '.', 1) 214 | if len(source_ext_id_splited) == 1: 215 | module = False 216 | external_ref = source_ext_id_splited[0] 217 | else: 218 | module = source_ext_id_splited[0] 219 | external_ref = source_ext_id_splited[1] 220 | try: 221 | target_id = target_ir_model_data_obj.get_object_reference( 222 | module, external_ref)[1] 223 | except: 224 | # Agregamos este nuevo try porque algunas veces module no es false si no que es como una cadena vacia 225 | try: 226 | target_id = target_ir_model_data_obj.get_object_reference( 227 | '', external_ref)[1] 228 | except: 229 | target_id = False 230 | target_reference = False 231 | if target_id: 232 | target_reference = model + ',' + str(target_id) 233 | result.append(target_reference) 234 | return result 235 | 236 | @api.multi 237 | def run_expressions( 238 | self, rec_id, source_connection=False, target_connection=False): 239 | result = [] 240 | 241 | for field_mapping in self: 242 | expression_result = False 243 | if not source_connection or not target_connection: 244 | (source_connection, target_connection) = field_mapping.action_id.manager_id.open_connections() 245 | source_model_obj = source_connection.model( 246 | field_mapping.action_id.source_model_id.model) 247 | target_model_obj = target_connection.model( 248 | field_mapping.action_id.target_model_id.model) 249 | 250 | obj_pool = source_model_obj 251 | cxt = { 252 | 'self': obj_pool, #to be replaced by target_obj 253 | 'source_obj': source_model_obj, 254 | 'source_connection': source_connection, 255 | 'target_obj': target_model_obj, 256 | 'target_connection': target_connection, 257 | 'rec_id': rec_id, 258 | 'pool': self.pool, 259 | 'time': time, 260 | 'cr': self._cr, 261 | # copy context to prevent side-effects of eval 262 | 'context': dict(self._context), 263 | 'uid': self.env.user.id, 264 | 'user': self.env.user, 265 | } 266 | if not field_mapping.expression: 267 | raise Warning(_( 268 | 'Warning. Type expression choosen buy not expression set')) 269 | # nocopy allows to return 'action' 270 | eval(field_mapping.expression.strip(), cxt, mode="exec") 271 | if 'result' in cxt['context']: 272 | expression_result = cxt['context'].get('result') 273 | result.append(expression_result) 274 | return result 275 | 276 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 277 | -------------------------------------------------------------------------------- /etl/i18n/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingadhoc/odoo-etl/d5f54db11c5f916da79e23c52037ea36a12640a4/etl/i18n/README -------------------------------------------------------------------------------- /etl/i18n/pt.po: -------------------------------------------------------------------------------- 1 | # Translation of Odoo Server. 2 | # This file contains the translation of the following modules: 3 | # * etl 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: Odoo Server 8.0-20150618\n" 8 | "Report-Msgid-Bugs-To: \n" 9 | "POT-Creation-Date: 2015-07-21 13:33+0000\n" 10 | "PO-Revision-Date: 2015-07-21 13:33+0000\n" 11 | "Last-Translator: <>\n" 12 | "Language-Team: \n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: \n" 16 | "Plural-Forms: \n" 17 | 18 | #. module: etl 19 | #: model:ir.actions.act_window,help:etl.action_etl_action_actions 20 | msgid "

Click to create a Actions.

\n" 21 | " " 22 | msgstr "

Clique para criar Ações.

\n" 23 | " " 24 | 25 | #. module: etl 26 | #: model:ir.actions.act_window,help:etl.action_etl_field_external_fields 27 | msgid "

Click to create a External Fields.

\n" 28 | " " 29 | msgstr "

Clique para criar Campos Externos.

\n" 30 | " " 31 | 32 | #. module: etl 33 | #: model:ir.actions.act_window,help:etl.action_etl_external_model_external_models 34 | msgid "

Click to create a External Models.

\n" 35 | " " 36 | msgstr "

Clique para criar Modelos Externos.

\n" 37 | " " 38 | 39 | #. module: etl 40 | #: model:ir.actions.act_window,help:etl.action_etl_field_mapping_fields_mapping 41 | msgid "

Click to create a Fields Mapping.

\n" 42 | " " 43 | msgstr "

Clique para criar um Mapeamento de Campos.

\n" 44 | " " 45 | 46 | #. module: etl 47 | #: model:ir.actions.act_window,help:etl.action_etl_manager_manager 48 | msgid "

Click to create a Manager.

\n" 49 | " " 50 | msgstr "

Clique para criar um Gestor.

\n" 51 | " " 52 | 53 | #. module: etl 54 | #: model:ir.actions.act_window,help:etl.action_etl_value_mapping_field_value_mapping_fields 55 | msgid "

Click to create a Value Mapping Fields.

\n" 56 | " " 57 | msgstr "

Clique para criar um Mapeamento de Valores dos Campos.

\n" 58 | " " 59 | 60 | #. module: etl 61 | #: model:ir.actions.act_window,help:etl.action_etl_value_mapping_field_detail_values_mapping 62 | msgid "

Click to create a Values Mapping.

\n" 63 | " " 64 | msgstr "

Clique para criar um Mapeamento de Valores.

\n" 65 | " " 66 | 67 | #. module: etl 68 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 69 | #: field:etl.field_mapping,action_id:0 70 | msgid "Action" 71 | msgstr "Ação" 72 | 73 | #. module: etl 74 | #: view:etl.manager:etl.view_etl_manager_form 75 | #: field:etl.manager,action_ids:0 76 | #: model:ir.actions.act_window,name:etl.action_etl_action_actions 77 | #: model:ir.ui.menu,name:etl.menu_actions 78 | msgid "Actions" 79 | msgstr "Ações" 80 | 81 | #. module: etl 82 | #: view:etl.action:etl.view_etl_action_form 83 | #: view:etl.field_mapping:etl.view_etl_field_mapping_form 84 | msgid "Block" 85 | msgstr "Bloquear" 86 | 87 | #. module: etl 88 | #: view:etl.action:etl.view_etl_action_filter 89 | #: field:etl.action,blocked:0 90 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 91 | #: field:etl.field_mapping,blocked:0 92 | msgid "Blocked" 93 | msgstr "Bloqueado" 94 | 95 | #. module: etl 96 | #: selection:etl.value_mapping_field,type:0 97 | msgid "Char (not implemented yet)" 98 | msgstr "Char (ainda não está implementado)" 99 | 100 | #. module: etl 101 | #: view:etl.action:etl.view_etl_action_form 102 | msgid "Check Depends" 103 | msgstr "Verificar Dependências" 104 | 105 | #. module: etl 106 | #: field:etl.action,create_uid:0 107 | #: field:etl.external_model,create_uid:0 108 | #: field:etl.external_model_record,create_uid:0 109 | #: field:etl.field,create_uid:0 110 | #: field:etl.field_mapping,create_uid:0 111 | #: field:etl.manager,create_uid:0 112 | #: field:etl.value_mapping_field,create_uid:0 113 | #: field:etl.value_mapping_field_detail,create_uid:0 114 | #: field:etl.value_mapping_field_value,create_uid:0 115 | msgid "Created by" 116 | msgstr "Criado por" 117 | 118 | #. module: etl 119 | #: field:etl.action,create_date:0 120 | #: field:etl.external_model,create_date:0 121 | #: field:etl.external_model_record,create_date:0 122 | #: field:etl.field,create_date:0 123 | #: field:etl.field_mapping,create_date:0 124 | #: field:etl.manager,create_date:0 125 | #: field:etl.value_mapping_field,create_date:0 126 | #: field:etl.value_mapping_field_detail,create_date:0 127 | #: field:etl.value_mapping_field_value,create_date:0 128 | msgid "Created on" 129 | msgstr "Criado em" 130 | 131 | #. module: etl 132 | #: model:ir.ui.menu,name:etl.menu_data 133 | msgid "Data" 134 | msgstr "Data" 135 | 136 | #. module: etl 137 | #: selection:etl.field_mapping,type:0 138 | msgid "Date Adapt" 139 | msgstr "Adaptar Data" 140 | 141 | #. module: etl 142 | #: view:etl.manager:etl.view_etl_manager_form 143 | msgid "Delete Workflows" 144 | msgstr "Apagar Workflows" 145 | 146 | #. module: etl 147 | #: field:etl.value_mapping_field,value_mapping_field_detail_ids:0 148 | msgid "Details" 149 | msgstr "Detalhes" 150 | 151 | #. module: etl 152 | #: model:ir.ui.menu,name:etl.menu_etl 153 | msgid "ETL" 154 | msgstr "ETL" 155 | 156 | #. module: etl 157 | #: code:addons\etl\external_model.py:162 158 | #, python-format 159 | msgid "Error getting connection" 160 | msgstr "Erro ao obter conexão" 161 | 162 | #. module: etl 163 | #: view:etl.manager:etl.view_etl_manager_form 164 | msgid "Exceptions" 165 | msgstr "Exceções" 166 | 167 | #. module: etl 168 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 169 | #: field:etl.field_mapping,expression:0 170 | msgid "Expression" 171 | msgstr "Expressão" 172 | 173 | #. module: etl 174 | #: model:ir.actions.act_window,name:etl.action_etl_field_external_fields 175 | #: model:ir.ui.menu,name:etl.menu_external_fields 176 | msgid "External Fields" 177 | msgstr "Campos Externos" 178 | 179 | #. module: etl 180 | #: view:etl.manager:etl.view_etl_manager_form 181 | #: field:etl.manager,external_model_ids:0 182 | #: model:ir.actions.act_window,name:etl.action_etl_external_model_external_models 183 | #: model:ir.ui.menu,name:etl.menu_external_models 184 | msgid "External Models" 185 | msgstr "Modelos Externos" 186 | 187 | #. module: etl 188 | #: view:etl.field:etl.view_etl_field_filter 189 | #: field:etl.field,field_description:0 190 | msgid "Field Description" 191 | msgstr "Descrição do Campo" 192 | 193 | #. module: etl 194 | #: view:etl.action:etl.view_etl_action_form 195 | msgid "Field Mapping" 196 | msgstr "Mapeamento de Campo" 197 | 198 | #. module: etl 199 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_filter 200 | #: field:etl.value_mapping_field,name:0 201 | msgid "Field Name" 202 | msgstr "Nome do Campo" 203 | 204 | #. module: etl 205 | #: view:etl.external_model:etl.view_etl_external_model_form 206 | #: field:etl.external_model,field_ids:0 207 | msgid "Fields" 208 | msgstr "Campos" 209 | 210 | #. module: etl 211 | #: field:etl.manager,field_analyze_default:0 212 | msgid "Fields Analize by Default" 213 | msgstr "Campos Analizados por Defeito" 214 | 215 | #. module: etl 216 | #: field:etl.manager,field_disable_default:0 217 | msgid "Fields Disable by Default" 218 | msgstr "Campos Desativados por Defeito" 219 | 220 | #. module: etl 221 | #: field:etl.manager,field_disable_words:0 222 | msgid "Fields Disable by Default Words" 223 | msgstr "Campos Desativados por Palavras Padrão" 224 | 225 | #. module: etl 226 | #: view:etl.action:etl.view_etl_action_form 227 | #: field:etl.action,field_mapping_ids:0 228 | #: model:ir.actions.act_window,name:etl.action_etl_field_mapping_fields_mapping 229 | #: model:ir.ui.menu,name:etl.menu_fields_mapping 230 | msgid "Fields Mapping" 231 | msgstr "Mapeamento de Campos" 232 | 233 | #. module: etl 234 | #: field:etl.external_model,fields_to_read:0 235 | msgid "Fields to read" 236 | msgstr "Campos para ler" 237 | 238 | #. module: etl 239 | #: field:etl.action,from_rec_id:0 240 | msgid "From Record" 241 | msgstr "De Registo" 242 | 243 | #. module: etl 244 | #: view:etl.field:etl.view_etl_field_filter 245 | #: field:etl.field,function:0 246 | msgid "Function" 247 | msgstr "Função" 248 | 249 | #. module: etl 250 | #: view:etl.manager:etl.view_etl_manager_form 251 | msgid "Get Records Number" 252 | msgstr "Obter número de registos" 253 | 254 | #. module: etl 255 | #: view:etl.action:etl.view_etl_action_filter 256 | #: view:etl.external_model:etl.view_etl_external_model_filter 257 | #: view:etl.external_model_record:etl.view_etl_external_model_record_filter 258 | #: view:etl.field:etl.view_etl_field_filter 259 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 260 | #: view:etl.manager:etl.view_etl_manager_filter 261 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_filter 262 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_filter 263 | #: view:etl.value_mapping_field_value:etl.view_etl_value_mapping_field_value_filter 264 | msgid "Group By" 265 | msgstr "Agrupar por" 266 | 267 | #. module: etl 268 | #: view:etl.external_model_record:etl.view_etl_external_model_record_filter 269 | #: field:etl.external_model_record,name:0 270 | #: view:etl.value_mapping_field_value:etl.view_etl_value_mapping_field_value_filter 271 | #: field:etl.value_mapping_field_value,name:0 272 | msgid "Help Name" 273 | msgstr "Nome de ajuda" 274 | 275 | #. module: etl 276 | #: field:etl.action,id:0 277 | #: field:etl.external_model,id:0 278 | #: field:etl.external_model_record,id:0 279 | #: field:etl.field,id:0 280 | #: field:etl.field_mapping,id:0 281 | #: field:etl.manager,id:0 282 | #: field:etl.value_mapping_field,id:0 283 | #: field:etl.value_mapping_field_detail,id:0 284 | #: field:etl.value_mapping_field_value,id:0 285 | msgid "ID" 286 | msgstr "ID" 287 | 288 | #. module: etl 289 | #: selection:etl.value_mapping_field,type:0 290 | msgid "Id" 291 | msgstr "ID" 292 | 293 | #. module: etl 294 | #: view:etl.manager:etl.view_etl_manager_form 295 | msgid "Install Modules" 296 | msgstr "Instalar Módulos" 297 | 298 | #. module: etl 299 | #: view:etl.value_mapping_field_value:etl.view_etl_value_mapping_field_value_filter 300 | #: field:etl.value_mapping_field_value,ext_id:0 301 | msgid "Key" 302 | msgstr "Chave" 303 | 304 | #. module: etl 305 | #: help:etl.manager,source_lang:0 306 | msgid "Language used on source database translatable fields" 307 | msgstr "Linguagem utilizada nos campos traduzíveis do banco de dados de origem" 308 | 309 | #. module: etl 310 | #: help:etl.manager,target_lang:0 311 | msgid "Language used on target database translatable fields" 312 | msgstr "Linguagem utilizada nos campos traduzíveis do banco de dados de destino" 313 | 314 | #. module: etl 315 | #: field:etl.action,write_uid:0 316 | #: field:etl.external_model,write_uid:0 317 | #: field:etl.external_model_record,write_uid:0 318 | #: field:etl.field,write_uid:0 319 | #: field:etl.field_mapping,write_uid:0 320 | #: field:etl.manager,write_uid:0 321 | #: field:etl.value_mapping_field,write_uid:0 322 | #: field:etl.value_mapping_field_detail,write_uid:0 323 | #: field:etl.value_mapping_field_value,write_uid:0 324 | msgid "Last Updated by" 325 | msgstr "Última atualização por" 326 | 327 | #. module: etl 328 | #: field:etl.action,write_date:0 329 | #: field:etl.external_model,write_date:0 330 | #: field:etl.external_model_record,write_date:0 331 | #: field:etl.field,write_date:0 332 | #: field:etl.field_mapping,write_date:0 333 | #: field:etl.manager,write_date:0 334 | #: field:etl.value_mapping_field,write_date:0 335 | #: field:etl.value_mapping_field_detail,write_date:0 336 | #: field:etl.value_mapping_field_value,write_date:0 337 | msgid "Last Updated on" 338 | msgstr "Última atualização em" 339 | 340 | #. module: etl 341 | #: view:etl.action:etl.view_etl_action_form 342 | #: field:etl.action,log:0 343 | #: view:etl.manager:etl.view_etl_manager_form 344 | #: field:etl.manager,log:0 345 | msgid "Log" 346 | msgstr "Log" 347 | 348 | #. module: etl 349 | #: view:etl.action:etl.view_etl_action_filter 350 | #: field:etl.action,manager_id:0 351 | #: view:etl.external_model:etl.view_etl_external_model_filter 352 | #: field:etl.external_model,manager_id:0 353 | #: view:etl.manager:etl.view_etl_manager_form 354 | #: model:ir.actions.act_window,name:etl.action_etl_manager_manager 355 | #: model:ir.ui.menu,name:etl.menu_manager 356 | #: model:res.groups,name:etl.group_manager 357 | msgid "Manager" 358 | msgstr "Gestor" 359 | 360 | #. module: etl 361 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_form 362 | msgid "Map Records" 363 | msgstr "Mapear Registos" 364 | 365 | #. module: etl 366 | #: field:etl.value_mapping_field,value_mapping_field_value_ids:0 367 | msgid "Mapping Values" 368 | msgstr "Valores de Mapeamento" 369 | 370 | #. module: etl 371 | #: view:etl.action:etl.view_etl_action_form 372 | msgid "Match Fields" 373 | msgstr "Corresponder Campos" 374 | 375 | #. module: etl 376 | #: view:etl.manager:etl.view_etl_manager_form 377 | msgid "Match Models" 378 | msgstr "Corresponder Modelos" 379 | 380 | #. module: etl 381 | #: view:etl.manager:etl.view_etl_manager_form 382 | msgid "Match and Order" 383 | msgstr "Corresponder e Ordenar" 384 | 385 | #. module: etl 386 | #: selection:etl.field_mapping,type:0 387 | msgid "Migrated ID" 388 | msgstr "ID Migrado" 389 | 390 | #. module: etl 391 | #: view:etl.external_model:etl.view_etl_external_model_filter 392 | #: field:etl.external_model,model:0 393 | #: field:etl.field,model_id:0 394 | msgid "Model" 395 | msgstr "Modelo" 396 | 397 | #. module: etl 398 | #: field:etl.manager,model_exception_words:0 399 | msgid "Model Exception Words" 400 | msgstr "Modelo Palavras de Excepção" 401 | 402 | #. module: etl 403 | #: field:etl.field_mapping,model_field_id:0 404 | msgid "Model Field" 405 | msgstr "Campo Modelo" 406 | 407 | #. module: etl 408 | #: field:etl.field_mapping,model_field:0 409 | msgid "Model Field Exp." 410 | msgstr "Modelo Expressão de Campo" 411 | 412 | #. module: etl 413 | #: view:etl.action:etl.view_etl_action_form 414 | msgid "Models" 415 | msgstr "Modelos" 416 | 417 | #. module: etl 418 | #: field:etl.manager,model_analyze_default:0 419 | msgid "Models Analyze by Default" 420 | msgstr "Modelos Analizados por Defeito" 421 | 422 | #. module: etl 423 | #: field:etl.manager,model_disable_default:0 424 | msgid "Models Disabled by Default" 425 | msgstr "Modelos Desativados por Defeito" 426 | 427 | #. module: etl 428 | #: field:etl.manager,workflow_models:0 429 | msgid "Models to delete Workflows" 430 | msgstr "Modelos para apagar Workflows" 431 | 432 | #. module: etl 433 | #: field:etl.manager,modules_to_install:0 434 | msgid "Modules To Install" 435 | msgstr "Módulos para Instalar" 436 | 437 | #. module: etl 438 | #: view:etl.action:etl.view_etl_action_filter 439 | #: field:etl.action,name:0 440 | #: view:etl.external_model:etl.view_etl_external_model_filter 441 | #: field:etl.external_model,name:0 442 | #: view:etl.field:etl.view_etl_field_filter 443 | #: field:etl.field,name:0 444 | #: view:etl.manager:etl.view_etl_manager_filter 445 | #: field:etl.manager,name:0 446 | msgid "Name" 447 | msgstr "Nome" 448 | 449 | #. module: etl 450 | #: view:etl.action:etl.view_etl_action_filter 451 | msgid "Not Grey" 452 | msgstr "Ativadas" 453 | 454 | #. module: etl 455 | #: view:etl.action:etl.view_etl_action_form 456 | #: field:etl.action,note:0 457 | #: field:etl.field_mapping,note:0 458 | #: field:etl.manager,note:0 459 | msgid "Notes" 460 | msgstr "Notas" 461 | 462 | #. module: etl 463 | #: view:etl.external_model:etl.view_etl_external_model_filter 464 | #: field:etl.external_model,order:0 465 | msgid "Order" 466 | msgstr "Ordenar" 467 | 468 | #. module: etl 469 | #: view:etl.manager:etl.view_etl_manager_form 470 | msgid "Order Actions" 471 | msgstr "Ordenar Ações" 472 | 473 | #. module: etl 474 | #: view:etl.manager:etl.view_etl_manager_form 475 | msgid "Read Active Source Models" 476 | msgstr "Ler Modelos de Origem Ativos" 477 | 478 | #. module: etl 479 | #: view:etl.external_model:etl.view_etl_external_model_form 480 | msgid "Read Fields" 481 | msgstr "Ler Campos" 482 | 483 | #. module: etl 484 | #: view:etl.manager:etl.view_etl_manager_form 485 | msgid "Read Models" 486 | msgstr "Ler Modelos" 487 | 488 | #. module: etl 489 | #: view:etl.external_model:etl.view_etl_external_model_form 490 | msgid "Read Records" 491 | msgstr "Ler Registos" 492 | 493 | #. module: etl 494 | #: view:etl.manager:etl.view_etl_manager_form 495 | msgid "Read and get Records" 496 | msgstr "Ler e obter Registos" 497 | 498 | #. module: etl 499 | #: view:etl.external_model:etl.view_etl_external_model_filter 500 | #: view:etl.external_model:etl.view_etl_external_model_form 501 | #: field:etl.external_model,records:0 502 | msgid "Records" 503 | msgstr "Registos" 504 | 505 | #. module: etl 506 | #: view:etl.field:etl.view_etl_field_filter 507 | #: field:etl.field,relation:0 508 | msgid "Relation" 509 | msgstr "Relação" 510 | 511 | #. module: etl 512 | #: view:etl.field:etl.view_etl_field_filter 513 | #: field:etl.field,relation_field:0 514 | msgid "Relation Field" 515 | msgstr "Campo de Relação" 516 | 517 | #. module: etl 518 | #: field:etl.action,repeating_action:0 519 | msgid "Repeating Action?" 520 | msgstr "Ação de Repetição?" 521 | 522 | #. module: etl 523 | #: field:etl.manager,repeating_models:0 524 | msgid "Repeating Models" 525 | msgstr "Modelos de Repetição" 526 | 527 | #. module: etl 528 | #: view:etl.field:etl.view_etl_field_filter 529 | #: field:etl.field,required:0 530 | msgid "Required" 531 | msgstr "Obrigatório" 532 | 533 | #. module: etl 534 | #: view:etl.action:etl.view_etl_action_form 535 | msgid "Run Action" 536 | msgstr "Correr Ação" 537 | 538 | #. module: etl 539 | #: view:etl.manager:etl.view_etl_manager_form 540 | msgid "Run Actions" 541 | msgstr "Correr Ações" 542 | 543 | #. module: etl 544 | #: view:etl.field_mapping:etl.view_etl_field_mapping_form 545 | msgid "Run Expressions" 546 | msgstr "Correr Expressões" 547 | 548 | #. module: etl 549 | #: view:etl.action:etl.view_etl_action_form 550 | msgid "Run Repeated Action" 551 | msgstr "Correr Ação Repetida" 552 | 553 | #. module: etl 554 | #: view:etl.manager:etl.view_etl_manager_form 555 | msgid "Run Repeating Actions" 556 | msgstr "Correr Ações Repetidas" 557 | 558 | #. module: etl 559 | #: selection:etl.value_mapping_field,type:0 560 | msgid "Selection" 561 | msgstr "Seleção" 562 | 563 | #. module: etl 564 | #: view:etl.action:etl.view_etl_action_filter 565 | #: field:etl.action,sequence:0 566 | #: view:etl.external_model:etl.view_etl_external_model_filter 567 | #: field:etl.external_model,sequence:0 568 | msgid "Sequence" 569 | msgstr "Sequência" 570 | 571 | #. module: etl 572 | #: selection:etl.external_model,type:0 573 | msgid "Source" 574 | msgstr "Origem" 575 | 576 | #. module: etl 577 | #: view:etl.manager:etl.view_etl_manager_filter 578 | #: field:etl.manager,source_database:0 579 | msgid "Source Database" 580 | msgstr "Base de Dados Origem" 581 | 582 | #. module: etl 583 | #: view:etl.action:etl.view_etl_action_filter 584 | #: field:etl.action,source_domain:0 585 | msgid "Source Domain" 586 | msgstr "Domínio Origem" 587 | 588 | #. module: etl 589 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 590 | #: field:etl.field_mapping,source_field:0 591 | msgid "Source Exp." 592 | msgstr "Expressão Origem" 593 | 594 | #. module: etl 595 | #: field:etl.value_mapping_field_detail,source_external_model_record_id:0 596 | msgid "Source External Model Record" 597 | msgstr "Registo de Modelo Externo Origem" 598 | 599 | #. module: etl 600 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 601 | #: field:etl.field_mapping,source_field_id:0 602 | msgid "Source Field" 603 | msgstr "Campo Origem" 604 | 605 | #. module: etl 606 | #: view:etl.manager:etl.view_etl_manager_filter 607 | #: field:etl.manager,source_hostname:0 608 | msgid "Source Hostname" 609 | msgstr "Hostname Origem" 610 | 611 | #. module: etl 612 | #: field:etl.value_mapping_field_detail,source_id:0 613 | msgid "Source ID" 614 | msgstr "ID Origem" 615 | 616 | #. module: etl 617 | #: field:etl.manager,source_lang:0 618 | msgid "Source Language" 619 | msgstr "Linguagem Origem" 620 | 621 | #. module: etl 622 | #: field:etl.manager,source_login:0 623 | msgid "Source Login" 624 | msgstr "Login Origem" 625 | 626 | #. module: etl 627 | #: view:etl.action:etl.view_etl_action_filter 628 | #: field:etl.action,source_model_id:0 629 | #: field:etl.value_mapping_field,source_model_id:0 630 | msgid "Source Model" 631 | msgstr "Modelo Origem" 632 | 633 | #. module: etl 634 | #: field:etl.manager,source_password:0 635 | msgid "Source Password" 636 | msgstr "Password Origem" 637 | 638 | #. module: etl 639 | #: field:etl.manager,source_port:0 640 | msgid "Source Port" 641 | msgstr "Porta Origem" 642 | 643 | #. module: etl 644 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 645 | #: field:etl.field_mapping,type:0 646 | msgid "Source Type" 647 | msgstr "Tipo Origem" 648 | 649 | #. module: etl 650 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_filter 651 | #: field:etl.value_mapping_field_detail,source_value:0 652 | #: field:etl.value_mapping_field_detail,source_value_id:0 653 | msgid "Source Value" 654 | msgstr "Valor Origem" 655 | 656 | #. module: etl 657 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_filter 658 | msgid "Source..." 659 | msgstr "Origem..." 660 | 661 | #. module: etl 662 | #: view:etl.action:etl.view_etl_action_filter 663 | #: field:etl.action,state:0 664 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 665 | #: field:etl.field_mapping,state:0 666 | msgid "State" 667 | msgstr "Estado" 668 | 669 | #. module: etl 670 | #: selection:etl.external_model,type:0 671 | msgid "Target" 672 | msgstr "Destino" 673 | 674 | #. module: etl 675 | #: view:etl.manager:etl.view_etl_manager_filter 676 | #: field:etl.manager,target_database:0 677 | msgid "Target Database" 678 | msgstr "Base de Dados Destino" 679 | 680 | #. module: etl 681 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 682 | #: field:etl.field_mapping,target_field:0 683 | msgid "Target Exp." 684 | msgstr "Expressão Destino" 685 | 686 | #. module: etl 687 | #: field:etl.value_mapping_field_detail,target_external_model_record_id:0 688 | msgid "Target External Model Record" 689 | msgstr "Registo de Modelo Externo Destino" 690 | 691 | #. module: etl 692 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 693 | #: field:etl.field_mapping,target_field_id:0 694 | msgid "Target Field" 695 | msgstr "Campo Destino" 696 | 697 | #. module: etl 698 | #: view:etl.manager:etl.view_etl_manager_filter 699 | #: field:etl.manager,target_hostname:0 700 | msgid "Target Hostname" 701 | msgstr "Hostname Destino" 702 | 703 | #. module: etl 704 | #: field:etl.value_mapping_field_detail,target_id:0 705 | msgid "Target ID" 706 | msgstr "ID Destino" 707 | 708 | #. module: etl 709 | #: field:etl.action,target_id_type:0 710 | msgid "Target ID Type" 711 | msgstr "ID Tipo Destino" 712 | 713 | #. module: etl 714 | #: field:etl.manager,target_lang:0 715 | msgid "Target Language" 716 | msgstr "Linguagem Destino" 717 | 718 | #. module: etl 719 | #: field:etl.manager,target_login:0 720 | msgid "Target Login" 721 | msgstr "Login Destino" 722 | 723 | #. module: etl 724 | #: view:etl.action:etl.view_etl_action_filter 725 | #: field:etl.action,target_model_id:0 726 | #: field:etl.value_mapping_field,target_model_id:0 727 | msgid "Target Model" 728 | msgstr "Modelo Destino" 729 | 730 | #. module: etl 731 | #: field:etl.manager,target_password:0 732 | msgid "Target Password" 733 | msgstr "Password Destino" 734 | 735 | #. module: etl 736 | #: field:etl.manager,target_port:0 737 | msgid "Target Port" 738 | msgstr "Porta Destino" 739 | 740 | #. module: etl 741 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_filter 742 | #: field:etl.value_mapping_field_detail,target_value:0 743 | #: field:etl.value_mapping_field_detail,target_value_id:0 744 | msgid "Target Value" 745 | msgstr "Valor Destino" 746 | 747 | #. module: etl 748 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_filter 749 | msgid "To Map" 750 | msgstr "Para mapear" 751 | 752 | #. module: etl 753 | #: field:etl.action,to_rec_id:0 754 | msgid "To Record" 755 | msgstr "Para Registo" 756 | 757 | #. module: etl 758 | #: view:etl.external_model:etl.view_etl_external_model_filter 759 | #: field:etl.external_model,type:0 760 | #: view:etl.field:etl.view_etl_field_filter 761 | #: field:etl.field,ttype:0 762 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_filter 763 | #: field:etl.value_mapping_field,type:0 764 | msgid "Type" 765 | msgstr "Tipo" 766 | 767 | #. module: etl 768 | #: code:addons\etl\manager.py:164 769 | #: code:addons\etl\manager.py:174 770 | #, python-format 771 | msgid "Unable to Connect to Database. 'Error: %s'" 772 | msgstr "Não é possível conectar à base de dados. 'Erro: %s'" 773 | 774 | #. module: etl 775 | #: view:etl.action:etl.view_etl_action_form 776 | msgid "Update Records Number" 777 | msgstr "Atualizar número de registos" 778 | 779 | #. module: etl 780 | #: model:res.groups,name:etl.group_user 781 | msgid "User" 782 | msgstr "Utilizador" 783 | 784 | #. module: etl 785 | #: selection:etl.field_mapping,type:0 786 | msgid "Value Mapping" 787 | msgstr "Mapeamento de Valor" 788 | 789 | #. module: etl 790 | #: field:etl.field_mapping,value_mapping_field_id:0 791 | msgid "Value Mapping Field" 792 | msgstr "Mapeamento de Valor do Campo" 793 | 794 | #. module: etl 795 | #: view:etl.manager:etl.view_etl_manager_form 796 | #: field:etl.manager,value_mapping_field_ids:0 797 | #: model:ir.actions.act_window,name:etl.action_etl_value_mapping_field_value_mapping_fields 798 | #: model:ir.ui.menu,name:etl.menu_value_mapping_fields 799 | msgid "Value Mapping Fields" 800 | msgstr "Mapeamento de Valores dos Campos" 801 | 802 | #. module: etl 803 | #: view:etl.external_model_record:etl.view_etl_external_model_record_filter 804 | #: field:etl.external_model_record,ext_id:0 805 | msgid "Value To be Mapped" 806 | msgstr "Valores a Mapear" 807 | 808 | #. module: etl 809 | #: model:ir.actions.act_window,name:etl.action_etl_value_mapping_field_detail_values_mapping 810 | #: model:ir.ui.menu,name:etl.menu_values_mapping 811 | msgid "Values Mapping" 812 | msgstr "Mapeamento de Valores" 813 | 814 | #. module: etl 815 | #: code:addons\etl\field_mapping.py:256 816 | #, python-format 817 | msgid "Warning. Type expression choosen buy not expression set" 818 | msgstr "Atenção. Tipo de expressão escolhida mas não definida" 819 | 820 | #. module: etl 821 | #: view:etl.action:etl.view_etl_action_filter 822 | #: view:etl.action:etl.view_etl_action_form 823 | #: view:etl.action:etl.view_etl_action_tree 824 | #: model:ir.model,name:etl.model_etl_action 825 | msgid "action" 826 | msgstr "Ação" 827 | 828 | #. module: etl 829 | #: selection:etl.action,target_id_type:0 830 | msgid "builded_id" 831 | msgstr "builded_id" 832 | 833 | #. module: etl 834 | #: selection:etl.action,state:0 835 | #: selection:etl.field_mapping,state:0 836 | msgid "disabled" 837 | msgstr "Desativado" 838 | 839 | #. module: etl 840 | #: selection:etl.action,state:0 841 | #: selection:etl.field_mapping,state:0 842 | msgid "enabled" 843 | msgstr "Ativado" 844 | 845 | #. module: etl 846 | #: selection:etl.field_mapping,type:0 847 | msgid "expression" 848 | msgstr "Expressão" 849 | 850 | #. module: etl 851 | #: view:etl.external_model:etl.view_etl_external_model_filter 852 | #: view:etl.external_model:etl.view_etl_external_model_form 853 | #: view:etl.external_model:etl.view_etl_external_model_tree 854 | #: model:ir.model,name:etl.model_etl_external_model 855 | msgid "external_model" 856 | msgstr "Modelo Externo" 857 | 858 | #. module: etl 859 | #: field:etl.external_model_record,external_model_id:0 860 | msgid "external_model_id" 861 | msgstr "Modelo Externo" 862 | 863 | #. module: etl 864 | #: view:etl.external_model_record:etl.view_etl_external_model_record_filter 865 | #: view:etl.external_model_record:etl.view_etl_external_model_record_form 866 | #: view:etl.external_model_record:etl.view_etl_external_model_record_tree 867 | #: model:ir.model,name:etl.model_etl_external_model_record 868 | msgid "external_model_record" 869 | msgstr "Registo de Modelo Externo" 870 | 871 | #. module: etl 872 | #: field:etl.external_model,external_model_record_ids:0 873 | msgid "external_model_record_ids" 874 | msgstr "Modelo Externo" 875 | 876 | #. module: etl 877 | #: view:etl.field:etl.view_etl_field_filter 878 | #: view:etl.field:etl.view_etl_field_form 879 | #: view:etl.field:etl.view_etl_field_tree 880 | #: selection:etl.field_mapping,type:0 881 | #: model:ir.model,name:etl.model_etl_field 882 | msgid "field" 883 | msgstr "Campo" 884 | 885 | #. module: etl 886 | #: view:etl.field_mapping:etl.view_etl_field_mapping_filter 887 | #: view:etl.field_mapping:etl.view_etl_field_mapping_form 888 | #: view:etl.field_mapping:etl.view_etl_field_mapping_tree 889 | #: model:ir.model,name:etl.model_etl_field_mapping 890 | msgid "field_mapping" 891 | msgstr "Mapeamento de Campo" 892 | 893 | #. module: etl 894 | #: field:etl.value_mapping_field,log:0 895 | msgid "log" 896 | msgstr "Log" 897 | 898 | #. module: etl 899 | #: view:etl.manager:etl.view_etl_manager_filter 900 | #: view:etl.manager:etl.view_etl_manager_form 901 | #: view:etl.manager:etl.view_etl_manager_tree 902 | #: model:ir.model,name:etl.model_etl_manager 903 | msgid "manager" 904 | msgstr "Gestor" 905 | 906 | #. module: etl 907 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_filter 908 | #: field:etl.value_mapping_field,manager_id:0 909 | msgid "manager_id" 910 | msgstr "Gestor" 911 | 912 | #. module: etl 913 | #: selection:etl.action,state:0 914 | msgid "no_records" 915 | msgstr "Sem registos" 916 | 917 | #. module: etl 918 | #: view:etl.manager:etl.view_etl_manager_form 919 | msgid "notes" 920 | msgstr "Notas" 921 | 922 | #. module: etl 923 | #: model:ir.module.category,description:etl.module_category_etl 924 | #: model:ir.module.category,name:etl.module_category_etl 925 | msgid "odoo ETL" 926 | msgstr "Odoo ETL" 927 | 928 | #. module: etl 929 | #: selection:etl.field_mapping,state:0 930 | msgid "on_repeating" 931 | msgstr "A repetir" 932 | 933 | #. module: etl 934 | #: selection:etl.field_mapping,state:0 935 | msgid "other_class" 936 | msgstr "Outra classe" 937 | 938 | #. module: etl 939 | #: selection:etl.field_mapping,type:0 940 | msgid "reference" 941 | msgstr "Referência" 942 | 943 | #. module: etl 944 | #: field:etl.external_model,source_action_ids:0 945 | msgid "source_action_ids" 946 | msgstr "Ação Origem" 947 | 948 | #. module: etl 949 | #: selection:etl.action,target_id_type:0 950 | msgid "source_id" 951 | msgstr "Origem" 952 | 953 | #. module: etl 954 | #: field:etl.action,source_id_exp:0 955 | msgid "source_id_exp" 956 | msgstr "Expressão Origem" 957 | 958 | #. module: etl 959 | #: field:etl.external_model,target_action_ids:0 960 | msgid "target_action_ids" 961 | msgstr "Ação Destino"" 962 | 963 | #. module: etl 964 | #: field:etl.action,target_id_prefix:0 965 | msgid "target_id_prefix" 966 | msgstr "Prefixo Destino" 967 | 968 | #. module: etl 969 | #: selection:etl.action,state:0 970 | #: selection:etl.field_mapping,state:0 971 | msgid "to_analyze" 972 | msgstr "A analisar" 973 | 974 | #. module: etl 975 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_filter 976 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_form 977 | #: view:etl.value_mapping_field:etl.view_etl_value_mapping_field_tree 978 | #: model:ir.model,name:etl.model_etl_value_mapping_field 979 | msgid "value_mapping_field" 980 | msgstr "Mapeamento de Valor do Campo" 981 | 982 | #. module: etl 983 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_filter 984 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_form 985 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_tree 986 | #: model:ir.model,name:etl.model_etl_value_mapping_field_detail 987 | msgid "value_mapping_field_detail" 988 | msgstr "Mapeamento de Valor de Detalhe do Campo" 989 | 990 | #. module: etl 991 | #: view:etl.value_mapping_field_detail:etl.view_etl_value_mapping_field_detail_filter 992 | #: field:etl.value_mapping_field_detail,value_mapping_field_id:0 993 | #: field:etl.value_mapping_field_value,value_mapping_field_id:0 994 | msgid "value_mapping_field_id" 995 | msgstr "Mapeamento de Valor do Campo" 996 | 997 | #. module: etl 998 | #: view:etl.value_mapping_field_value:etl.view_etl_value_mapping_field_value_filter 999 | #: view:etl.value_mapping_field_value:etl.view_etl_value_mapping_field_value_form 1000 | #: view:etl.value_mapping_field_value:etl.view_etl_value_mapping_field_value_tree 1001 | #: model:ir.model,name:etl.model_etl_value_mapping_field_value 1002 | msgid "value_mapping_field_value" 1003 | msgstr "Valor de Mapeamento de Valor do Campo" 1004 | 1005 | -------------------------------------------------------------------------------- /etl/images/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingadhoc/odoo-etl/d5f54db11c5f916da79e23c52037ea36a12640a4/etl/images/README -------------------------------------------------------------------------------- /etl/manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | from erppeek import Client 9 | from ast import literal_eval 10 | import logging 11 | _logger = logging.getLogger(__name__) 12 | 13 | 14 | class manager(models.Model): 15 | """""" 16 | 17 | _name = 'etl.manager' 18 | _description = 'manager' 19 | 20 | name = fields.Char( 21 | string='Name', 22 | required=True 23 | ) 24 | source_hostname = fields.Char( 25 | string='Source Hostname', 26 | required=True, 27 | default='http://localhost', 28 | ) 29 | source_port = fields.Integer( 30 | string='Source Port', 31 | required=True 32 | ) 33 | source_database = fields.Char( 34 | string='Source Database', 35 | required=True 36 | ) 37 | source_login = fields.Char( 38 | string='Source Login', 39 | required=True 40 | ) 41 | source_password = fields.Char( 42 | string='Source Password', 43 | required=True 44 | ) 45 | target_hostname = fields.Char( 46 | string='Target Hostname', 47 | required=True, 48 | default='http://localhost', 49 | ) 50 | target_port = fields.Integer( 51 | string='Target Port', 52 | required=True 53 | ) 54 | target_database = fields.Char( 55 | string='Target Database', 56 | required=True 57 | ) 58 | target_login = fields.Char( 59 | string='Target Login', 60 | required=True 61 | ) 62 | target_password = fields.Char( 63 | string='Target Password', 64 | required=True 65 | ) 66 | log = fields.Text( 67 | string='Log' 68 | ) 69 | model_disable_default = fields.Text( 70 | string='Models Disabled by Default', 71 | # TODO move this default to another model 72 | default="['ir.model','ir.model.fields','ir.model.access','ir.model.data','ir.sequence.type','ir.sequence','ir.ui.menu','ir.ui.view.custom','ir.ui.view','ir.ui.view_sc','ir.default','ir.actions.actions','ir.actions.act_window','ir.actions.act_window.view','ir.actions.wizard','ir.actions.url','ir.server.object.lines','ir.actions.server','ir.actions.act_window_close','ir.actions.todo.category','ir.actions.todo','ir.actions.client','ir.values','ir.translation','ir.exports','ir.exports.line','workflow','workflow.activity','workflow.transition','workflow.instance','workflow.workitem','workflow.triggers','ir.rule','ir.module.category','ir.module.module','ir.module.module.dependency','res.widget','res.widget.user','publisher_warranty.contract','ir.module.record','board.board','board.board.line','decimal.precision','process.process','process.node','process.condition','process.transition','process.transition.action','email_template.preview','account.tax.template','account.account.template','account.tax.code.template','account.chart.template','account.fiscal.position.template','account.fiscal.position.tax.template','account.fiscal.position.account.template','temp.range','mrp.property.group','mrp.property','account.invoice.ar.installer','pos.config.journal','oo.config','mass.object','mass.editing.wizard','support.support_contract','support.email','account_analytic_analysis.summary.user','account_analytic_analysis.summary.month','res.groups','mail.alias']" 73 | ) 74 | field_disable_default = fields.Text( 75 | string='Fields Disable by Default', 76 | # TODO move this default to another model 77 | default="['lang','printed_vat','context_lang','context_department_id','groups_id','alias_defaults','alias_id','alias_model_id','create_date','calendar_last_notif_ack',]", 78 | ) 79 | model_exception_words = fields.Char( 80 | string='Model Exception Words', 81 | # TODO move this default to another model 82 | default="['report','ir.logging','ir.qweb']", 83 | ) 84 | model_analyze_default = fields.Text( 85 | string='Models Analyze by Default', 86 | # TODO move this default to another model 87 | default="['ir.attachment','ir.cron','ir.filters','ir.config_parameter','ir.mail_server','res.country','res.country.state','res.lang','res.currency','res.currency.rate.type','res.currency.rate','multi_company.default','res.company','res.users','res.request','res.request.link','res.request.history','res.log','ir.property','mail.message','mail.thread','product.uom.categ','product.uom','product.ul','product.price.type','product.pricelist.type','base.action.rule','email.template','fetchmail.server','edi.document','account.tax','account.account','account.journal.view','account.journal.column','account.fiscalyear','account.period','account.journal.period','account.analytic.journal','account.fiscal.position','account.fiscal.position.tax','account.fiscal.position.account','account.sequence.fiscalyear','procurement.order','sale.shop','document.storage','document.directory','document.directory.dctx','document.directory.content.type','document.directory.content','account.voucher',]", 88 | ) 89 | note = fields.Html( 90 | string='Notes' 91 | ) 92 | field_analyze_default = fields.Text( 93 | string='Fields Analize by Default', 94 | # TODO move this default to another model 95 | default="['reconcile_partial_id','reconcile_id']", 96 | ) 97 | repeating_models = fields.Text( 98 | string='Repeating Models', 99 | # TODO move this default to another model 100 | default="['res.partner','res.company','res.users','account.fiscalyear','product.template','product.product','purchase.order','sale.order','hr.employee','project.task','procurement.order']", 101 | ) 102 | field_disable_words = fields.Text( 103 | string='Fields Disable by Default Words', 104 | # TODO move this default to another model 105 | default="['in_group','sel_groups_','rml_header','rml_foot',]", 106 | ) 107 | modules_to_install = fields.Text( 108 | string='Modules To Install', 109 | default=[] 110 | ) 111 | workflow_models = fields.Char( 112 | string='Models to delete Workflows', 113 | required=True, 114 | default='[]' 115 | ) 116 | action_ids = fields.One2many( 117 | 'etl.action', 118 | 'manager_id', 119 | string='Actions', 120 | domain=[('state', 'in', ['to_analyze', 'enabled'])], 121 | copy=False, 122 | ) 123 | external_model_ids = fields.One2many( 124 | 'etl.external_model', 125 | 'manager_id', 126 | string='External Models', 127 | readonly=True, 128 | copy=False, 129 | ) 130 | value_mapping_field_ids = fields.One2many( 131 | 'etl.value_mapping_field', 132 | 'manager_id', 133 | string='Value Mapping Fields', 134 | copy=False, 135 | ) 136 | source_lang = fields.Char( 137 | 'Source Language', 138 | required=True, 139 | default='en_US', 140 | # TODO improove this and load all translations for tranlatable fields 141 | help='Language used on source database translatable fields' 142 | ) 143 | target_lang = fields.Char( 144 | 'Target Language', 145 | required=True, 146 | default='en_US', 147 | # TODO improove this and load all translations for tranlatable fields 148 | help='Language used on target database translatable fields' 149 | ) 150 | target_id_type = fields.Selection( 151 | [(u'source_id', 'Source ID'), (u'builded_id', 'Builded ID')], 152 | string='Target ID Type', 153 | required=True, 154 | default='builded_id', 155 | help="Selection on how the Records target ID's will be build:\n\t" 156 | " - Source ID - will keep the source ID (external_id)\n\t" 157 | " - Builded ID - every external ID will be build as concatenation of Manager Name + _ + Source Model" 158 | ) 159 | 160 | _constraints = [ 161 | ] 162 | 163 | @api.multi 164 | def open_connections(self): 165 | ''' 166 | ''' 167 | self.ensure_one() 168 | try: 169 | _logger.info('Getting source connection') 170 | source_connection = Client( 171 | '%s:%i' % (self.source_hostname, self.source_port), 172 | db=self.source_database, 173 | user=self.source_login, 174 | password=self.source_password) 175 | except Exception, e: 176 | raise Warning( 177 | _("Unable to Connect to Database. 'Error: %s'") % e) 178 | try: 179 | _logger.info('Getting target connection') 180 | target_connection = Client( 181 | '%s:%i' % (self.target_hostname, self.target_port), 182 | db=self.target_database, 183 | user=self.target_login, 184 | password=self.target_password) 185 | except Exception, e: 186 | raise Warning( 187 | _("Unable to Connect to Database. 'Error: %s'") % e) 188 | return [source_connection, target_connection] 189 | 190 | @api.one 191 | def read_active_source_models(self): 192 | ''' 193 | ''' 194 | (source_connection, target_connection) = self.open_connections() 195 | actions = self.env['etl.action'].search( 196 | [('manager_id', '=', self.id), ('state', '=', 'enabled')], 197 | order='sequence') 198 | actions.read_source_model(source_connection, target_connection) 199 | 200 | @api.one 201 | def delete_workflows(self): 202 | (source_connection, target_connection) = self.open_connections() 203 | target_wf_instance_obj = target_connection.model("workflow.instance") 204 | res_types = literal_eval(self.workflow_models) 205 | target_wf_instance_ids = target_wf_instance_obj.search( 206 | [('res_type', 'in', res_types)]) 207 | target_wf_instance_obj.unlink(target_wf_instance_ids) 208 | 209 | @api.one 210 | def install_modules(self): 211 | (source_connection, target_connection) = self.open_connections() 212 | target_module_obj = target_connection.model("ir.module.module") 213 | modules = literal_eval(self.modules_to_install) 214 | domain = [('name', 'in', modules)] 215 | target_module_ids = target_module_obj.search(domain) 216 | target_module_obj.button_immediate_install(target_module_ids) 217 | 218 | @api.one 219 | def run_actions(self): 220 | '''Run all actions (none repeating)''' 221 | (source_connection, target_connection) = self.open_connections() 222 | actions = self.env['etl.action'].search( 223 | [('manager_id', '=', self.id), ('state', '=', 'enabled')], 224 | order='sequence') 225 | actions.run_action(source_connection, target_connection) 226 | 227 | @api.one 228 | def run_repeated_actions(self): 229 | '''Run all repeating actions''' 230 | (source_connection, target_connection) = self.open_connections() 231 | actions = self.env['etl.action'].search([ 232 | ('manager_id', '=', self.id), 233 | ('repeating_action', '=', True), 234 | ('state', '=', 'enabled')], 235 | order='sequence') 236 | actions.run_repeated_action(source_connection, target_connection) 237 | 238 | @api.one 239 | def match_models_and_order_actions(self): 240 | '''Match models and order the actions''' 241 | self.match_models() 242 | self.order_actions() 243 | return True 244 | 245 | @api.one 246 | def match_models(self): 247 | '''Match models''' 248 | _logger.info('Matching models for manager %s' % self.name) 249 | # read all source models 250 | source_domain = [('manager_id', '=', self.id), ('type', '=', 'source')] 251 | source_models = self.env['etl.external_model'].search(source_domain) 252 | 253 | # get disable and to analyze models 254 | data = [] 255 | model_disable_default = [] 256 | model_analyze_default = [] 257 | if self.model_disable_default: 258 | model_disable_default = literal_eval(self.model_disable_default) 259 | if self.model_analyze_default: 260 | model_analyze_default = literal_eval(self.model_analyze_default) 261 | 262 | # get blocked external ids models 263 | blocked_models = self.env['etl.action'].search( 264 | [('blocked', '=', True), ('manager_id', '=', self.id)]) 265 | blocked_model_ext_ids = blocked_models.export_data(['id'])['datas'] 266 | 267 | # for each source model look for a target model and give state 268 | for model in source_models: 269 | target_domain = [ 270 | ('manager_id', '=', self.id), 271 | ('type', '=', 'target'), ('model', '=', model.model)] 272 | target_model = self.env['etl.external_model'].search( 273 | target_domain, limit=1) 274 | 275 | # give right state to model mapping 276 | state = 'enabled' 277 | if model.model in model_disable_default: 278 | state = 'disabled' 279 | elif model.model in model_analyze_default or not target_model: 280 | state = 'to_analyze' 281 | if model.records == 0: 282 | state = 'no_records' 283 | 284 | # get vals for action mapping and create and id 285 | vals = [ 286 | 'model_mapping_' + str(self.id) + '_' + str(model.id), 287 | state, 288 | model.name + ' (' + model.model + ')', 289 | model.order, 290 | model.id, 291 | target_model and target_model.id or False, 292 | self.id 293 | ] 294 | 295 | # look if this id should be blocked 296 | if [vals[0]] in blocked_model_ext_ids: 297 | continue 298 | 299 | # append if not to data 300 | data.append(vals) 301 | 302 | # write actions with data an fields, give result to log 303 | action_fields = [ 304 | 'id', 'state', 'name', 'sequence', 'source_model_id/.id', 305 | 'target_model_id/.id', 'manager_id/.id' 306 | ] 307 | _logger.info('Loading actions match for manager %s' % self.name) 308 | import_result = self.env['etl.action'].load(action_fields, data) 309 | 310 | # write log on manager 311 | self.log = import_result 312 | 313 | # call for match fields 314 | _logger.info('Matching fields for models %s of manager %s' % ( 315 | import_result['ids'], self.name)) 316 | self.env['etl.action'].browse(import_result['ids']).match_fields() 317 | 318 | @api.one 319 | def order_actions(self): 320 | '''Order actions for ids managers''' 321 | # Get enabled actions 322 | actions = self.env['etl.action'].search([ 323 | ('manager_id', '=', self.id), 324 | ('state', 'in', ['to_analyze', 'enabled'])]) 325 | 326 | # If repeating_mdodels defined on the manager, take them as exceptions 327 | exceptions = [] 328 | if self.repeating_models: 329 | repeating_models = self.repeating_models 330 | exceptions = literal_eval(repeating_models) 331 | 332 | # Get unordered and ordered ids from action model 333 | (unordered_ids, ordered_ids) = actions.order_actions(exceptions) 334 | 335 | # get unordered and ordered actions names to write in log. Write log 336 | ordered_actions = [] 337 | unordered_actions = [] 338 | for ordered_action in self.env['etl.action'].browse(ordered_ids): 339 | ordered_actions.append(ordered_action.source_model_id.model) 340 | for unordered_action in self.env['etl.action'].browse(unordered_ids): 341 | unordered_actions.append(unordered_action.source_model_id.model) 342 | self.log = 'Ordered actions: %s\n\nUnordered actions: %s' % ( 343 | str(ordered_actions), str(unordered_actions)) 344 | 345 | # check actions depends if no unordered_ids 346 | if not unordered_ids: 347 | actions.check_m2o_depends() 348 | 349 | @api.one 350 | def read_and_get(self): 351 | '''Read source and target models and get records number''' 352 | (source_connection, target_connection) = self.open_connections() 353 | self.read_models() 354 | self.get_records() 355 | 356 | @api.one 357 | def get_records(self): 358 | '''Get number of records for source and target models''' 359 | (source_connection, target_connection) = self.open_connections() 360 | source_models = self.env['etl.external_model'].search( 361 | [('type', '=', 'source'), ('manager_id', '=', self.id)]) 362 | target_models = self.env['etl.external_model'].search( 363 | [('type', '=', 'target'), ('manager_id', '=', self.id)]) 364 | source_models.get_records(source_connection) 365 | target_models.get_records(target_connection) 366 | 367 | @api.one 368 | def read_models(self): 369 | '''Get models and fields of source and target database''' 370 | # external_model_obj = self.pool['etl.external_model'] 371 | (source_connection, target_connection) = self.open_connections() 372 | self.read_model(source_connection, 'source')[self.id] 373 | self.read_model(target_connection, 'target')[self.id] 374 | source_external_models = self.env['etl.external_model'].search([ 375 | ('manager_id', '=', self.id), ('type', '=', 'source')]) 376 | target_external_models = self.env['etl.external_model'].search([ 377 | ('manager_id', '=', self.id), ('type', '=', 'target')]) 378 | source_external_models.read_fields(source_connection) 379 | target_external_models.read_fields(target_connection) 380 | 381 | @api.multi 382 | def read_model(self, connection, relation_type): 383 | ''' Get models for one manger and one type (source or target)''' 384 | res = {} 385 | for manager in self: 386 | external_model_obj = connection.model("ir.model") 387 | 388 | # osv_memory = False for not catching transients models 389 | domain = [('osv_memory', '=', False)] 390 | 391 | # catch de models excpections worlds and append to search domain 392 | words_exception = manager.model_exception_words 393 | if words_exception: 394 | words_exception = literal_eval(words_exception) 395 | for exception in words_exception: 396 | domain.append(('model', 'not like', exception)) 397 | 398 | # get external model ids 399 | external_model_ids = external_model_obj.search(domain) 400 | 401 | # read id, model and name of external models 402 | external_model_fields = ['.id', 'model', 'name'] 403 | export_data = external_model_obj.export_data( 404 | external_model_ids, external_model_fields) 405 | 406 | # We fix .id to id because we are going to use it 407 | external_model_fields[0] = 'id' 408 | 409 | # We add the type, manager and sequence to external fields and data 410 | external_model_fields.extend( 411 | ['type', 'manager_id/.id', 'sequence']) 412 | external_model_data = [] 413 | for record in export_data['datas']: 414 | # we extend each record with type, manager and a sequence 415 | record.extend([relation_type, manager.id, int(record[0]) * 10]) 416 | # replace the .id with our own external identifier 417 | record[0] = 'man_%s_%s_%s' % ( 418 | str(manager.id), 419 | relation_type, 420 | str(record[1]).replace('.', '_') 421 | ) 422 | external_model_data.append(record) 423 | 424 | # Load external_model_data to external models model 425 | rec_ids = self.env['etl.external_model'].load( 426 | external_model_fields, external_model_data) 427 | res[manager.id] = rec_ids 428 | return res 429 | 430 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 431 | -------------------------------------------------------------------------------- /etl/report/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingadhoc/odoo-etl/d5f54db11c5f916da79e23c52037ea36a12640a4/etl/report/README -------------------------------------------------------------------------------- /etl/report/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # odoo ETL 3 | # Copyright (C) 2015 Ingenieria ADHOC 4 | # No email 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Affero General Public License as 8 | # published by the Free Software Foundation, either version 3 of the 9 | # License, or (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Affero General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Affero General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | 21 | 22 | 23 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 24 | -------------------------------------------------------------------------------- /etl/security/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingadhoc/odoo-etl/d5f54db11c5f916da79e23c52037ea36a12640a4/etl/security/README -------------------------------------------------------------------------------- /etl/security/etl_group.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | odoo ETL 6 | odoo ETL 7 | 1 8 | 9 | 10 | 11 | User 12 | 13 | 14 | 15 | 16 | Manager 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | -------------------------------------------------------------------------------- /etl/security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" 2 | "access_etl_manager_manager","etl_manager.manager","model_etl_manager","group_manager",1,1,1,1 3 | "access_etl_manager_user","etl_manager.user","model_etl_manager","group_user",1,1,1,1 4 | "access_etl_action_manager","etl_action.manager","model_etl_action","group_manager",1,1,1,1 5 | "access_etl_action_user","etl_action.user","model_etl_action","group_user",1,1,1,1 6 | "access_etl_field_mapping_manager","etl_field_mapping.manager","model_etl_field_mapping","group_manager",1,1,1,1 7 | "access_etl_field_mapping_user","etl_field_mapping.user","model_etl_field_mapping","group_user",1,1,1,1 8 | "access_etl_external_model_manager","etl_external_model.manager","model_etl_external_model","group_manager",1,1,1,1 9 | "access_etl_external_model_user","etl_external_model.user","model_etl_external_model","group_user",1,1,1,1 10 | "access_etl_field_manager","etl_field.manager","model_etl_field","group_manager",1,1,1,1 11 | "access_etl_field_user","etl_field.user","model_etl_field","group_user",1,1,1,1 12 | "access_etl_value_mapping_field_manager","etl_value_mapping_field.manager","model_etl_value_mapping_field","group_manager",1,1,1,1 13 | "access_etl_value_mapping_field_user","etl_value_mapping_field.user","model_etl_value_mapping_field","group_user",1,1,1,1 14 | "access_etl_value_mapping_field_detail_manager","etl_value_mapping_field_detail.manager","model_etl_value_mapping_field_detail","group_manager",1,1,1,1 15 | "access_etl_value_mapping_field_detail_user","etl_value_mapping_field_detail.user","model_etl_value_mapping_field_detail","group_user",1,1,1,1 16 | "access_etl_external_model_record_manager","etl_external_model_record.manager","model_etl_external_model_record","group_manager",1,1,1,1 17 | "access_etl_external_model_record_user","etl_external_model_record.user","model_etl_external_model_record","group_user",1,1,1,1 18 | "access_etl_value_mapping_field_value_manager","etl_value_mapping_field_value.manager","model_etl_value_mapping_field_value","group_manager",1,1,1,1 19 | "access_etl_value_mapping_field_value_user","etl_value_mapping_field_value.user","model_etl_value_mapping_field_value","group_user",1,1,1,1 20 | -------------------------------------------------------------------------------- /etl/static/description/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingadhoc/odoo-etl/d5f54db11c5f916da79e23c52037ea36a12640a4/etl/static/description/icon.png -------------------------------------------------------------------------------- /etl/test/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ingadhoc/odoo-etl/d5f54db11c5f916da79e23c52037ea36a12640a4/etl/test/README -------------------------------------------------------------------------------- /etl/value_mapping_field.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | 9 | 10 | class value_mapping_field(models.Model): 11 | """""" 12 | 13 | _name = 'etl.value_mapping_field' 14 | _description = 'value_mapping_field' 15 | 16 | name = fields.Char( 17 | string='Field Name', 18 | required=True 19 | ) 20 | type = fields.Selection( 21 | [(u'id', u'Id'), (u'char', u'Char (not implemented yet)'), (u'selection', u'Selection')], 22 | string='Type', 23 | required=True 24 | ) 25 | source_model_id = fields.Many2one( 26 | 'etl.external_model', 27 | string='Source Model' 28 | ) 29 | target_model_id = fields.Many2one( 30 | 'etl.external_model', 31 | string='Target Model' 32 | ) 33 | log = fields.Text( 34 | string='log' 35 | ) 36 | value_mapping_field_detail_ids = fields.One2many( 37 | 'etl.value_mapping_field_detail', 38 | 'value_mapping_field_id', 39 | string='Details' 40 | ) 41 | value_mapping_field_value_ids = fields.One2many( 42 | 'etl.value_mapping_field_value', 43 | 'value_mapping_field_id', 44 | string='Mapping Values' 45 | ) 46 | manager_id = fields.Many2one( 47 | 'etl.manager', 48 | ondelete='cascade', 49 | string='manager_id', 50 | required=True 51 | ) 52 | 53 | _constraints = [ 54 | ] 55 | 56 | @api.one 57 | def map_record(self): 58 | value_mapping_data = [] 59 | for source_record in self.source_model_id.external_model_record_ids: 60 | domain = [ 61 | ('external_model_id', '=', self.target_model_id.id), 62 | ('name', 'ilike', source_record.name)] 63 | target_record = self.env[ 64 | 'etl.external_model_record'].search(domain, limit=1) 65 | value_mapping_data.append([ 66 | 'value_mapping_' + str(source_record.id), 67 | source_record.id, 68 | target_record and target_record.id or False, 69 | self.id, 70 | ]) 71 | 72 | value_mapping_fields = [ 73 | 'id', 74 | 'source_external_model_record_id/.id', 75 | 'target_external_model_record_id/.id', 76 | 'value_mapping_field_id/.id'] 77 | import_result = self.env['etl.value_mapping_field_detail'].load( 78 | value_mapping_fields, value_mapping_data) 79 | 80 | # write log and domain if active field exist 81 | self.log = import_result 82 | 83 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 84 | -------------------------------------------------------------------------------- /etl/value_mapping_field_detail.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | 9 | 10 | class value_mapping_field_detail(models.Model): 11 | """""" 12 | 13 | _name = 'etl.value_mapping_field_detail' 14 | _description = 'value_mapping_field_detail' 15 | 16 | source_id = fields.Char( 17 | string='Source ID' 18 | ) 19 | source_value = fields.Char( 20 | string='Source Value' 21 | ) 22 | target_id = fields.Char( 23 | string='Target ID' 24 | ) 25 | target_value = fields.Char( 26 | string='Target Value' 27 | ) 28 | source_external_model_record_id = fields.Many2one( 29 | 'etl.external_model_record', 30 | string='Source External Model Record' 31 | ) 32 | target_external_model_record_id = fields.Many2one( 33 | 'etl.external_model_record', 34 | string='Target External Model Record' 35 | ) 36 | source_value_id = fields.Many2one( 37 | 'etl.value_mapping_field_value', 38 | string='Source Value' 39 | ) 40 | target_value_id = fields.Many2one( 41 | 'etl.value_mapping_field_value', 42 | string='Target Value' 43 | ) 44 | value_mapping_field_id = fields.Many2one( 45 | 'etl.value_mapping_field', 46 | ondelete='cascade', 47 | string='value_mapping_field_id', 48 | required=True 49 | ) 50 | source_name = fields.Char( 51 | related='source_external_model_record_id.name', 52 | string='Source Name', 53 | readonly=True, 54 | ) 55 | source_model_id = fields.Many2one( 56 | related='value_mapping_field_id.source_model_id', 57 | relation='etl.external_model', 58 | string='Source Model', 59 | readonly=True, 60 | ) 61 | target_model_id = fields.Many2one( 62 | related='value_mapping_field_id.target_model_id', 63 | relation='etl.external_model', 64 | string='Target Model', 65 | readonly=True, 66 | ) 67 | 68 | _constraints = [ 69 | ] 70 | 71 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 72 | -------------------------------------------------------------------------------- /etl/value_mapping_field_value.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ############################################################################## 3 | # For copyright and license notices, see __openerp__.py file in module root 4 | # directory 5 | ############################################################################## 6 | from openerp import models, fields, api, _ 7 | from openerp.exceptions import Warning 8 | 9 | 10 | class value_mapping_field_value(models.Model): 11 | """""" 12 | 13 | _name = 'etl.value_mapping_field_value' 14 | _description = 'value_mapping_field_value' 15 | 16 | ext_id = fields.Char( 17 | string='Key', 18 | required=True 19 | ) 20 | name = fields.Char( 21 | string='Help Name', 22 | required=True 23 | ) 24 | value_mapping_field_id = fields.Many2one( 25 | 'etl.value_mapping_field', 26 | ondelete='cascade', 27 | string='value_mapping_field_id', 28 | required=True 29 | ) 30 | 31 | _constraints = [ 32 | ] 33 | 34 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: 35 | -------------------------------------------------------------------------------- /etl/view/action_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | etl.action.select 7 | etl.action 8 | 9 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | etl.action.form 36 | etl.action 37 | 38 |
39 |
40 |
48 | 49 |
50 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | 98 |
99 |
100 |
101 | 102 | 103 | 104 | etl.action.tree 105 | etl.action 106 | 107 | 110 | 112 | 115 | 117 | 119 | 121 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | Actions 131 | etl.action 132 | form 133 | tree,form 134 | 135 | 136 | {'search_default_not_grey':1} 137 | 138 |

Click to create a Actions.

139 |
140 |
141 | 142 | 149 |
150 |
151 | 153 | -------------------------------------------------------------------------------- /etl/view/etl_menuitem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 17 | 18 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /etl/view/external_model_record_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | etl.external_model_record.select 7 | etl.external_model_record 8 | 9 | 10 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | etl.external_model_record.form 23 | etl.external_model_record 24 | 25 |
26 |
27 |
28 | 29 |
30 |
31 | 32 | 34 | 36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 | 44 | 45 | etl.external_model_record.tree 46 | etl.external_model_record 47 | 48 | 50 | 52 | 54 | 55 | 56 | 57 | 58 |
59 |
60 | 62 | -------------------------------------------------------------------------------- /etl/view/external_model_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | etl.external_model.select 7 | etl.external_model 8 | 9 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | etl.external_model.form 33 | etl.external_model 34 | 35 |
36 |
37 | 38 |
41 | 42 |
43 |
44 | 45 | 47 | 49 | 51 | 53 | 56 | 58 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | 74 |
75 |
76 |
77 | 78 | 79 | 80 | etl.external_model.tree 81 | etl.external_model 82 | 83 | 85 | 87 | 89 | 91 | 93 | 95 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | External Models 104 | etl.external_model 105 | form 106 | tree,form 107 | 108 | 109 | 110 |

Click to create a External Models.

111 |
112 |
113 | 114 | 121 |
122 |
123 | 125 | -------------------------------------------------------------------------------- /etl/view/field_mapping_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | etl.field_mapping.select 7 | etl.field_mapping 8 | 9 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | etl.field_mapping.form 37 | etl.field_mapping 38 | 39 |
40 | 41 | 42 | 43 |
44 |
48 | 49 |
50 |
51 | 52 | 54 | 57 | 61 | 64 | 68 | 71 | 74 | 78 | 82 | 85 | 87 | 88 |
89 | 90 | 91 |
92 |
93 | 94 | 95 | 96 | etl.field_mapping.tree 97 | etl.field_mapping 98 | 99 | 102 | 104 | 106 | 108 | 110 | 111 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Fields Mapping 121 | etl.field_mapping 122 | form 123 | tree,form 124 | 125 | 126 | 127 |

Click to create a Fields Mapping.

128 |
129 |
130 | 131 | 138 | 139 |
140 |
141 | 143 | -------------------------------------------------------------------------------- /etl/view/field_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | etl.field.select 7 | etl.field 8 | 9 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | etl.field.form 33 | etl.field 34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 59 |
60 | 61 |
62 |
63 |
64 | 65 | 66 | 67 | etl.field.tree 68 | etl.field 69 | 70 | 72 | 74 | 76 | 78 | 80 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | External Fields 89 | etl.field 90 | form 91 | tree,form 92 | 93 | 94 | 95 |

Click to create a External Fields.

96 |
97 |
98 | 99 | 106 |
107 |
108 | 110 | -------------------------------------------------------------------------------- /etl/view/manager_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | etl.manager.select 7 | etl.manager 8 | 9 | 10 | 12 | 14 | 16 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | etl.manager.form 29 | etl.manager 30 | 31 |
32 |
33 |
46 | 47 |
48 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 66 | 68 | 70 | 73 | 75 | 76 | 78 | 80 | 82 | 84 | 87 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |