diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml new file mode 100644 index 00000000..56eb0b34 --- /dev/null +++ b/.github/workflows/test-docs.yml @@ -0,0 +1,69 @@ +name: Test & Docs + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + tests: + timeout-minutes: 60 + runs-on: ubuntu-latest + services: + postgres: + image: postgis/postgis:12-2.5 + ports: + - 5432:5432 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + dependencies: [normal, minimal] + database: [postgresql] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: '**/pyproject.toml' + - name: Change dependencies to minimal supported versions + run: sed -i -e '/requires-python/!s/>=/==/g; /requires-python/!s/~=.*==\(.*\)/==\1/g; /requires-python/!s/~=/==/g;' pyproject.toml + if: matrix.dependencies == 'minimal' + - name: Install dependencies + run: | + sudo apt update + sudo apt install libgdal-dev + python -m pip install --upgrade pip + make develop installjs vendors + - name: run tests + run: make test + env: + DJANGO_SETTINGS_MODULE: 'umap.tests.settings' + UMAP_SETTINGS: 'umap/tests/settings.py' + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Install dependencies + run: | + python3 -m pip install -e .[test,dev] + + - name: Run Lint + run: make lint + + - name: Run Docs + run: make docs diff --git a/.gitignore b/.gitignore index 5b5d15b3..efb609d9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,12 @@ umap/settings/local/* docs/_build umap/remote_static tmp/* -node_modules/* umap/static/umap/vendors site/* .pytest_cache/ +node_modules +umap.conf +data ### Python ### # Byte-compiled / optimized / DLL files diff --git a/Makefile b/Makefile index a66e462d..0189f821 100644 --- a/Makefile +++ b/Makefile @@ -10,13 +10,21 @@ develop: ## Install the test and dev dependencies python3 -m pip install -e .[test,dev] playwright install -.PHONY: pretty-templates -pretty-templates: ## Prettify template files - djlint umap/templates --reformat +.PHONY: format +format: ## Format the code and templates files + djlint umap/templates --reformat &&\ + isort --profile black . &&\ + ruff format --target-version=py38 . -.PHONY: lint-templates -lint-templates: ## Lint template files - djlint umap/templates --lint +.PHONY: lint +lint: ## Lint the code and template files + djlint umap/templates --lint &&\ + isort --check --profile black . &&\ + ruff format --check --target-version=py38 . &&\ + vermin --no-tips --violations -t=3.8- . + +docs: ## Compile the docs + mkdocs build .PHONY: version version: ## Display the current version @@ -47,10 +55,10 @@ publish: ## Publish the Python package to Pypi make clean test: - py.test -xv umap/tests/ + pytest -xv umap/tests/ test-integration: - py.test -xv umap/tests/integration/ + pytest -xv umap/tests/integration/ clean: rm -f dist/* diff --git a/manage.py b/manage.py index 6686b796..c08a8d73 100644 --- a/manage.py +++ b/manage.py @@ -3,10 +3,7 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", - "umap.settings" - ) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umap.settings") from django.core.management import execute_from_command_line diff --git a/package-lock.json b/package-lock.json index f6c5ca40..557d3962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "WTFPL", "dependencies": { "@tmcw/togeojson": "^5.8.0", + "colorbrewer": "^1.5.6", "csv2geojson": "5.1.1", "dompurify": "^3.0.3", "georsstogeojson": "^0.1.0", @@ -31,6 +32,7 @@ "leaflet.path.drag": "0.0.6", "leaflet.photon": "0.8.0", "osmtogeojson": "^3.0.0-beta.3", + "simple-statistics": "^7.8.3", "togpx": "^0.5.4", "tokml": "0.4.0" }, @@ -494,6 +496,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorbrewer": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-1.5.6.tgz", + "integrity": "sha512-fONg2pGXyID8zNgKHBlagW8sb/AMShGzj4rRJfz5biZ7iuHQZYquSCLE/Co1oSQFmt/vvwjyezJCejQl7FG/tg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2122,6 +2129,14 @@ "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, + "node_modules/simple-statistics": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-7.8.3.tgz", + "integrity": "sha512-JFvMY00t6SBGtwMuJ+nqgsx9ylkMiJ5JlK9bkj8AdvniIe5615wWQYkKHXe84XtSuc40G/tlrPu0A5/NlJvv8A==", + "engines": { + "node": "*" + } + }, "node_modules/sinon": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.0.tgz", @@ -2898,6 +2913,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "colorbrewer": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/colorbrewer/-/colorbrewer-1.5.6.tgz", + "integrity": "sha512-fONg2pGXyID8zNgKHBlagW8sb/AMShGzj4rRJfz5biZ7iuHQZYquSCLE/Co1oSQFmt/vvwjyezJCejQl7FG/tg==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4143,6 +4163,11 @@ "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" }, + "simple-statistics": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/simple-statistics/-/simple-statistics-7.8.3.tgz", + "integrity": "sha512-JFvMY00t6SBGtwMuJ+nqgsx9ylkMiJ5JlK9bkj8AdvniIe5615wWQYkKHXe84XtSuc40G/tlrPu0A5/NlJvv8A==" + }, "sinon": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.0.tgz", diff --git a/pyproject.toml b/pyproject.toml index 7831d31a..a7ca170a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,16 +23,14 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] dependencies = [ - "Django>=4.1", + "Django>=4.2,<5", "django-agnocomplete==2.2.0", "django-compressor==4.3.1", "django-environ==0.10.0", @@ -47,17 +45,20 @@ dependencies = [ [project.optional-dependencies] dev = [ "hatch==1.7.0", - "black==23.3.0", + "ruff==0.1.6", "djlint==1.31.0", "mkdocs==1.5.2", + "vermin==1.5.2", + "pymdown-extensions==10.4", + "isort==5.12", ] test = [ "factory-boy==3.2.1", - "playwright==1.38.0", + "playwright>=1.39,<2", "pytest==6.2.5", "pytest-django==4.5.2", - "pytest-playwright==0.4.2", - + "pytest-playwright>=0.4.3,<1", + "pytest-xdist>=3.5.0,<4", ] docker = [ "uwsgi==2.0.21", diff --git a/pytest.ini b/pytest.ini index 610ba029..0d65930e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] DJANGO_SETTINGS_MODULE=umap.tests.settings -addopts = --pdbcls=IPython.terminal.debugger:Pdb --no-migrations +addopts = --pdbcls=IPython.terminal.debugger:Pdb --no-migrations --numprocesses auto diff --git a/umap/admin.py b/umap/admin.py index f686786d..22796267 100644 --- a/umap/admin.py +++ b/umap/admin.py @@ -1,10 +1,14 @@ from django.contrib.gis import admin -from .models import Map, DataLayer, Pictogram, TileLayer, Licence + +from .models import DataLayer, Licence, Map, Pictogram, TileLayer class TileLayerAdmin(admin.ModelAdmin): - list_display = ('name', 'rank', ) - list_editable = ('rank', ) + list_display = ( + "name", + "rank", + ) + list_editable = ("rank",) class MapAdmin(admin.GISModelAdmin): diff --git a/umap/autocomplete.py b/umap/autocomplete.py index 860c74f1..c2bd02c0 100644 --- a/umap/autocomplete.py +++ b/umap/autocomplete.py @@ -1,11 +1,9 @@ +from agnocomplete.core import AgnocompleteModel +from agnocomplete.register import register from django.conf import settings from django.contrib.auth import get_user_model -from agnocomplete.register import register -from agnocomplete.core import AgnocompleteModel - - @register class AutocompleteUser(AgnocompleteModel): model = get_user_model() @@ -13,5 +11,5 @@ class AutocompleteUser(AgnocompleteModel): def item(self, current_item): data = super().item(current_item) - data['url'] = current_item.get_url() + data["url"] = current_item.get_url() return data diff --git a/umap/bin/__init__.py b/umap/bin/__init__.py index 0a252ac3..7481b8f9 100644 --- a/umap/bin/__init__.py +++ b/umap/bin/__init__.py @@ -5,12 +5,9 @@ from django.core import management def main(): - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", - "umap.settings" - ) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umap.settings") management.execute_from_command_line() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/umap/decorators.py b/umap/decorators.py index b9b5232a..005e4215 100644 --- a/umap/decorators.py +++ b/umap/decorators.py @@ -1,13 +1,12 @@ from functools import wraps -from django.urls import reverse_lazy -from django.shortcuts import get_object_or_404 -from django.http import HttpResponseForbidden from django.conf import settings +from django.http import HttpResponseForbidden +from django.shortcuts import get_object_or_404 +from django.urls import reverse_lazy -from .views import simple_json_response from .models import Map - +from .views import simple_json_response LOGIN_URL = getattr(settings, "LOGIN_URL", "login") LOGIN_URL = reverse_lazy(LOGIN_URL) if not LOGIN_URL.startswith("/") else LOGIN_URL diff --git a/umap/forms.py b/umap/forms.py index ec94e7a8..d1225b22 100644 --- a/umap/forms.py +++ b/umap/forms.py @@ -1,12 +1,12 @@ from django import forms -from django.contrib.gis.geos import Point -from django.contrib.auth import get_user_model -from django.utils.translation import gettext_lazy as _ -from django.template.defaultfilters import slugify from django.conf import settings +from django.contrib.auth import get_user_model +from django.contrib.gis.geos import Point from django.forms.utils import ErrorList +from django.template.defaultfilters import slugify +from django.utils.translation import gettext_lazy as _ -from .models import Map, DataLayer +from .models import DataLayer, Map DEFAULT_LATITUDE = ( settings.LEAFLET_LATITUDE if hasattr(settings, "LEAFLET_LATITUDE") else 51 diff --git a/umap/management/commands/generate_js_locale.py b/umap/management/commands/generate_js_locale.py index 3e312fac..f5081445 100644 --- a/umap/management/commands/generate_js_locale.py +++ b/umap/management/commands/generate_js_locale.py @@ -1,24 +1,23 @@ from pathlib import Path -from django.core.management.base import BaseCommand from django.conf import settings +from django.core.management.base import BaseCommand from django.template.loader import render_to_string from django.utils.translation import to_locale -ROOT = Path(settings.PROJECT_DIR) / 'static/umap/locale/' +ROOT = Path(settings.PROJECT_DIR) / "static/umap/locale/" class Command(BaseCommand): - def handle(self, *args, **options): - self.verbosity = options['verbosity'] + self.verbosity = options["verbosity"] for code, name in settings.LANGUAGES: code = to_locale(code) if self.verbosity > 0: print("Processing", name) - path = ROOT / '{code}.json'.format(code=code) + path = ROOT / "{code}.json".format(code=code) if not path.exists(): - print(path, 'does not exist.', 'Skipping') + print(path, "does not exist.", "Skipping") else: with path.open(encoding="utf-8") as f: if self.verbosity > 1: @@ -26,12 +25,11 @@ class Command(BaseCommand): self.render(code, f.read()) def render(self, code, json): - path = ROOT / '{code}.js'.format(code=code) + path = ROOT / "{code}.js".format(code=code) with path.open("w", encoding="utf-8") as f: - content = render_to_string('umap/locale.js', { - "locale": json, - "locale_code": code - }) + content = render_to_string( + "umap/locale.js", {"locale": json, "locale_code": code} + ) if self.verbosity > 1: print("Exporting to", path) f.write(content) diff --git a/umap/management/commands/import_pictograms.py b/umap/management/commands/import_pictograms.py index caa1ceb4..1adf2447 100644 --- a/umap/management/commands/import_pictograms.py +++ b/umap/management/commands/import_pictograms.py @@ -59,7 +59,7 @@ class Command(BaseCommand): else: picto = Pictogram() picto.name = name - if (path.name != self.path.name): # Subfolders only + if path.name != self.path.name: # Subfolders only picto.category = path.name picto.attribution = self.attribution with (path / filename).open("rb") as f: diff --git a/umap/managers.py b/umap/managers.py index d1728890..1fc07d96 100644 --- a/umap/managers.py +++ b/umap/managers.py @@ -2,7 +2,9 @@ from django.db.models import Manager class PublicManager(Manager): - def get_queryset(self): - return super(PublicManager, self).get_queryset().filter( - share_status=self.model.PUBLIC) + return ( + super(PublicManager, self) + .get_queryset() + .filter(share_status=self.model.PUBLIC) + ) diff --git a/umap/middleware.py b/umap/middleware.py index d4c0e81e..ffe01ebf 100644 --- a/umap/middleware.py +++ b/umap/middleware.py @@ -5,13 +5,12 @@ from django.utils.translation import gettext as _ def readonly_middleware(get_response): - if not settings.UMAP_READONLY: raise MiddlewareNotUsed def middleware(request): - if request.method not in ['GET', 'OPTIONS']: - return HttpResponseForbidden(_('Site is readonly for maintenance')) + if request.method not in ["GET", "OPTIONS"]: + return HttpResponseForbidden(_("Site is readonly for maintenance")) return get_response(request) diff --git a/umap/migrations/0001_initial.py b/umap/migrations/0001_initial.py index fff32572..ec64d375 100644 --- a/umap/migrations/0001_initial.py +++ b/umap/migrations/0001_initial.py @@ -1,15 +1,15 @@ # Generated by Django 2.0.5 on 2018-05-19 09:27 -from django.conf import settings import django.contrib.gis.db.models.fields -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + import umap.fields import umap.models class Migration(migrations.Migration): - initial = True dependencies = [ @@ -18,90 +18,238 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='DataLayer', + name="DataLayer", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='name')), - ('description', models.TextField(blank=True, null=True, verbose_name='description')), - ('geojson', models.FileField(blank=True, null=True, upload_to=umap.models.upload_to)), - ('display_on_load', models.BooleanField(default=False, help_text='Display this layer on load.', verbose_name='display on load')), - ('rank', models.SmallIntegerField(default=0)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200, verbose_name="name")), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="description"), + ), + ( + "geojson", + models.FileField( + blank=True, null=True, upload_to=umap.models.upload_to + ), + ), + ( + "display_on_load", + models.BooleanField( + default=False, + help_text="Display this layer on load.", + verbose_name="display on load", + ), + ), + ("rank", models.SmallIntegerField(default=0)), ], options={ - 'ordering': ('rank',), + "ordering": ("rank",), }, ), migrations.CreateModel( - name='Licence', + name="Licence", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='name')), - ('details', models.URLField(help_text='Link to a page where the licence is detailed.', verbose_name='details')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200, verbose_name="name")), + ( + "details", + models.URLField( + help_text="Link to a page where the licence is detailed.", + verbose_name="details", + ), + ), ], options={ - 'ordering': ('name',), - 'abstract': False, + "ordering": ("name",), + "abstract": False, }, ), migrations.CreateModel( - name='Map', + name="Map", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='name')), - ('slug', models.SlugField()), - ('description', models.TextField(blank=True, null=True, verbose_name='description')), - ('center', django.contrib.gis.db.models.fields.PointField(geography=True, srid=4326, verbose_name='center')), - ('zoom', models.IntegerField(default=7, verbose_name='zoom')), - ('locate', models.BooleanField(default=False, help_text='Locate user on load?', verbose_name='locate')), - ('modified_at', models.DateTimeField(auto_now=True)), - ('edit_status', models.SmallIntegerField(choices=[(1, 'Everyone can edit'), (2, 'Only editors can edit'), (3, 'Only owner can edit')], default=3, verbose_name='edit status')), - ('share_status', models.SmallIntegerField(choices=[(1, 'everyone (public)'), (2, 'anyone with link'), (3, 'editors only')], default=1, verbose_name='share status')), - ('settings', umap.fields.DictField(blank=True, null=True, verbose_name='settings')), - ('editors', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='editors')), - ('licence', models.ForeignKey(default=umap.models.get_default_licence, help_text='Choose the map licence.', on_delete=django.db.models.deletion.SET_DEFAULT, to='umap.Licence', verbose_name='licence')), - ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='owned_maps', to=settings.AUTH_USER_MODEL, verbose_name='owner')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200, verbose_name="name")), + ("slug", models.SlugField()), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="description"), + ), + ( + "center", + django.contrib.gis.db.models.fields.PointField( + geography=True, srid=4326, verbose_name="center" + ), + ), + ("zoom", models.IntegerField(default=7, verbose_name="zoom")), + ( + "locate", + models.BooleanField( + default=False, + help_text="Locate user on load?", + verbose_name="locate", + ), + ), + ("modified_at", models.DateTimeField(auto_now=True)), + ( + "edit_status", + models.SmallIntegerField( + choices=[ + (1, "Everyone can edit"), + (2, "Only editors can edit"), + (3, "Only owner can edit"), + ], + default=3, + verbose_name="edit status", + ), + ), + ( + "share_status", + models.SmallIntegerField( + choices=[ + (1, "everyone (public)"), + (2, "anyone with link"), + (3, "editors only"), + ], + default=1, + verbose_name="share status", + ), + ), + ( + "settings", + umap.fields.DictField( + blank=True, null=True, verbose_name="settings" + ), + ), + ( + "editors", + models.ManyToManyField( + blank=True, to=settings.AUTH_USER_MODEL, verbose_name="editors" + ), + ), + ( + "licence", + models.ForeignKey( + default=umap.models.get_default_licence, + help_text="Choose the map licence.", + on_delete=django.db.models.deletion.SET_DEFAULT, + to="umap.Licence", + verbose_name="licence", + ), + ), + ( + "owner", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="owned_maps", + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ), ], options={ - 'ordering': ('name',), - 'abstract': False, + "ordering": ("name",), + "abstract": False, }, ), migrations.CreateModel( - name='Pictogram', + name="Pictogram", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='name')), - ('attribution', models.CharField(max_length=300)), - ('pictogram', models.ImageField(upload_to='pictogram')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200, verbose_name="name")), + ("attribution", models.CharField(max_length=300)), + ("pictogram", models.ImageField(upload_to="pictogram")), ], options={ - 'ordering': ('name',), - 'abstract': False, + "ordering": ("name",), + "abstract": False, }, ), migrations.CreateModel( - name='TileLayer', + name="TileLayer", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='name')), - ('url_template', models.CharField(help_text='URL template using OSM tile format', max_length=200)), - ('minZoom', models.IntegerField(default=0)), - ('maxZoom', models.IntegerField(default=18)), - ('attribution', models.CharField(max_length=300)), - ('rank', models.SmallIntegerField(blank=True, help_text='Order of the tilelayers in the edit box', null=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=200, verbose_name="name")), + ( + "url_template", + models.CharField( + help_text="URL template using OSM tile format", max_length=200 + ), + ), + ("minZoom", models.IntegerField(default=0)), + ("maxZoom", models.IntegerField(default=18)), + ("attribution", models.CharField(max_length=300)), + ( + "rank", + models.SmallIntegerField( + blank=True, + help_text="Order of the tilelayers in the edit box", + null=True, + ), + ), ], options={ - 'ordering': ('rank', 'name'), + "ordering": ("rank", "name"), }, ), migrations.AddField( - model_name='map', - name='tilelayer', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='maps', to='umap.TileLayer', verbose_name='background'), + model_name="map", + name="tilelayer", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="maps", + to="umap.TileLayer", + verbose_name="background", + ), ), migrations.AddField( - model_name='datalayer', - name='map', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='umap.Map'), + model_name="datalayer", + name="map", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="umap.Map" + ), ), ] diff --git a/umap/migrations/0002_tilelayer_tms.py b/umap/migrations/0002_tilelayer_tms.py index 9b966815..9357e183 100644 --- a/umap/migrations/0002_tilelayer_tms.py +++ b/umap/migrations/0002_tilelayer_tms.py @@ -4,15 +4,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('umap', '0001_initial'), + ("umap", "0001_initial"), ] operations = [ migrations.AddField( - model_name='tilelayer', - name='tms', + model_name="tilelayer", + name="tms", field=models.BooleanField(default=False), ), ] diff --git a/umap/migrations/0003_add_tilelayer.py b/umap/migrations/0003_add_tilelayer.py index c1e35be9..672d91fb 100644 --- a/umap/migrations/0003_add_tilelayer.py +++ b/umap/migrations/0003_add_tilelayer.py @@ -5,22 +5,26 @@ from django.db import migrations def add_tilelayer(apps, *args): - TileLayer = apps.get_model('umap', 'TileLayer') + TileLayer = apps.get_model("umap", "TileLayer") if TileLayer.objects.count(): return TileLayer( - name='Positron', - url_template=('https://cartodb-basemaps-{s}.global.ssl.fastly.net/' - 'light_all/{z}/{x}/{y}.png'), - attribution=('© [[http://www.openstreetmap.org/copyright|' - 'OpenStreetMap]] contributors, © ' - '[[https://carto.com/attributions|CARTO]]')).save() + name="Positron", + url_template=( + "https://cartodb-basemaps-{s}.global.ssl.fastly.net/" + "light_all/{z}/{x}/{y}.png" + ), + attribution=( + "© [[http://www.openstreetmap.org/copyright|" + "OpenStreetMap]] contributors, © " + "[[https://carto.com/attributions|CARTO]]" + ), + ).save() class Migration(migrations.Migration): - dependencies = [ - ('umap', '0002_tilelayer_tms'), + ("umap", "0002_tilelayer_tms"), ] operations = [ diff --git a/umap/migrations/0004_add_licence.py b/umap/migrations/0004_add_licence.py index c5e7bff7..955ae687 100644 --- a/umap/migrations/0004_add_licence.py +++ b/umap/migrations/0004_add_licence.py @@ -5,18 +5,15 @@ from django.db import migrations def add_licence(apps, *args): - Licence = apps.get_model('umap', 'Licence') + Licence = apps.get_model("umap", "Licence") if Licence.objects.count(): return - Licence( - name='ODbL', - details='http://opendatacommons.org/licenses/odbl/').save() + Licence(name="ODbL", details="http://opendatacommons.org/licenses/odbl/").save() class Migration(migrations.Migration): - dependencies = [ - ('umap', '0003_add_tilelayer'), + ("umap", "0003_add_tilelayer"), ] operations = [ diff --git a/umap/migrations/0005_remove_map_tilelayer.py b/umap/migrations/0005_remove_map_tilelayer.py index e6a57cbe..f5f2c7db 100644 --- a/umap/migrations/0005_remove_map_tilelayer.py +++ b/umap/migrations/0005_remove_map_tilelayer.py @@ -4,14 +4,13 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('umap', '0004_add_licence'), + ("umap", "0004_add_licence"), ] operations = [ migrations.RemoveField( - model_name='map', - name='tilelayer', + model_name="map", + name="tilelayer", ), ] diff --git a/umap/migrations/0006_auto_20190407_0719.py b/umap/migrations/0006_auto_20190407_0719.py index baa93ca0..761c33be 100644 --- a/umap/migrations/0006_auto_20190407_0719.py +++ b/umap/migrations/0006_auto_20190407_0719.py @@ -5,15 +5,16 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('umap', '0005_remove_map_tilelayer'), + ("umap", "0005_remove_map_tilelayer"), ] operations = [ migrations.AlterField( - model_name='map', - name='settings', - field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, null=True, verbose_name='settings'), + model_name="map", + name="settings", + field=django.contrib.postgres.fields.jsonb.JSONField( + blank=True, default=dict, null=True, verbose_name="settings" + ), ), ] diff --git a/umap/migrations/0007_auto_20190416_1757.py b/umap/migrations/0007_auto_20190416_1757.py index 51cbbc13..f29da40c 100644 --- a/umap/migrations/0007_auto_20190416_1757.py +++ b/umap/migrations/0007_auto_20190416_1757.py @@ -4,15 +4,23 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('umap', '0006_auto_20190407_0719'), + ("umap", "0006_auto_20190407_0719"), ] operations = [ migrations.AlterField( - model_name='map', - name='share_status', - field=models.SmallIntegerField(choices=[(1, 'everyone (public)'), (2, 'anyone with link'), (3, 'editors only'), (9, 'blocked')], default=1, verbose_name='share status'), + model_name="map", + name="share_status", + field=models.SmallIntegerField( + choices=[ + (1, "everyone (public)"), + (2, "anyone with link"), + (3, "editors only"), + (9, "blocked"), + ], + default=1, + verbose_name="share status", + ), ), ] diff --git a/umap/migrations/0008_alter_map_settings.py b/umap/migrations/0008_alter_map_settings.py index a9e0648a..bda0a232 100644 --- a/umap/migrations/0008_alter_map_settings.py +++ b/umap/migrations/0008_alter_map_settings.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("umap", "0007_auto_20190416_1757"), ] diff --git a/umap/migrations/0009_star.py b/umap/migrations/0009_star.py index 7f2fec0e..d5136847 100644 --- a/umap/migrations/0009_star.py +++ b/umap/migrations/0009_star.py @@ -1,25 +1,44 @@ # Generated by Django 4.1.7 on 2023-05-05 18:02 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('umap', '0008_alter_map_settings'), + ("umap", "0008_alter_map_settings"), ] operations = [ migrations.CreateModel( - name='Star', + name="Star", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('at', models.DateTimeField(auto_now=True)), - ('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stars', to=settings.AUTH_USER_MODEL)), - ('map', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='umap.map')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("at", models.DateTimeField(auto_now=True)), + ( + "by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="stars", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "map", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="umap.map" + ), + ), ], ), ] diff --git a/umap/migrations/0010_alter_map_edit_status_alter_map_share_status.py b/umap/migrations/0010_alter_map_edit_status_alter_map_share_status.py index 4407c655..6c8e6a00 100644 --- a/umap/migrations/0010_alter_map_edit_status_alter_map_share_status.py +++ b/umap/migrations/0010_alter_map_edit_status_alter_map_share_status.py @@ -4,20 +4,32 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('umap', '0009_star'), + ("umap", "0009_star"), ] operations = [ migrations.AlterField( - model_name='map', - name='edit_status', - field=models.SmallIntegerField(choices=[(1, 'Everyone'), (2, 'Editors only'), (3, 'Owner only')], default=3, verbose_name='edit status'), + model_name="map", + name="edit_status", + field=models.SmallIntegerField( + choices=[(1, "Everyone"), (2, "Editors only"), (3, "Owner only")], + default=3, + verbose_name="edit status", + ), ), migrations.AlterField( - model_name='map', - name='share_status', - field=models.SmallIntegerField(choices=[(1, 'Everyone (public)'), (2, 'Anyone with link'), (3, 'Editors only'), (9, 'Blocked')], default=1, verbose_name='share status'), + model_name="map", + name="share_status", + field=models.SmallIntegerField( + choices=[ + (1, "Everyone (public)"), + (2, "Anyone with link"), + (3, "Editors only"), + (9, "Blocked"), + ], + default=1, + verbose_name="share status", + ), ), ] diff --git a/umap/migrations/0011_alter_map_edit_status_alter_map_share_status.py b/umap/migrations/0011_alter_map_edit_status_alter_map_share_status.py index ab054e4d..3522ccd3 100644 --- a/umap/migrations/0011_alter_map_edit_status_alter_map_share_status.py +++ b/umap/migrations/0011_alter_map_edit_status_alter_map_share_status.py @@ -1,24 +1,37 @@ # Generated by Django 4.2.2 on 2023-08-07 06:07 from django.db import migrations, models + import umap.models class Migration(migrations.Migration): - dependencies = [ - ('umap', '0010_alter_map_edit_status_alter_map_share_status'), + ("umap", "0010_alter_map_edit_status_alter_map_share_status"), ] operations = [ migrations.AlterField( - model_name='map', - name='edit_status', - field=models.SmallIntegerField(choices=[(1, 'Everyone'), (2, 'Editors only'), (3, 'Owner only')], default=umap.models.get_default_edit_status, verbose_name='edit status'), + model_name="map", + name="edit_status", + field=models.SmallIntegerField( + choices=[(1, "Everyone"), (2, "Editors only"), (3, "Owner only")], + default=umap.models.get_default_edit_status, + verbose_name="edit status", + ), ), migrations.AlterField( - model_name='map', - name='share_status', - field=models.SmallIntegerField(choices=[(1, 'Everyone (public)'), (2, 'Anyone with link'), (3, 'Editors only'), (9, 'Blocked')], default=umap.models.get_default_share_status, verbose_name='share status'), + model_name="map", + name="share_status", + field=models.SmallIntegerField( + choices=[ + (1, "Everyone (public)"), + (2, "Anyone with link"), + (3, "Editors only"), + (9, "Blocked"), + ], + default=umap.models.get_default_share_status, + verbose_name="share status", + ), ), ] diff --git a/umap/migrations/0014_map_created_at.py b/umap/migrations/0014_map_created_at.py index 5768776c..a804dd67 100644 --- a/umap/migrations/0014_map_created_at.py +++ b/umap/migrations/0014_map_created_at.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.2 on 2023-09-27 08:50 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/umap/models.py b/umap/models.py index cc676602..4dfcf2ef 100644 --- a/umap/models.py +++ b/umap/models.py @@ -1,14 +1,14 @@ import os import time +from django.conf import settings from django.contrib.auth.models import User from django.contrib.gis.db import models -from django.conf import settings -from django.urls import reverse -from django.utils.translation import gettext_lazy as _ +from django.core.files.base import File from django.core.signing import Signer from django.template.defaultfilters import slugify -from django.core.files.base import File +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from .managers import PublicManager @@ -380,7 +380,7 @@ class DataLayer(NamedModel): } obj["id"] = self.pk obj["permissions"] = {"edit_status": self.edit_status} - obj["editMode"] = "advanced" if self.can_edit(user, request) else 'disabled' + obj["editMode"] = "advanced" if self.can_edit(user, request) else "disabled" return obj def clone(self, map_inst=None): @@ -436,7 +436,7 @@ class DataLayer(NamedModel): root = self.storage_root() names = self.geojson.storage.listdir(root)[1] for name in names: - if name.startswith(f'{self.pk}_') and name.endswith(".gz"): + if name.startswith(f"{self.pk}_") and name.endswith(".gz"): self.geojson.storage.delete(os.path.join(root, name)) def can_edit(self, user=None, request=None): diff --git a/umap/settings/base.py b/umap/settings/base.py index 33451aaa..7437f416 100644 --- a/umap/settings/base.py +++ b/umap/settings/base.py @@ -4,7 +4,6 @@ from email.utils import parseaddr import environ from django.conf.locale import LANG_INFO -from django.template.defaultfilters import slugify env = environ.Env() @@ -244,9 +243,7 @@ UMAP_MAPS_PER_PAGE = 5 UMAP_MAPS_PER_SEARCH = 25 UMAP_MAPS_PER_PAGE_OWNER = 10 UMAP_SEARCH_CONFIGURATION = "simple" -UMAP_FEEDBACK_LINK = ( - "https://wiki.openstreetmap.org/wiki/UMap#Feedback_and_help" # noqa -) +UMAP_FEEDBACK_LINK = "https://wiki.openstreetmap.org/wiki/UMap#Feedback_and_help" USER_MAPS_URL = "user_maps" DATABASES = {"default": env.db(default="postgis://localhost:5432/umap")} UMAP_DEFAULT_SHARE_STATUS = None diff --git a/umap/templates/auth/user_form.html b/umap/templates/auth/user_form.html index c57dec7e..67933eea 100644 --- a/umap/templates/auth/user_form.html +++ b/umap/templates/auth/user_form.html @@ -3,7 +3,7 @@ {% block maincontent %}

- {% trans "My Dashboard" %} | {% trans "My Profile" %} + {% trans "My Dashboard" %} | {% trans "My Profile" %}

@@ -16,7 +16,7 @@
{% csrf_token %} {{ form }} - +
{% if backends.backends|length %} diff --git a/umap/templates/base.html b/umap/templates/base.html index 0a57fe35..97007244 100644 --- a/umap/templates/base.html +++ b/umap/templates/base.html @@ -16,9 +16,15 @@ {# See https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs #} - - - + + + + diff --git a/umap/templatetags/umap_tags.py b/umap/templatetags/umap_tags.py index 70bc244e..349e426e 100644 --- a/umap/templatetags/umap_tags.py +++ b/umap/templatetags/umap_tags.py @@ -10,53 +10,50 @@ from ..views import _urls_for_js register = template.Library() -@register.inclusion_tag('umap/css.html') +@register.inclusion_tag("umap/css.html") def umap_css(): - return { - "STATIC_URL": settings.STATIC_URL - } + return {"STATIC_URL": settings.STATIC_URL} -@register.inclusion_tag('umap/js.html') +@register.inclusion_tag("umap/js.html") def umap_js(locale=None): - return { - "STATIC_URL": settings.STATIC_URL, - "locale": locale - } + return {"STATIC_URL": settings.STATIC_URL, "locale": locale} -@register.inclusion_tag('umap/map_fragment.html') +@register.inclusion_tag("umap/map_fragment.html") def map_fragment(map_instance, **kwargs): layers = DataLayer.objects.filter(map=map_instance) datalayer_data = [c.metadata() for c in layers] map_settings = map_instance.settings if "properties" not in map_settings: - map_settings['properties'] = {} - map_settings['properties'].update({ - 'tilelayers': [TileLayer.get_default().json], - 'datalayers': datalayer_data, - 'urls': _urls_for_js(), - 'STATIC_URL': settings.STATIC_URL, - "editMode": 'disabled', - 'hash': False, - 'attributionControl': False, - 'scrollWheelZoom': False, - 'umapAttributionControl': False, - 'noControl': True, - 'umap_id': map_instance.pk, - 'onLoadPanel': "none", - 'captionBar': False, - 'default_iconUrl': "%sumap/img/marker.png" % settings.STATIC_URL, - 'slideshow': {} - }) - map_settings['properties'].update(kwargs) - prefix = kwargs.pop('prefix', None) or 'map' - page = kwargs.pop('page', None) or '' + map_settings["properties"] = {} + map_settings["properties"].update( + { + "tilelayers": [TileLayer.get_default().json], + "datalayers": datalayer_data, + "urls": _urls_for_js(), + "STATIC_URL": settings.STATIC_URL, + "editMode": "disabled", + "hash": False, + "attributionControl": False, + "scrollWheelZoom": False, + "umapAttributionControl": False, + "noControl": True, + "umap_id": map_instance.pk, + "onLoadPanel": "none", + "captionBar": False, + "default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, + "slideshow": {}, + } + ) + map_settings["properties"].update(kwargs) + prefix = kwargs.pop("prefix", None) or "map" + page = kwargs.pop("page", None) or "" unique_id = prefix + str(page) + "_" + str(map_instance.pk) return { "map_settings": json.dumps(map_settings), "map": map_instance, - "unique_id": unique_id + "unique_id": unique_id, } @@ -73,7 +70,7 @@ def tilelayer_preview(tilelayer): @register.filter def notag(s): - return s.replace('<', '<') + return s.replace("<", "<") @register.simple_tag(takes_context=True) @@ -86,5 +83,6 @@ def paginate_querystring(context, page): @register.filter def ipdb(what): import ipdb + ipdb.set_trace() - return '' + return "" diff --git a/umap/tests/base.py b/umap/tests/base.py index 42b03ab7..bbcaa043 100644 --- a/umap/tests/base.py +++ b/umap/tests/base.py @@ -1,5 +1,5 @@ -import json import copy +import json import factory from django.contrib.auth import get_user_model diff --git a/umap/tests/conftest.py b/umap/tests/conftest.py index 249f5855..7e0b034e 100644 --- a/umap/tests/conftest.py +++ b/umap/tests/conftest.py @@ -77,4 +77,3 @@ def datalayer(map): @pytest.fixture def tilelayer(): return TileLayerFactory() - diff --git a/umap/tests/integration/test_picto.py b/umap/tests/integration/test_picto.py index 5a960f20..61c92a65 100644 --- a/umap/tests/integration/test_picto.py +++ b/umap/tests/integration/test_picto.py @@ -1,8 +1,8 @@ from pathlib import Path import pytest -from playwright.sync_api import expect from django.core.files.base import ContentFile +from playwright.sync_api import expect from umap.models import Map, Pictogram diff --git a/umap/tests/integration/test_slideshow.py b/umap/tests/integration/test_slideshow.py index 1911defc..01f79e2f 100644 --- a/umap/tests/integration/test_slideshow.py +++ b/umap/tests/integration/test_slideshow.py @@ -1,8 +1,8 @@ from pathlib import Path import pytest -from playwright.sync_api import expect from django.core.files.base import ContentFile +from playwright.sync_api import expect from umap.models import Map, Pictogram diff --git a/umap/tests/settings.py b/umap/tests/settings.py index 6d2b28bb..37a72c5f 100644 --- a/umap/tests/settings.py +++ b/umap/tests/settings.py @@ -5,15 +5,17 @@ from umap.settings.base import * # pylint: disable=W0614,W0401 SECRET_KEY = "justfortests" COMPRESS_ENABLED = False FROM_EMAIL = "test@test.org" -EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" -if "TRAVIS" in os.environ: +if os.environ.get("GITHUB_ACTIONS", False) == "true": DATABASES = { "default": { "ENGINE": "django.contrib.gis.db.backends.postgis", - "NAME": "umap", - "PORT": 5433, - "USER": "travis", + "NAME": "postgres", + "USER": "postgres", + "HOST": "localhost", + "PORT": 5432, + "PASSWORD": "postgres", } } diff --git a/umap/tests/test_datalayer.py b/umap/tests/test_datalayer.py index 3464d430..c5c12f89 100644 --- a/umap/tests/test_datalayer.py +++ b/umap/tests/test_datalayer.py @@ -3,9 +3,10 @@ import os import pytest from django.core.files.base import ContentFile -from .base import DataLayerFactory, MapFactory from umap.models import DataLayer, Map +from .base import DataLayerFactory, MapFactory + pytestmark = pytest.mark.django_db @@ -63,13 +64,13 @@ def test_should_remove_old_versions_on_save(datalayer, map, settings): settings.UMAP_KEEP_VERSIONS = 3 root = datalayer.storage_root() before = len(datalayer.geojson.storage.listdir(root)[1]) - newer = f'{root}/{datalayer.pk}_1440924889.geojson' - medium = f'{root}/{datalayer.pk}_1440923687.geojson' - older = f'{root}/{datalayer.pk}_1440918637.geojson' - other = f'{root}/123456_1440918637.geojson' + newer = f"{root}/{datalayer.pk}_1440924889.geojson" + medium = f"{root}/{datalayer.pk}_1440923687.geojson" + older = f"{root}/{datalayer.pk}_1440918637.geojson" + other = f"{root}/123456_1440918637.geojson" for path in [medium, newer, older, other]: datalayer.geojson.storage.save(path, ContentFile("{}")) - datalayer.geojson.storage.save(path + '.gz', ContentFile("{}")) + datalayer.geojson.storage.save(path + ".gz", ContentFile("{}")) assert len(datalayer.geojson.storage.listdir(root)[1]) == 8 + before datalayer.save() files = datalayer.geojson.storage.listdir(root)[1] diff --git a/umap/tests/test_map.py b/umap/tests/test_map.py index d0bb52e7..41cdf740 100644 --- a/umap/tests/test_map.py +++ b/umap/tests/test_map.py @@ -50,11 +50,13 @@ def test_editors_can_edit_if_status_editors(map, user): assert map.can_edit(user) -def test_logged_in_user_should_be_allowed_for_anonymous_map_with_anonymous_edit_status(map, user, rf): # noqa +def test_logged_in_user_should_be_allowed_for_anonymous_map_with_anonymous_edit_status( + map, user, rf +): # noqa map.owner = None map.edit_status = map.ANONYMOUS map.save() - url = reverse('map_update', kwargs={'map_id': map.pk}) + url = reverse("map_update", kwargs={"map_id": map.pk}) request = rf.get(url) request.user = user assert map.can_edit(user, request) @@ -70,7 +72,7 @@ def test_anonymous_user_should_not_be_allowed_for_anonymous_map(map, user, rf): def test_clone_should_return_new_instance(map, user): clone = map.clone() assert map.pk != clone.pk - assert u"Clone of " + map.name == clone.name + assert "Clone of " + map.name == clone.name assert map.settings == clone.settings assert map.center == clone.center assert map.zoom == clone.zoom @@ -108,8 +110,7 @@ def test_clone_should_clone_datalayers_and_features_too(map, user, datalayer): def test_publicmanager_should_get_only_public_maps(map, user, licence): map.share_status = map.PUBLIC open_map = MapFactory(owner=user, licence=licence, share_status=Map.OPEN) - private_map = MapFactory(owner=user, licence=licence, - share_status=Map.PRIVATE) + private_map = MapFactory(owner=user, licence=licence, share_status=Map.PRIVATE) assert map in Map.public.all() assert open_map not in Map.public.all() assert private_map not in Map.public.all() diff --git a/umap/tests/test_map_views.py b/umap/tests/test_map_views.py index 88dda5f0..d86b925f 100644 --- a/umap/tests/test_map_views.py +++ b/umap/tests/test_map_views.py @@ -3,8 +3,8 @@ import json import pytest from django.contrib.auth import get_user_model from django.core import mail -from django.urls import reverse from django.core.signing import Signer +from django.urls import reverse from umap.models import DataLayer, Map, Star @@ -275,9 +275,7 @@ def test_owner_cannot_access_map_with_share_status_blocked(client, map): assert response.status_code == 403 -def test_non_editor_cannot_access_map_if_share_status_private( - client, map, user -): # noqa +def test_non_editor_cannot_access_map_if_share_status_private(client, map, user): # noqa url = reverse("map", args=(map.slug, map.pk)) map.share_status = map.PRIVATE map.save() @@ -355,9 +353,7 @@ def test_anonymous_update_without_cookie_fails(client, anonymap, post_data): # @pytest.mark.usefixtures("allow_anonymous") -def test_anonymous_update_with_cookie_should_work( - cookieclient, anonymap, post_data -): # noqa +def test_anonymous_update_with_cookie_should_work(cookieclient, anonymap, post_data): # noqa url = reverse("map_update", kwargs={"map_id": anonymap.pk}) # POST only mendatory fields name = "new map name" @@ -438,9 +434,7 @@ def test_clone_anonymous_map_should_not_be_possible_if_user_is_not_allowed( @pytest.mark.usefixtures("allow_anonymous") -def test_clone_map_should_be_possible_if_edit_status_is_anonymous( - client, anonymap -): # noqa +def test_clone_map_should_be_possible_if_edit_status_is_anonymous(client, anonymap): # noqa assert Map.objects.count() == 1 url = reverse("map_clone", kwargs={"map_id": anonymap.pk}) anonymap.edit_status = anonymap.ANONYMOUS diff --git a/umap/tests/test_tilelayer.py b/umap/tests/test_tilelayer.py index 11e81709..ccb2fd55 100644 --- a/umap/tests/test_tilelayer.py +++ b/umap/tests/test_tilelayer.py @@ -6,16 +6,22 @@ pytestmark = pytest.mark.django_db def test_tilelayer_json(): - tilelayer = TileLayerFactory(attribution='Attribution', maxZoom=19, - minZoom=0, name='Name', rank=1, tms=True, - url_template='http://{s}.x.fr/{z}/{x}/{y}') + tilelayer = TileLayerFactory( + attribution="Attribution", + maxZoom=19, + minZoom=0, + name="Name", + rank=1, + tms=True, + url_template="http://{s}.x.fr/{z}/{x}/{y}", + ) assert tilelayer.json == { - 'attribution': 'Attribution', - 'id': tilelayer.id, - 'maxZoom': 19, - 'minZoom': 0, - 'name': 'Name', - 'rank': 1, - 'tms': True, - 'url_template': 'http://{s}.x.fr/{z}/{x}/{y}' + "attribution": "Attribution", + "id": tilelayer.id, + "maxZoom": 19, + "minZoom": 0, + "name": "Name", + "rank": 1, + "tms": True, + "url_template": "http://{s}.x.fr/{z}/{x}/{y}", } diff --git a/umap/tests/test_views.py b/umap/tests/test_views.py index 25f080a3..1c865357 100644 --- a/umap/tests/test_views.py +++ b/umap/tests/test_views.py @@ -1,17 +1,18 @@ import json import socket -from datetime import date, timedelta +from datetime import date, datetime, timedelta import pytest from django.conf import settings from django.contrib.auth import get_user, get_user_model -from django.urls import reverse from django.test import RequestFactory +from django.urls import reverse +from django.utils.timezone import make_aware from umap import VERSION from umap.views import validate_url -from .base import UserFactory, MapFactory +from .base import MapFactory, UserFactory User = get_user_model() @@ -186,9 +187,9 @@ def test_stats_empty(client): @pytest.mark.django_db def test_stats_basic(client, map, datalayer, user2): - map.owner.last_login = date.today() + map.owner.last_login = make_aware(datetime.now()) map.owner.save() - user2.last_login = date.today() - timedelta(days=8) + user2.last_login = make_aware(datetime.now()) - timedelta(days=8) user2.save() response = client.get(reverse("stats")) assert json.loads(response.content.decode()) == { diff --git a/umap/urls.py b/umap/urls.py index 4c607219..f2905025 100644 --- a/umap/urls.py +++ b/umap/urls.py @@ -1,5 +1,4 @@ from django.conf import settings -from django.urls import include, path, re_path from django.conf.urls.i18n import i18n_patterns from django.conf.urls.static import static from django.contrib import admin @@ -7,16 +6,17 @@ from django.contrib.auth import views as auth_views from django.contrib.auth.decorators import login_required from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.urls import include, path, re_path from django.views.decorators.cache import cache_control, cache_page, never_cache from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic.base import RedirectView from . import views from .decorators import ( - jsonize_view, - login_required_if_not_anonymous_allowed, can_edit_map, can_view_map, + jsonize_view, + login_required_if_not_anonymous_allowed, ) from .utils import decorated_patterns diff --git a/umap/utils.py b/umap/utils.py index 29417150..969c1364 100644 --- a/umap/utils.py +++ b/umap/utils.py @@ -1,8 +1,7 @@ import gzip import os -from django.urls import get_resolver -from django.urls import URLPattern, URLResolver +from django.urls import URLPattern, URLResolver, get_resolver def get_uri_template(urlname, args=None, prefix=""): diff --git a/umap/views.py b/umap/views.py index 21f9dc4d..d092de1c 100644 --- a/umap/views.py +++ b/umap/views.py @@ -3,16 +3,17 @@ import mimetypes import os import re import socket -from datetime import date, timedelta +from datetime import datetime, timedelta from http.client import InvalidURL from pathlib import Path -from urllib.error import URLError -from urllib.parse import quote +from urllib.error import HTTPError, URLError +from urllib.parse import quote, urlparse +from urllib.request import Request, build_opener from django.conf import settings from django.contrib import messages -from django.contrib.auth import logout as do_logout from django.contrib.auth import get_user_model +from django.contrib.auth import logout as do_logout from django.contrib.gis.measure import D from django.contrib.postgres.search import SearchQuery, SearchVector from django.contrib.staticfiles.storage import staticfiles_storage @@ -33,6 +34,7 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.utils.encoding import smart_bytes from django.utils.http import http_date +from django.utils.timezone import make_aware from django.utils.translation import gettext as _ from django.utils.translation import to_locale from django.views.decorators.cache import cache_control @@ -45,13 +47,13 @@ from django.views.generic.list import ListView from . import VERSION from .forms import ( + DEFAULT_CENTER, DEFAULT_LATITUDE, DEFAULT_LONGITUDE, - DEFAULT_CENTER, - DataLayerForm, - DataLayerPermissionsForm, AnonymousDataLayerPermissionsForm, AnonymousMapPermissionsForm, + DataLayerForm, + DataLayerPermissionsForm, FlatErrorList, MapSettingsForm, SendLinkForm, @@ -61,16 +63,6 @@ from .forms import ( from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer from .utils import get_uri_template, gzip_file, is_ajax -try: - # python3 - from urllib.parse import urlparse - from urllib.request import Request, build_opener - from urllib.error import HTTPError -except ImportError: - from urlparse import urlparse - from urllib2 import Request, HTTPError, build_opener - - User = get_user_model() @@ -403,7 +395,7 @@ def _urls_for_js(urls=None): """ if urls is None: # prevent circular import - from .urls import urlpatterns, i18n_urls + from .urls import i18n_urls, urlpatterns urls = [ url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None) @@ -1019,7 +1011,7 @@ class PictogramJSONList(ListView): def stats(request): - last_week = date.today() - timedelta(days=7) + last_week = make_aware(datetime.now()) - timedelta(days=7) return simple_json_response( **{ "version": VERSION, diff --git a/umap/wsgi.py b/umap/wsgi.py index 89db657f..ea292782 100644 --- a/umap/wsgi.py +++ b/umap/wsgi.py @@ -15,13 +15,13 @@ framework. """ import os -os.environ.setdefault("DJANGO_SETTINGS_MODULE", - "umap.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umap.settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application + application = get_wsgi_application() # Apply WSGI middleware here.