diff --git a/.travis.yml b/.travis.yml index 3bf7db9e..a9b9a1b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,6 @@ matrix: - env: TOXENV=py37-djangodev-mysql - env: TOXENV=py37-djangodev-postgresql include: - - python: 3.7 - env: TOXENV=py37-django111-sqlite - python: 3.7 env: TOXENV=py37-django20-sqlite - python: 3.7 @@ -28,6 +26,21 @@ matrix: env: TOXENV=checkqa - python: 3.7 env: TOXENV=docs + - stage: deploy + python: 3.7 + script: skip + deploy: + provider: pypi + user: jazzband + server: https://jazzband.co/projects/django-cities-light/upload + distributions: sdist bdist_wheel + password: + secure: TCH5tGIggL2wsWce2svMwpEpPiwVOYqq1R3uSBTexszleP0OafNq/wZk2KZEReR5w1Aq68qp5F5Eeh2ZjJTq4f9M4LtTvqQzrmyNP55DYk/uB1rBJm9b4gBgMtAknxdI2g7unkhQEDo4suuPCVofM7rrDughySNpmvlUQYDttHQ= + skip_existing: true + on: + tags: true + repo: jazzband/django-cities-light + python: 3.7 install: - pip install -U pip - pip install tox codecov diff --git a/CHANGELOG b/CHANGELOG index 0ef41eef..23887a14 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +2020-08-28 3.8.0 + Fix for subregion #229 + Documentation improvement #221 #189 + General improvements on warnings and pylint recomendations + Dropped support of Django 1.11 + 2020-08-06 3.7.0 Fix for subregion Drop support for python 2 diff --git a/README.rst b/README.rst index d3741d7c..7e7b6e24 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ database, you should use Requirements: - Python >= 3.6 -- Django >= 1.11 +- Django >= 2.0 - MySQL or PostgreSQL or SQLite. Yes, for some reason, code that used to work on MySQL (not without pain xD) @@ -82,6 +82,16 @@ By default, update procedure attempts to update all fields, including Country/Re ./manage.py cities_light --keep-slugs +Get more cities +--------------- + +The configuration parameter CITIES_LIGHT_CITY_SOURCES, comes with the default value +http://download.geonames.org/export/dump/cities15000.zip that has cities with a population +over 15000, if you need to load cities with less population please use another source. For the list +of available source please check here: http://download.geonames.org/export/dump/readme.txt + + + Using fixtures -------------- diff --git a/cities_light/contrib/restframework3.py b/cities_light/contrib/restframework3.py index 351781b8..0c0ce20d 100644 --- a/cities_light/contrib/restframework3.py +++ b/cities_light/contrib/restframework3.py @@ -13,15 +13,16 @@ If rest_framework (v3) is installed, all you have to do is add this url include:: - url(r'^cities_light/api/', include('cities_light.contrib.restframework3')), + path(r'^cities_light/api/', + include('cities_light.contrib.restframework3')), And that's all ! """ +from django.urls import include, path from rest_framework import viewsets, relations from rest_framework.serializers import HyperlinkedModelSerializer from rest_framework import routers -from django.conf.urls import url, include from ..loading import get_cities_models @@ -127,7 +128,7 @@ def get_queryset(self): """ Allows a GET param, 'q', to be used against search_names. """ - queryset = super(CitiesLightListModelViewSet, self).get_queryset() + queryset = self.queryset if self.request.GET.get('q', None): return queryset.filter( @@ -146,5 +147,5 @@ def get_queryset(self): basename='cities-light-api-subregion') urlpatterns = [ - url(r'^', include(router.urls)), + path('', include(router.urls)), ] diff --git a/cities_light/downloader.py b/cities_light/downloader.py index cbf88d72..33f556a4 100644 --- a/cities_light/downloader.py +++ b/cities_light/downloader.py @@ -42,7 +42,8 @@ def download(self, source, destination, force=False): return True - def source_matches_destination(self, source, destination): + @staticmethod + def source_matches_destination(source, destination): """Return True if source and destination point to the same file.""" parsed_source = urlparse(source) if parsed_source.scheme == 'file': @@ -56,7 +57,8 @@ def source_matches_destination(self, source, destination): return True return False - def needs_downloading(self, source, destination, force): + @staticmethod + def needs_downloading(source, destination, force): """Return True if source should be downloaded to destination.""" src_file = urlopen(source) src_size = int(src_file.headers['content-length']) diff --git a/cities_light/geonames.py b/cities_light/geonames.py index b1978d36..7eaac890 100644 --- a/cities_light/geonames.py +++ b/cities_light/geonames.py @@ -14,7 +14,7 @@ class Geonames(object): def __init__(self, url, force=False): # Creating a directory if not exist if not os.path.exists(DATA_DIR): - self.logger.info('Creating %s' % DATA_DIR) + self.logger.info('Creating %s', DATA_DIR) os.mkdir(DATA_DIR) destination_file_name = url.split('/')[-1] @@ -39,10 +39,11 @@ def __init__(self, url, force=False): self.file_path = os.path.join( DATA_DIR, destination_file_name) - def download(self, url, path, force=False): + @staticmethod + def download(url, path, force=False): downloader = Downloader() - # Returns true or false(either downloded or not based on - # the condition in downloader.py) + # Returns true or false(either downloaded or not based on + # the condition in downloader.py return downloader.download( source=url, destination=path, @@ -52,8 +53,8 @@ def download(self, url, path, force=False): def extract(self, zip_path, file_name): destination = os.path.join(DATA_DIR, file_name) - self.logger.info('Extracting %s from %s into %s' % ( - file_name, zip_path, destination)) + self.logger.info('Extracting %s from %s into %s', + file_name, zip_path, destination) # Extracting the file in the data directory zip_file = zipfile.ZipFile(zip_path) if zip_file: @@ -61,10 +62,8 @@ def extract(self, zip_path, file_name): def parse(self): file = open(self.file_path, encoding='utf-8', mode='r') - line = True for line in file: - line = line.strip() # If the line is blank/empty or a comment, skip it and continue if len(line) < 1 or line[0] == '#': diff --git a/cities_light/management/commands/cities_light.py b/cities_light/management/commands/cities_light.py index 6aa35373..3e5916d4 100644 --- a/cities_light/management/commands/cities_light.py +++ b/cities_light/management/commands/cities_light.py @@ -256,7 +256,8 @@ def _clear_identity_maps(self): del self._subregion_codes self._country_codes = {} self._region_codes = collections.defaultdict(dict) - self._subregion_codes = collections.defaultdict(dict) + self._subregion_codes = collections.defaultdict( + lambda: collections.defaultdict(dict)) def _get_country_id(self, country_code2): """ @@ -289,12 +290,12 @@ def _get_subregion_id(self, country_code2, region_id, subregion_id): self._region_codes[country_id][region_id] = Region.objects.get( country_id=country_id, geoname_code=region_id).pk - if subregion_id not in self._subregion_codes[country_id]: - self._subregion_codes[country_id][subregion_id] = \ + if subregion_id not in self._subregion_codes[country_id][region_id]: + self._subregion_codes[country_id][region_id][subregion_id] = \ SubRegion.objects.get( region_id=self._region_codes[country_id][region_id], geoname_code=subregion_id).pk - return self._subregion_codes[country_id][subregion_id] + return self._subregion_codes[country_id][region_id][subregion_id] def country_import(self, items): try: diff --git a/cities_light/management/commands/cities_light_fixtures.py b/cities_light/management/commands/cities_light_fixtures.py index 10f06602..6e845661 100644 --- a/cities_light/management/commands/cities_light_fixtures.py +++ b/cities_light/management/commands/cities_light_fixtures.py @@ -59,8 +59,7 @@ def create_parser(self, *args, **kwargs): parser.formatter_class = RawTextHelpFormatter return parser - @staticmethod - def add_arguments(parser): + def add_arguments(self, parser): parser.add_argument( 'subcommand', type=str, diff --git a/cities_light/receivers.py b/cities_light/receivers.py index 2c0df970..095933d7 100644 --- a/cities_light/receivers.py +++ b/cities_light/receivers.py @@ -33,18 +33,18 @@ def city_country(sender, instance, **kwargs): def city_search_names(sender, instance, **kwargs): search_names = set() - country_names = set((instance.country.name,)) + country_names = {instance.country.name, } if instance.country.alternate_names: for n in instance.country.alternate_names.split(';'): country_names.add(n) - city_names = set((instance.name,)) + city_names = {instance.name, } if instance.alternate_names: for n in instance.alternate_names.split(';'): city_names.add(n) if instance.region_id: - region_names = set((instance.region.name,)) + region_names = {instance.region.name, } if instance.region.alternate_names: for n in instance.region.alternate_names.split(';'): region_names.add(n) @@ -94,6 +94,8 @@ def filter_non_cities(sender, items, **kwargs): """ if items[7] not in INCLUDE_CITY_TYPES: raise InvalidItems() + + city_items_pre_import.connect(filter_non_cities) @@ -110,6 +112,8 @@ def filter_non_included_countries_country(sender, items, **kwargs): if items[0].split('.')[0] not in INCLUDE_COUNTRIES: raise InvalidItems() + + country_items_pre_import.connect(filter_non_included_countries_country) @@ -126,6 +130,8 @@ def filter_non_included_countries_region(sender, items, **kwargs): if items[0].split('.')[0] not in INCLUDE_COUNTRIES: raise InvalidItems() + + region_items_pre_import.connect(filter_non_included_countries_region) @@ -142,6 +148,8 @@ def filter_non_included_countries_subregion(sender, items, **kwargs): if items[0].split('.')[0] not in INCLUDE_COUNTRIES: raise InvalidItems() + + subregion_items_pre_import.connect(filter_non_included_countries_subregion) @@ -158,4 +166,6 @@ def filter_non_included_countries_city(sender, items, **kwargs): if items[8].split('.')[0] not in INCLUDE_COUNTRIES: raise InvalidItems() + + city_items_pre_import.connect(filter_non_included_countries_city) diff --git a/cities_light/tests/test_contrib.py b/cities_light/tests/test_contrib.py index bf9ba112..f30d5812 100644 --- a/cities_light/tests/test_contrib.py +++ b/cities_light/tests/test_contrib.py @@ -11,7 +11,6 @@ from ..contrib.ajax_selects_lookups import ( CountryLookup, RegionLookup, - SubRegionLookup, CityLookup ) diff --git a/cities_light/tests/test_fixtures.py b/cities_light/tests/test_fixtures.py index 40eb4587..c10c5ab1 100644 --- a/cities_light/tests/test_fixtures.py +++ b/cities_light/tests/test_fixtures.py @@ -143,7 +143,7 @@ def test_call_with_base_url_and_fixtures_base_url_is_none(self, m_exists): # --base-url not specified with mock.patch(func) as _mock: exc = 'Please specify --base-url or settings' - with self.assertRaisesRegexp(CommandError, exc): + with self.assertRaisesRegex(CommandError, exc): call_command('cities_light_fixtures', 'load') _mock.assert_not_called() diff --git a/cities_light/tests/test_migrations.py b/cities_light/tests/test_migrations.py index cc6c50c0..7cb972cd 100644 --- a/cities_light/tests/test_migrations.py +++ b/cities_light/tests/test_migrations.py @@ -1,15 +1,12 @@ from __future__ import unicode_literals -import pytest -import io + from django import test from django.apps import apps -from django.db.migrations.state import ProjectState -from django.db.migrations import Migration +from django.db.migrations.autodetector import MigrationAutodetector from django.db.migrations.loader import MigrationLoader from django.db.migrations.questioner import ( - InteractiveMigrationQuestioner, MigrationQuestioner, -) -from django.db.migrations.autodetector import MigrationAutodetector + InteractiveMigrationQuestioner, ) +from django.db.migrations.state import ProjectState class TestNoMigrationLeft(test.TestCase): diff --git a/setup.py b/setup.py index 2f5a80b6..3cbf7936 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,4 @@ -from setuptools import setup, find_packages -import shutil -import sys +from setuptools import setup import os import os.path diff --git a/tox.ini b/tox.ini index 459e7867..d1c60e37 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,9 @@ [tox] envlist = - py{35,36, 37,38}-django{111,22,30,31}{-sqlite,-mysql,-postgresql}, + py{35,36, 37,38}-django{20,22,30,31}{-sqlite,-mysql,-postgresql}, py{35,36, 37,38}-djangodev{-sqlite,-mysql,-postgresql}, checkqa, + pylint, docs skip_missing_interpreters = True sitepackages = False @@ -39,6 +40,8 @@ deps = pytest-cov mock coverage + pylint + pylint-django djangorestframework django-dbdiff>=0.4.0 django-ajax-selects==1.6.0 @@ -56,7 +59,6 @@ whitelist_externals = psql deps = {[test]deps} - django111: Django>=1.11a,<2.0 django20: Django>=2.0,<2.1 django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 @@ -78,10 +80,16 @@ setenv = passenv = TEST_* DBDIFF_* DB_* [testenv:checkqa] -basepython = python3.5 +basepython = python3.7 commands = pep8 --ignore=E402,E124,E128 --exclude=tests,migrations cities_light deps = pep8 +[testenv:pylint] +basepython = python3.7 +commands = pylint -j 4 --load-plugins pylint_django cities_light/ -E +deps = + {[base]deps} + [testenv:dev] commands = deps =