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 %}