Browse Source

[MIG] base_location_geonames_import: Migration to 12.0

Generated new readme.
Adapted the module due to the refactoring of base_location.
Adapted tests.
pull/643/head
Aitor Bouzas 6 years ago
committed by Pedro M. Baeza
parent
commit
336727cb2c
  1. 99
      base_location_geonames_import/README.rst
  2. 2
      base_location_geonames_import/__init__.py
  3. 16
      base_location_geonames_import/__manifest__.py
  4. 4
      base_location_geonames_import/models/__init__.py
  5. 1
      base_location_geonames_import/models/res_country.py
  6. 7
      base_location_geonames_import/readme/CONFIGURE.rst
  7. 7
      base_location_geonames_import/readme/CONTRIBUTORS.rst
  8. 2
      base_location_geonames_import/readme/DESCRIPTION.rst
  9. 4
      base_location_geonames_import/readme/INSTALL.rst
  10. 8
      base_location_geonames_import/readme/USAGE.rst
  11. 457
      base_location_geonames_import/static/description/index.html
  12. 1
      base_location_geonames_import/tests/__init__.py
  13. 51
      base_location_geonames_import/tests/test_base_location_geonames_import.py
  14. 2
      base_location_geonames_import/wizard/__init__.py
  15. 317
      base_location_geonames_import/wizard/geonames_import.py
  16. 15
      base_location_geonames_import/wizard/geonames_import_view.xml

99
base_location_geonames_import/README.rst

@ -1,62 +1,92 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
=============================
Base Location Geonames Import
=============================
This module adds a wizard to import cities and/or better zip entries from
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpartner--contact-lightgray.png?logo=github
:target: https://github.com/OCA/partner-contact/tree/12.0/base_location_geonames_import
:alt: OCA/partner-contact
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/partner-contact-12-0/partner-contact-12-0-base_location_geonames_import
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/134/12.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
This module adds a wizard to import cities and/or city zip entries from
`Geonames <http://www.geonames.org/>`_ database.
**Table of contents**
.. contents::
:local:
Installation
============
To install this module, you need the Python library 'requests'.
To install this module, you need the Python library 'requests'::
pip install requests
Configuration
=============
To access the menu to import better zip entries from Geonames,
you must add yourself to the groups *Technical features* and *Sales manager*.
To access the menu to import city zip entries from Geonames
you must add yourself to the groups *Administration / Settings* or, if you have sale module
installed, *Sales / Manager* group.
If want want/need to modify the default URL
If you want/need to modify the default URL
(http://download.geonames.org/export/zip/), you can set the *geonames.url*
system parameter.
Usage
=====
Go to *Settings > Technical > Cities/Locations Management > Import from Geonames*,
Go to *Contacts > Configuration > Localization > Import from Geonames*,
and click on it to open a wizard.
When you start the wizard, it will ask you to select a country. If the
country has been set-up to require the entry of cites from a list you will be
able to indicate if you want to import the cities or zip codes.
When you start the wizard, it will ask you to select a country.
Then, for the
selected country, it will delete all the current better zip entries, download
Then, for the selected country, it will delete all not detected entries, download
the latest version of the list of cities from geonames.org and create new
better zip entries.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/134/11.0
city zip entries.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/partner-contact/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Bugs are tracked on `GitHub Issues <https://github.com/OCA/partner-contact/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/partner-contact/issues/new?body=module:%20base_location_geonames_import%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* Akretion
* Agile Business Group
* Tecnativa
* AdaptiveCity
Contributors
------------
~~~~~~~~~~~~
* Alexis de Lattre <alexis.delattre@akretion.com>
* Lorenzo Battistini <lorenzo.battistini@agilebg.com>
@ -64,24 +94,21 @@ Contributors
* Dave Lasley <dave@laslabs.com>
* Jordi Ballester <jordi.ballester@eficent.com>
* Franco Tampieri <franco@tampieri.info>
* Aitor Bouzas <aitor.bouzas@adaptivecity.com>
Icon
----
Maintainers
~~~~~~~~~~~
* http://icon-park.com/icon/location-map-pin-orange3/
* http://commons.wikimedia.org/wiki/File:View-refresh.svg
Maintainer
----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.
This module is part of the `OCA/partner-contact <https://github.com/OCA/partner-contact/tree/12.0/base_location_geonames_import>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

2
base_location_geonames_import/__init__.py

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
from . import wizard

16
base_location_geonames_import/__manifest__.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 Akretion (Alexis de Lattre
# <alexis.delattre@akretion.com>)
# Copyright 2014 Lorenzo Battistini <lorenzo.battistini@agilebg.com>
@ -9,22 +8,23 @@
{
'name': 'Base Location Geonames Import',
'version': '11.0.1.1.1',
'version': '12.0.1.0.0',
'category': 'Partner Management',
'license': 'AGPL-3',
'summary': 'Import better zip entries from Geonames',
'summary': 'Import zip entries from Geonames',
'author': 'Akretion,'
'Agile Business Group,'
'Antiun Ingeniería S.L.,'
'Tecnativa,'
'AdaptiveCity,'
'Odoo Community Association (OCA)',
'website': 'http://www.akretion.com',
'depends': ['base_location'],
'external_dependencies': {'python': ['requests']},
'website': 'https://github.com/OCA/partner-contact',
'depends': [
'base_location',
],
'data': [
'data/res_country_data.xml',
'views/res_country_view.xml',
'wizard/geonames_import_view.xml',
],
],
'installable': True,
}

4
base_location_geonames_import/models/__init__.py

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Franco Tampieri, Freelancer http://franco.tampieri.info
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import res_country

1
base_location_geonames_import/models/res_country.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Franco Tampieri, Freelancer http://franco.tampieri.info
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

7
base_location_geonames_import/readme/CONFIGURE.rst

@ -0,0 +1,7 @@
To access the menu to import city zip entries from Geonames
you must add yourself to the groups *Administration / Settings* or, if you have sale module
installed, *Sales / Manager* group.
If you want/need to modify the default URL
(http://download.geonames.org/export/zip/), you can set the *geonames.url*
system parameter.

7
base_location_geonames_import/readme/CONTRIBUTORS.rst

@ -0,0 +1,7 @@
* Alexis de Lattre <alexis.delattre@akretion.com>
* Lorenzo Battistini <lorenzo.battistini@agilebg.com>
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
* Dave Lasley <dave@laslabs.com>
* Jordi Ballester <jordi.ballester@eficent.com>
* Franco Tampieri <franco@tampieri.info>
* Aitor Bouzas <aitor.bouzas@adaptivecity.com>

2
base_location_geonames_import/readme/DESCRIPTION.rst

@ -0,0 +1,2 @@
This module adds a wizard to import cities and/or city zip entries from
`Geonames <http://www.geonames.org/>`_ database.

4
base_location_geonames_import/readme/INSTALL.rst

@ -0,0 +1,4 @@
To install this module, you need the Python library 'requests'::
pip install requests

8
base_location_geonames_import/readme/USAGE.rst

@ -0,0 +1,8 @@
Go to *Contacts > Configuration > Localization > Import from Geonames*,
and click on it to open a wizard.
When you start the wizard, it will ask you to select a country.
Then, for the selected country, it will delete all not detected entries, download
the latest version of the list of cities from geonames.org and create new
city zip entries.

457
base_location_geonames_import/static/description/index.html

@ -0,0 +1,457 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>Base Location Geonames Import</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="base-location-geonames-import">
<h1 class="title">Base Location Geonames Import</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/partner-contact/tree/12.0/base_location_geonames_import"><img alt="OCA/partner-contact" src="https://img.shields.io/badge/github-OCA%2Fpartner--contact-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/partner-contact-12-0/partner-contact-12-0-base_location_geonames_import"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/134/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module adds a wizard to import cities and/or city zip entries from
<a class="reference external" href="http://www.geonames.org/">Geonames</a> database.</p>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
<li><a class="reference internal" href="#configuration" id="id2">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="id3">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
<p>To install this module, you need the Python library ‘requests’:</p>
<pre class="literal-block">
pip install requests
</pre>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id2">Configuration</a></h1>
<p>To access the menu to import city zip entries from Geonames
you must add yourself to the groups <em>Administration / Settings</em> or, if you have sale module
installed, <em>Sales / Manager</em> group.</p>
<p>If you want/need to modify the default URL
(<a class="reference external" href="http://download.geonames.org/export/zip/">http://download.geonames.org/export/zip/</a>), you can set the <em>geonames.url</em>
system parameter.</p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id3">Usage</a></h1>
<p>Go to <em>Contacts &gt; Configuration &gt; Localization &gt; Import from Geonames</em>,
and click on it to open a wizard.</p>
<p>When you start the wizard, it will ask you to select a country.</p>
<p>Then, for the selected country, it will delete all not detected entries, download
the latest version of the list of cities from geonames.org and create new
city zip entries.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/partner-contact/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/partner-contact/issues/new?body=module:%20base_location_geonames_import%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
<ul class="simple">
<li>Akretion</li>
<li>Agile Business Group</li>
<li>Tecnativa</li>
<li>AdaptiveCity</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
<ul class="simple">
<li>Alexis de Lattre &lt;<a class="reference external" href="mailto:alexis.delattre&#64;akretion.com">alexis.delattre&#64;akretion.com</a>&gt;</li>
<li>Lorenzo Battistini &lt;<a class="reference external" href="mailto:lorenzo.battistini&#64;agilebg.com">lorenzo.battistini&#64;agilebg.com</a>&gt;</li>
<li>Pedro M. Baeza &lt;<a class="reference external" href="mailto:pedro.baeza&#64;tecnativa.com">pedro.baeza&#64;tecnativa.com</a>&gt;</li>
<li>Dave Lasley &lt;<a class="reference external" href="mailto:dave&#64;laslabs.com">dave&#64;laslabs.com</a>&gt;</li>
<li>Jordi Ballester &lt;<a class="reference external" href="mailto:jordi.ballester&#64;eficent.com">jordi.ballester&#64;eficent.com</a>&gt;</li>
<li>Franco Tampieri &lt;<a class="reference external" href="mailto:franco&#64;tampieri.info">franco&#64;tampieri.info</a>&gt;</li>
<li>Aitor Bouzas &lt;<a class="reference external" href="mailto:aitor.bouzas&#64;adaptivecity.com">aitor.bouzas&#64;adaptivecity.com</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/partner-contact/tree/12.0/base_location_geonames_import">OCA/partner-contact</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

1
base_location_geonames_import/tests/__init__.py

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import test_base_location_geonames_import

51
base_location_geonames_import/tests/test_base_location_geonames_import.py

@ -1,19 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo.tests import common
from odoo.exceptions import UserError
class TestBaseLocationGeonamesImport(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestBaseLocationGeonamesImport, cls).setUpClass()
super().setUpClass()
cls.country = cls.env.ref('base.mc')
cls.country.enforce_cities = True
cls.wizard = cls.env['better.zip.geonames.import'].create({
cls.city = cls.env['res.city'].create({
'name': 'Test city',
'country_id': cls.country.id,
})
cls.wizard = cls.env['city.zip.geonames.import'].create({
'country_id': cls.country.id,
})
cls.wrong_country = cls.env['res.country'].create({
'name': 'Wrong country',
'code': 'ZZYYXX',
})
cls.wrong_wizard = cls.env['city.zip.geonames.import'].create({
'country_id': cls.wrong_country.id,
})
def test_import_country(self):
max_import = 10
@ -24,8 +35,8 @@ class TestBaseLocationGeonamesImport(common.SavepointCase):
])
self.assertTrue(state_count)
# Look if there are imported zips
zip_count = self.env['res.better.zip'].search_count([
('country_id', '=', self.country.id)
zip_count = self.env['res.city.zip'].search_count([
('city_id.country_id', '=', self.country.id)
])
self.assertEqual(zip_count, max_import)
@ -47,15 +58,15 @@ class TestBaseLocationGeonamesImport(common.SavepointCase):
])
self.assertEqual(city_count, city_count2)
zip_count = self.env['res.better.zip'].search_count([
('country_id', '=', self.country.id)
zip_count = self.env['res.city.zip'].search_count([
('city_id.country_id', '=', self.country.id)
])
self.assertEqual(zip_count, max_import)
def test_delete_old_entries(self):
zip_entry = self.env['res.better.zip'].create({
'city': 'Test city',
'country_id': self.country.id,
zip_entry = self.env['res.city.zip'].create({
'name': 'Brussels',
'city_id': self.city.id,
})
self.wizard.run_import()
self.assertFalse(zip_entry.exists())
@ -70,10 +81,10 @@ class TestBaseLocationGeonamesImport(common.SavepointCase):
def test_import_title(self):
self.wizard.letter_case = 'title'
self.wizard.with_context(max_import=1).run_import()
zip = self.env['res.better.zip'].search(
[('country_id', '=', self.country.id)], limit=1
zip = self.env['res.city.zip'].search(
[('city_id.country_id', '=', self.country.id)], limit=1
)
self.assertEqual(zip.city, zip.city.title())
self.assertEqual(zip.city_id.name, zip.city_id.name.title())
city = self.env['res.city'].search(
[('country_id', '=', self.country.id)], limit=1
@ -83,12 +94,18 @@ class TestBaseLocationGeonamesImport(common.SavepointCase):
def test_import_upper(self):
self.wizard.letter_case = 'upper'
self.wizard.with_context(max_import=1).run_import()
zip = self.env['res.better.zip'].search(
[('country_id', '=', self.country.id)], limit=1
zip = self.env['res.city.zip'].search(
[('city_id.country_id', '=', self.country.id)], limit=1
)
self.assertEqual(zip.city, zip.city.upper())
self.assertEqual(zip.city_id.name, zip.city_id.name.upper())
city = self.env['res.city'].search(
[('country_id', '=', self.country.id)], limit=1
)
self.assertEqual(city.name, city.name.upper())
def test_download_error(self):
"""Check that we get an error when trying to download
with a wrong country code"""
with self.assertRaises(UserError):
self.wrong_wizard.run_import()

2
base_location_geonames_import/wizard/__init__.py

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import geonames_import

317
base_location_geonames_import/wizard/geonames_import.py

@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2014-2016 Akretion (Alexis de Lattre
# <alexis.delattre@akretion.com>)
# Copyright 2014 Lorenzo Battistini <lorenzo.battistini@agilebg.com>
# Copyright 2016 Pedro M. Baeza <pedro.baeza@tecnativa.com>
# Copyright 2017 Eficent Business and IT Consulting Services, S.L.
# <contact@eficent.com>
# Copyright 2018 Aitor Bouzas <aitor.bouzas@adaptivecity.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models, tools
from odoo import _, api, fields, models
from odoo.exceptions import UserError
import requests
import tempfile
@ -20,26 +20,26 @@ import csv
logger = logging.getLogger(__name__)
class BetterZipGeonamesImport(models.TransientModel):
_name = 'better.zip.geonames.import'
_description = 'Import Better Zip from Geonames'
class CityZipGeonamesImport(models.TransientModel):
_name = 'city.zip.geonames.import'
_description = 'Import City Zips from Geonames'
_rec_name = 'country_id'
country_id = fields.Many2one('res.country', 'Country', required=True)
enforce_cities = fields.Boolean(string='Enforce Cities',
help='The city will be created as a '
'separate entity.',
related='country_id.enforce_cities',
readonly=True)
code_row_index = fields.Integer(
related='country_id.geonames_state_code_column',
readonly=True)
name_row_index = fields.Integer(
related='country_id.geonames_state_name_column')
letter_case = fields.Selection([
('unchanged', 'Unchanged'),
('title', 'Title Case'),
('upper', 'Upper Case'),
], string='Letter Case', default='unchanged',
], string='Letter Case', default='unchanged',
help="Converts retreived city and state names to Title Case "
"(upper case on each first letter of a word) or Upper Case "
"(all letters upper case).")
"(upper case on each first letter of a word) or Upper Case "
"(all letters upper case).")
@api.model
def transform_city_name(self, city, country):
@ -48,7 +48,12 @@ class BetterZipGeonamesImport(models.TransientModel):
:param country: Country record
:return: Transformed city name
"""
return city
res = city
if self.letter_case == 'title':
res = city.title()
elif self.letter_case == 'upper':
res = city.upper()
return res
@api.model
def _domain_search_res_city(self, row, country):
@ -56,127 +61,59 @@ class BetterZipGeonamesImport(models.TransientModel):
('country_id', '=', country.id)]
@api.model
def _domain_search_better_zip(self, row, country, res_city):
domain = [('name', '=', row[1]),
('city', '=', self.transform_city_name(row[2], country)),
('country_id', '=', country.id)]
def _domain_search_city_zip(self, row, res_city):
domain = [('name', '=', row[1])]
if res_city:
domain += [('city_id', '=', res_city.id)]
return domain
@api.model
def _prepare_res_city(self, row, country):
state = self.select_or_create_state(row, country)
def select_state(self, row, country):
code = row[self.code_row_index or 4]
return self.env['res.country.state'].search(
[('country_id', '=', country.id),
('code', '=', code)], limit=1,
)
@api.model
def select_city(self, row, country):
res_city_model = self.env['res.city']
return res_city_model.search(self._domain_search_res_city(
row, country), limit=1)
@api.model
def select_zip(self, row, country):
city = self.select_city(row, country)
return self.env['res.city.zip'].search(self._domain_search_city_zip(
row, city))
@api.model
def prepare_state(self, row, country):
return {
'name': row[self.name_row_index or 3],
'code': row[self.code_row_index or 4],
'country_id': country.id,
}
@api.model
def prepare_city(self, row, country, state_id):
vals = {
'name': self.transform_city_name(row[2], country),
'state_id': state.id,
'state_id': state_id,
'country_id': country.id,
}
return vals
@api.model
def _prepare_better_zip(self, row, country, res_city):
state = self.select_or_create_state(row, country)
city_name = self.transform_city_name(row[2], country)
def prepare_zip(self, row, city_id):
vals = {
'name': row[1],
'city_id': res_city and res_city.id or False,
'city': res_city and res_city.name or city_name,
'state_id': state.id,
'country_id': country.id,
'latitude': row[9],
'longitude': row[10],
'city_id': city_id,
}
return vals
@api.model
def create_better_zip(self, row, country, res_city):
if row[0] != country.code:
raise UserError(
_("The country code inside the file (%s) doesn't "
"correspond to the selected country (%s).")
% (row[0], country.code))
logger.debug('ZIP = %s - City = %s' % (row[1], row[2]))
if self.letter_case == 'title':
row[2] = row[2].title()
row[3] = row[3].title()
elif self.letter_case == 'upper':
row[2] = row[2].upper()
row[3] = row[3].upper()
if row[1] and row[2]:
zip_model = self.env['res.better.zip']
zips = zip_model.search(self._domain_search_better_zip(
row, country, res_city))
if zips:
return zips[0]
else:
vals = self._prepare_better_zip(row, country, res_city)
if vals:
logger.debug('Creating res.better.zip %s' % vals['name'])
return zip_model.create(vals)
else: # pragma: no cover
return False
@api.model
def create_res_city(self, row, country):
if row[0] != country.code:
raise UserError(
_("The country code inside the file (%s) doesn't "
"correspond to the selected country (%s).")
% (row[0], country.code))
logger.debug('Processing city creation for ZIP = %s - City = %s' %
(row[1], row[2]))
if self.letter_case == 'title':
row[2] = row[2].title()
row[3] = row[3].title()
elif self.letter_case == 'upper':
row[2] = row[2].upper()
row[3] = row[3].upper()
if row[2]:
res_city_model = self.env['res.city']
res_cities = res_city_model.search(self._domain_search_res_city(
row, country))
if res_cities:
return res_cities[0]
else:
vals = self._prepare_res_city(row, country)
if vals:
logger.debug('Creating res.city %s' % vals['name'])
return res_city_model.create(vals)
else: # pragma: no cover
return False
@tools.ormcache('country_id', 'code')
def _get_state(self, country_id, code, name):
state = self.env['res.country.state'].search(
[('country_id', '=', country_id),
('code', '=', code)], limit=1,
)
if state: # pragma: no cover
return state
else:
return self.env['res.country.state'].create({
'name': name,
'code': code,
'country_id': country_id,
})
@api.model
def select_or_create_state(
self, row, country, code_row_index=4, name_row_index=3):
if country.geonames_state_code_column:
code_row_index = country.geonames_state_code_column
if country.geonames_state_name_column:
name_row_index = country.geonames_state_name_column
return self._get_state(
country.id, row[code_row_index], row[name_row_index],
)
@api.multi
def run_import(self):
self.ensure_one()
zip_model = self.env['res.better.zip']
res_city_model = self.env['res.city']
def get_and_parse_csv(self):
country_code = self.country_id.code
config_url = self.env['ir.config_parameter'].get_param(
'geonames.url',
@ -188,47 +125,131 @@ class BetterZipGeonamesImport(models.TransientModel):
raise UserError(
_('Got an error %d when trying to download the file %s.')
% (res_request.status_code, url))
# Store current record list
res_cities_to_delete = res_city_model
zips_to_delete = zip_model.search(
[('country_id', '=', self.country_id.id)])
if self.enforce_cities:
res_cities_to_delete = res_city_model.search(
[('country_id', '=', self.country_id.id)])
f_geonames = zipfile.ZipFile(io.BytesIO(res_request.content))
tempdir = tempfile.mkdtemp(prefix='odoo')
f_geonames.extract('%s.txt' % country_code, tempdir)
logger.info('The geonames zipfile has been decompressed')
data_file = open(os.path.join(tempdir, '%s.txt' % country_code), 'r',
encoding='utf-8')
data_file.seek(0)
logger.info('Starting to create the cities and/or better zip entries')
max_import = self.env.context.get('max_import', 0)
reader = csv.reader(data_file, delimiter=' ')
for i, row in enumerate(reader):
res_city = False
if self.enforce_cities:
res_city = self.create_res_city(row, self.country_id)
if res_city in res_cities_to_delete:
res_cities_to_delete -= res_city
zip_code = self.create_better_zip(row, self.country_id,
res_city)
if zip_code in zips_to_delete:
zips_to_delete -= zip_code
if max_import and (i + 1) == max_import:
break
parsed_csv = [row for i, row in enumerate(reader)]
data_file.close()
if zips_to_delete and not max_import:
zips_to_delete.unlink()
logger.info('%d better zip entries deleted for country %s' %
(len(zips_to_delete), self.country_id.name))
if res_cities_to_delete and not max_import:
res_cities_to_delete.unlink()
logger.info('The geonames zipfile has been decompressed')
return parsed_csv
def _create_states(self, parsed_csv, search_states, max_import):
# States
state_vals_list = []
state_dict = {}
for i, row in enumerate(parsed_csv):
if max_import and i == max_import:
break
state = self.select_state(
row, self.country_id) if search_states else False
if not state:
state_vals = self.prepare_state(row, self.country_id)
if state_vals not in state_vals_list:
state_vals_list.append(state_vals)
else:
state_dict[state.code] = state.id
created_states = self.env['res.country.state'].create(state_vals_list)
for i, vals in enumerate(state_vals_list):
state_dict[vals['code']] = created_states[i].id
return state_dict
def _create_cities(self, parsed_csv,
search_cities, max_import, state_dict):
# Cities
city_vals_list = []
city_dict = {}
for i, row in enumerate(parsed_csv):
if max_import and i == max_import:
break
city = self.select_city(
row, self.country_id) if search_cities else False
if not city:
state_id = state_dict[
row[self.code_row_index or 4]]
city_vals = self.prepare_city(
row, self.country_id, state_id)
if city_vals not in city_vals_list:
city_vals_list.append(city_vals)
else:
city_dict[city.name] = city.id
created_cities = self.env['res.city'].create(city_vals_list)
for i, vals in enumerate(city_vals_list):
city_dict[vals['name']] = created_cities[i].id
return city_dict
@api.multi
def run_import(self):
self.ensure_one()
state_model = self.env['res.country.state']
zip_model = self.env['res.city.zip']
res_city_model = self.env['res.city']
# Store current record list
current_zips = zip_model.search(
[('city_id.country_id', '=', self.country_id.id)])
search_zips = True and len(current_zips) > 0 or False
current_cities = res_city_model.search(
[('country_id', '=', self.country_id.id)])
search_cities = True and len(current_cities) > 0 or False
current_states = state_model.search(
[('country_id', '=', self.country_id.id)])
search_states = True and len(current_states) > 0 or False
parsed_csv = self.get_and_parse_csv()
max_import = self.env.context.get('max_import', 0)
logger.info('Starting to create the cities and/or city zip entries')
state_dict = self._create_states(parsed_csv,
search_states, max_import)
city_dict = self._create_cities(parsed_csv,
search_cities, max_import, state_dict)
# Zips
zip_vals_list = []
for i, row in enumerate(parsed_csv):
if max_import and i == max_import:
break
# Don't search if there aren't any records
zip = False
if search_zips:
zip = self.select_zip(row, self.country_id)
if not zip:
city_id = city_dict[
self.transform_city_name(row[2], self.country_id)]
zip_vals = self.prepare_zip(row, city_id)
if zip_vals not in zip_vals_list:
zip_vals_list.append(zip_vals)
delete_zips = self.env['res.city.zip'].create(zip_vals_list)
current_zips -= delete_zips
if not max_import:
current_zips.unlink()
logger.info('%d city zip entries deleted for country %s' %
(len(current_zips), self.country_id.name))
# Since we wrapped the entire cities
# creation in a function we need
# to perform a search with city_dict in
# order to know which are the new ones so
# we can delete the old ones
created_cities = res_city_model.search(
[('country_id', '=', self.country_id.id),
('id', 'in', [value for key, value in city_dict.items()])]
)
current_cities -= created_cities
current_cities.unlink()
logger.info('%d res.city entries deleted for country %s' %
(len(res_cities_to_delete), self.country_id.name))
(len(current_cities), self.country_id.name))
logger.info(
'The wizard to create cities and/or better zip entries from '
'The wizard to create cities and/or city zip entries from '
'geonames has been successfully completed.')
return True

15
base_location_geonames_import/wizard/geonames_import_view.xml

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="better_zip_geonames_import_form" model="ir.ui.view">
<record id="city_zip_geonames_import_form" model="ir.ui.view">
<field name="name">Import from Geonames form view</field>
<field name="model">better.zip.geonames.import</field>
<field name="model">city.zip.geonames.import</field>
<field name="arch" type="xml">
<form string="Import from Geonames">
<group name="help">
@ -13,7 +13,6 @@
</group>
<group name="main">
<field name="country_id"/>
<field name="enforce_cities"/>
<field name="letter_case"/>
</group>
<footer>
@ -25,17 +24,17 @@
</field>
</record>
<record id="better_zip_geonames_import_action" model="ir.actions.act_window">
<record id="city_zip_geonames_import_action" model="ir.actions.act_window">
<field name="name">Import from Geonames</field>
<field name="res_model">better.zip.geonames.import</field>
<field name="res_model">city.zip.geonames.import</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem
id="better_zip_geonames_import_menu"
action="better_zip_geonames_import_action"
parent="base_location.locations_root_menu"
id="city_zip_geonames_import_menu"
action="city_zip_geonames_import_action"
parent="contacts.menu_localisation"
sequence="50"
/>

Loading…
Cancel
Save