Merge pull request #1413 from umap-project/almet/ci

This commit is contained in:
Alexis Métaireau 2023-11-24 22:36:36 +00:00 committed by GitHub
commit 34ee8e81de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 594 additions and 300 deletions

69
.github/workflows/test-docs.yml vendored Normal file
View file

@ -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

4
.gitignore vendored
View file

@ -4,10 +4,12 @@ umap/settings/local/*
docs/_build docs/_build
umap/remote_static umap/remote_static
tmp/* tmp/*
node_modules/*
umap/static/umap/vendors umap/static/umap/vendors
site/* site/*
.pytest_cache/ .pytest_cache/
node_modules
umap.conf
data
### Python ### ### Python ###
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View file

@ -10,13 +10,21 @@ develop: ## Install the test and dev dependencies
python3 -m pip install -e .[test,dev] python3 -m pip install -e .[test,dev]
playwright install playwright install
.PHONY: pretty-templates .PHONY: format
pretty-templates: ## Prettify template files format: ## Format the code and templates files
djlint umap/templates --reformat djlint umap/templates --reformat &&\
isort --profile black . &&\
ruff format --target-version=py38 .
.PHONY: lint-templates .PHONY: lint
lint-templates: ## Lint template files lint: ## Lint the code and template files
djlint umap/templates --lint 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 .PHONY: version
version: ## Display the current version version: ## Display the current version
@ -47,10 +55,10 @@ publish: ## Publish the Python package to Pypi
make clean make clean
test: test:
py.test -xv umap/tests/ pytest -xv umap/tests/
test-integration: test-integration:
py.test -xv umap/tests/integration/ pytest -xv umap/tests/integration/
clean: clean:
rm -f dist/* rm -f dist/*

View file

@ -3,10 +3,7 @@ import os
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":
os.environ.setdefault( os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umap.settings")
"DJANGO_SETTINGS_MODULE",
"umap.settings"
)
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line

25
package-lock.json generated
View file

@ -10,6 +10,7 @@
"license": "WTFPL", "license": "WTFPL",
"dependencies": { "dependencies": {
"@tmcw/togeojson": "^5.8.0", "@tmcw/togeojson": "^5.8.0",
"colorbrewer": "^1.5.6",
"csv2geojson": "5.1.1", "csv2geojson": "5.1.1",
"dompurify": "^3.0.3", "dompurify": "^3.0.3",
"georsstogeojson": "^0.1.0", "georsstogeojson": "^0.1.0",
@ -31,6 +32,7 @@
"leaflet.path.drag": "0.0.6", "leaflet.path.drag": "0.0.6",
"leaflet.photon": "0.8.0", "leaflet.photon": "0.8.0",
"osmtogeojson": "^3.0.0-beta.3", "osmtogeojson": "^3.0.0-beta.3",
"simple-statistics": "^7.8.3",
"togpx": "^0.5.4", "togpx": "^0.5.4",
"tokml": "0.4.0" "tokml": "0.4.0"
}, },
@ -494,6 +496,11 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "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": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" "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": { "node_modules/sinon": {
"version": "15.1.0", "version": "15.1.0",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.0.tgz", "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.0.tgz",
@ -2898,6 +2913,11 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "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": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
"integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" "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": { "sinon": {
"version": "15.1.0", "version": "15.1.0",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.0.tgz", "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.0.tgz",

View file

@ -23,16 +23,14 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only", "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.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
] ]
dependencies = [ dependencies = [
"Django>=4.1", "Django>=4.2,<5",
"django-agnocomplete==2.2.0", "django-agnocomplete==2.2.0",
"django-compressor==4.3.1", "django-compressor==4.3.1",
"django-environ==0.10.0", "django-environ==0.10.0",
@ -47,17 +45,20 @@ dependencies = [
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"hatch==1.7.0", "hatch==1.7.0",
"black==23.3.0", "ruff==0.1.6",
"djlint==1.31.0", "djlint==1.31.0",
"mkdocs==1.5.2", "mkdocs==1.5.2",
"vermin==1.5.2",
"pymdown-extensions==10.4",
"isort==5.12",
] ]
test = [ test = [
"factory-boy==3.2.1", "factory-boy==3.2.1",
"playwright==1.38.0", "playwright>=1.39,<2",
"pytest==6.2.5", "pytest==6.2.5",
"pytest-django==4.5.2", "pytest-django==4.5.2",
"pytest-playwright==0.4.2", "pytest-playwright>=0.4.3,<1",
"pytest-xdist>=3.5.0,<4",
] ]
docker = [ docker = [
"uwsgi==2.0.21", "uwsgi==2.0.21",

View file

@ -1,3 +1,3 @@
[pytest] [pytest]
DJANGO_SETTINGS_MODULE=umap.tests.settings DJANGO_SETTINGS_MODULE=umap.tests.settings
addopts = --pdbcls=IPython.terminal.debugger:Pdb --no-migrations addopts = --pdbcls=IPython.terminal.debugger:Pdb --no-migrations --numprocesses auto

View file

@ -1,10 +1,14 @@
from django.contrib.gis import admin 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): class TileLayerAdmin(admin.ModelAdmin):
list_display = ('name', 'rank', ) list_display = (
list_editable = ('rank', ) "name",
"rank",
)
list_editable = ("rank",)
class MapAdmin(admin.GISModelAdmin): class MapAdmin(admin.GISModelAdmin):

View file

@ -1,11 +1,9 @@
from agnocomplete.core import AgnocompleteModel
from agnocomplete.register import register
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from agnocomplete.register import register
from agnocomplete.core import AgnocompleteModel
@register @register
class AutocompleteUser(AgnocompleteModel): class AutocompleteUser(AgnocompleteModel):
model = get_user_model() model = get_user_model()
@ -13,5 +11,5 @@ class AutocompleteUser(AgnocompleteModel):
def item(self, current_item): def item(self, current_item):
data = super().item(current_item) data = super().item(current_item)
data['url'] = current_item.get_url() data["url"] = current_item.get_url()
return data return data

View file

@ -5,12 +5,9 @@ from django.core import management
def main(): def main():
os.environ.setdefault( os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umap.settings")
"DJANGO_SETTINGS_MODULE",
"umap.settings"
)
management.execute_from_command_line() management.execute_from_command_line()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View file

@ -1,13 +1,12 @@
from functools import wraps 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.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 .models import Map
from .views import simple_json_response
LOGIN_URL = getattr(settings, "LOGIN_URL", "login") LOGIN_URL = getattr(settings, "LOGIN_URL", "login")
LOGIN_URL = reverse_lazy(LOGIN_URL) if not LOGIN_URL.startswith("/") else LOGIN_URL LOGIN_URL = reverse_lazy(LOGIN_URL) if not LOGIN_URL.startswith("/") else LOGIN_URL

View file

@ -1,12 +1,12 @@
from django import forms 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.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.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 = ( DEFAULT_LATITUDE = (
settings.LEAFLET_LATITUDE if hasattr(settings, "LEAFLET_LATITUDE") else 51 settings.LEAFLET_LATITUDE if hasattr(settings, "LEAFLET_LATITUDE") else 51

View file

@ -1,24 +1,23 @@
from pathlib import Path from pathlib import Path
from django.core.management.base import BaseCommand
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import to_locale 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): class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
self.verbosity = options['verbosity'] self.verbosity = options["verbosity"]
for code, name in settings.LANGUAGES: for code, name in settings.LANGUAGES:
code = to_locale(code) code = to_locale(code)
if self.verbosity > 0: if self.verbosity > 0:
print("Processing", name) print("Processing", name)
path = ROOT / '{code}.json'.format(code=code) path = ROOT / "{code}.json".format(code=code)
if not path.exists(): if not path.exists():
print(path, 'does not exist.', 'Skipping') print(path, "does not exist.", "Skipping")
else: else:
with path.open(encoding="utf-8") as f: with path.open(encoding="utf-8") as f:
if self.verbosity > 1: if self.verbosity > 1:
@ -26,12 +25,11 @@ class Command(BaseCommand):
self.render(code, f.read()) self.render(code, f.read())
def render(self, code, json): 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: with path.open("w", encoding="utf-8") as f:
content = render_to_string('umap/locale.js', { content = render_to_string(
"locale": json, "umap/locale.js", {"locale": json, "locale_code": code}
"locale_code": code )
})
if self.verbosity > 1: if self.verbosity > 1:
print("Exporting to", path) print("Exporting to", path)
f.write(content) f.write(content)

View file

@ -59,7 +59,7 @@ class Command(BaseCommand):
else: else:
picto = Pictogram() picto = Pictogram()
picto.name = name picto.name = name
if (path.name != self.path.name): # Subfolders only if path.name != self.path.name: # Subfolders only
picto.category = path.name picto.category = path.name
picto.attribution = self.attribution picto.attribution = self.attribution
with (path / filename).open("rb") as f: with (path / filename).open("rb") as f:

View file

@ -2,7 +2,9 @@ from django.db.models import Manager
class PublicManager(Manager): class PublicManager(Manager):
def get_queryset(self): def get_queryset(self):
return super(PublicManager, self).get_queryset().filter( return (
share_status=self.model.PUBLIC) super(PublicManager, self)
.get_queryset()
.filter(share_status=self.model.PUBLIC)
)

View file

@ -5,13 +5,12 @@ from django.utils.translation import gettext as _
def readonly_middleware(get_response): def readonly_middleware(get_response):
if not settings.UMAP_READONLY: if not settings.UMAP_READONLY:
raise MiddlewareNotUsed raise MiddlewareNotUsed
def middleware(request): def middleware(request):
if request.method not in ['GET', 'OPTIONS']: if request.method not in ["GET", "OPTIONS"]:
return HttpResponseForbidden(_('Site is readonly for maintenance')) return HttpResponseForbidden(_("Site is readonly for maintenance"))
return get_response(request) return get_response(request)

View file

@ -1,15 +1,15 @@
# Generated by Django 2.0.5 on 2018-05-19 09:27 # Generated by Django 2.0.5 on 2018-05-19 09:27
from django.conf import settings
import django.contrib.gis.db.models.fields import django.contrib.gis.db.models.fields
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import umap.fields import umap.fields
import umap.models import umap.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
@ -18,90 +18,238 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='DataLayer', name="DataLayer",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200, verbose_name='name')), "id",
('description', models.TextField(blank=True, null=True, verbose_name='description')), models.AutoField(
('geojson', models.FileField(blank=True, null=True, upload_to=umap.models.upload_to)), auto_created=True,
('display_on_load', models.BooleanField(default=False, help_text='Display this layer on load.', verbose_name='display on load')), primary_key=True,
('rank', models.SmallIntegerField(default=0)), 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={ options={
'ordering': ('rank',), "ordering": ("rank",),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Licence', name="Licence",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200, verbose_name='name')), "id",
('details', models.URLField(help_text='Link to a page where the licence is detailed.', verbose_name='details')), 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={ options={
'ordering': ('name',), "ordering": ("name",),
'abstract': False, "abstract": False,
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Map', name="Map",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200, verbose_name='name')), "id",
('slug', models.SlugField()), models.AutoField(
('description', models.TextField(blank=True, null=True, verbose_name='description')), auto_created=True,
('center', django.contrib.gis.db.models.fields.PointField(geography=True, srid=4326, verbose_name='center')), primary_key=True,
('zoom', models.IntegerField(default=7, verbose_name='zoom')), serialize=False,
('locate', models.BooleanField(default=False, help_text='Locate user on load?', verbose_name='locate')), verbose_name="ID",
('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')), ("name", models.CharField(max_length=200, verbose_name="name")),
('settings', umap.fields.DictField(blank=True, null=True, verbose_name='settings')), ("slug", models.SlugField()),
('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')), "description",
('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')), 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={ options={
'ordering': ('name',), "ordering": ("name",),
'abstract': False, "abstract": False,
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Pictogram', name="Pictogram",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200, verbose_name='name')), "id",
('attribution', models.CharField(max_length=300)), models.AutoField(
('pictogram', models.ImageField(upload_to='pictogram')), 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={ options={
'ordering': ('name',), "ordering": ("name",),
'abstract': False, "abstract": False,
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='TileLayer', name="TileLayer",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(max_length=200, verbose_name='name')), "id",
('url_template', models.CharField(help_text='URL template using OSM tile format', max_length=200)), models.AutoField(
('minZoom', models.IntegerField(default=0)), auto_created=True,
('maxZoom', models.IntegerField(default=18)), primary_key=True,
('attribution', models.CharField(max_length=300)), serialize=False,
('rank', models.SmallIntegerField(blank=True, help_text='Order of the tilelayers in the edit box', null=True)), 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={ options={
'ordering': ('rank', 'name'), "ordering": ("rank", "name"),
}, },
), ),
migrations.AddField( migrations.AddField(
model_name='map', model_name="map",
name='tilelayer', 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'), 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( migrations.AddField(
model_name='datalayer', model_name="datalayer",
name='map', name="map",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='umap.Map'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="umap.Map"
),
), ),
] ]

View file

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0001_initial'), ("umap", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='tilelayer', model_name="tilelayer",
name='tms', name="tms",
field=models.BooleanField(default=False), field=models.BooleanField(default=False),
), ),
] ]

View file

@ -5,22 +5,26 @@ from django.db import migrations
def add_tilelayer(apps, *args): def add_tilelayer(apps, *args):
TileLayer = apps.get_model('umap', 'TileLayer') TileLayer = apps.get_model("umap", "TileLayer")
if TileLayer.objects.count(): if TileLayer.objects.count():
return return
TileLayer( TileLayer(
name='Positron', name="Positron",
url_template=('https://cartodb-basemaps-{s}.global.ssl.fastly.net/' url_template=(
'light_all/{z}/{x}/{y}.png'), "https://cartodb-basemaps-{s}.global.ssl.fastly.net/"
attribution=('&copy; [[http://www.openstreetmap.org/copyright|' "light_all/{z}/{x}/{y}.png"
'OpenStreetMap]] contributors, &copy; ' ),
'[[https://carto.com/attributions|CARTO]]')).save() attribution=(
"&copy; [[http://www.openstreetmap.org/copyright|"
"OpenStreetMap]] contributors, &copy; "
"[[https://carto.com/attributions|CARTO]]"
),
).save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0002_tilelayer_tms'), ("umap", "0002_tilelayer_tms"),
] ]
operations = [ operations = [

View file

@ -5,18 +5,15 @@ from django.db import migrations
def add_licence(apps, *args): def add_licence(apps, *args):
Licence = apps.get_model('umap', 'Licence') Licence = apps.get_model("umap", "Licence")
if Licence.objects.count(): if Licence.objects.count():
return return
Licence( Licence(name="ODbL", details="http://opendatacommons.org/licenses/odbl/").save()
name='ODbL',
details='http://opendatacommons.org/licenses/odbl/').save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0003_add_tilelayer'), ("umap", "0003_add_tilelayer"),
] ]
operations = [ operations = [

View file

@ -4,14 +4,13 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0004_add_licence'), ("umap", "0004_add_licence"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='map', model_name="map",
name='tilelayer', name="tilelayer",
), ),
] ]

View file

@ -5,15 +5,16 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0005_remove_map_tilelayer'), ("umap", "0005_remove_map_tilelayer"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='map', model_name="map",
name='settings', name="settings",
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict, null=True, verbose_name='settings'), field=django.contrib.postgres.fields.jsonb.JSONField(
blank=True, default=dict, null=True, verbose_name="settings"
),
), ),
] ]

View file

@ -4,15 +4,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0006_auto_20190407_0719'), ("umap", "0006_auto_20190407_0719"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='map', model_name="map",
name='share_status', 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'), field=models.SmallIntegerField(
choices=[
(1, "everyone (public)"),
(2, "anyone with link"),
(3, "editors only"),
(9, "blocked"),
],
default=1,
verbose_name="share status",
),
), ),
] ]

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("umap", "0007_auto_20190416_1757"), ("umap", "0007_auto_20190416_1757"),
] ]

View file

@ -1,25 +1,44 @@
# Generated by Django 4.1.7 on 2023-05-05 18:02 # Generated by Django 4.1.7 on 2023-05-05 18:02
import django.db.models.deletion
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('umap', '0008_alter_map_settings'), ("umap", "0008_alter_map_settings"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Star', name="Star",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('at', models.DateTimeField(auto_now=True)), "id",
('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stars', to=settings.AUTH_USER_MODEL)), models.AutoField(
('map', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='umap.map')), 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"
),
),
], ],
), ),
] ]

View file

@ -4,20 +4,32 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0009_star'), ("umap", "0009_star"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='map', model_name="map",
name='edit_status', name="edit_status",
field=models.SmallIntegerField(choices=[(1, 'Everyone'), (2, 'Editors only'), (3, 'Owner only')], default=3, verbose_name='edit status'), field=models.SmallIntegerField(
choices=[(1, "Everyone"), (2, "Editors only"), (3, "Owner only")],
default=3,
verbose_name="edit status",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='map', model_name="map",
name='share_status', 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'), field=models.SmallIntegerField(
choices=[
(1, "Everyone (public)"),
(2, "Anyone with link"),
(3, "Editors only"),
(9, "Blocked"),
],
default=1,
verbose_name="share status",
),
), ),
] ]

View file

@ -1,24 +1,37 @@
# Generated by Django 4.2.2 on 2023-08-07 06:07 # Generated by Django 4.2.2 on 2023-08-07 06:07
from django.db import migrations, models from django.db import migrations, models
import umap.models import umap.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('umap', '0010_alter_map_edit_status_alter_map_share_status'), ("umap", "0010_alter_map_edit_status_alter_map_share_status"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='map', model_name="map",
name='edit_status', 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'), 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( migrations.AlterField(
model_name='map', model_name="map",
name='share_status', 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'), 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",
),
), ),
] ]

View file

@ -1,7 +1,7 @@
# Generated by Django 4.2.2 on 2023-09-27 08:50 # Generated by Django 4.2.2 on 2023-09-27 08:50
from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View file

@ -1,14 +1,14 @@
import os import os
import time import time
from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.gis.db import models from django.contrib.gis.db import models
from django.conf import settings from django.core.files.base import File
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.core.signing import Signer from django.core.signing import Signer
from django.template.defaultfilters import slugify 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 from .managers import PublicManager
@ -380,7 +380,7 @@ class DataLayer(NamedModel):
} }
obj["id"] = self.pk obj["id"] = self.pk
obj["permissions"] = {"edit_status": self.edit_status} 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 return obj
def clone(self, map_inst=None): def clone(self, map_inst=None):
@ -436,7 +436,7 @@ class DataLayer(NamedModel):
root = self.storage_root() root = self.storage_root()
names = self.geojson.storage.listdir(root)[1] names = self.geojson.storage.listdir(root)[1]
for name in names: 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)) self.geojson.storage.delete(os.path.join(root, name))
def can_edit(self, user=None, request=None): def can_edit(self, user=None, request=None):

View file

@ -4,7 +4,6 @@ from email.utils import parseaddr
import environ import environ
from django.conf.locale import LANG_INFO from django.conf.locale import LANG_INFO
from django.template.defaultfilters import slugify
env = environ.Env() env = environ.Env()
@ -244,9 +243,7 @@ UMAP_MAPS_PER_PAGE = 5
UMAP_MAPS_PER_SEARCH = 25 UMAP_MAPS_PER_SEARCH = 25
UMAP_MAPS_PER_PAGE_OWNER = 10 UMAP_MAPS_PER_PAGE_OWNER = 10
UMAP_SEARCH_CONFIGURATION = "simple" UMAP_SEARCH_CONFIGURATION = "simple"
UMAP_FEEDBACK_LINK = ( UMAP_FEEDBACK_LINK = "https://wiki.openstreetmap.org/wiki/UMap#Feedback_and_help"
"https://wiki.openstreetmap.org/wiki/UMap#Feedback_and_help" # noqa
)
USER_MAPS_URL = "user_maps" USER_MAPS_URL = "user_maps"
DATABASES = {"default": env.db(default="postgis://localhost:5432/umap")} DATABASES = {"default": env.db(default="postgis://localhost:5432/umap")}
UMAP_DEFAULT_SHARE_STATUS = None UMAP_DEFAULT_SHARE_STATUS = None

View file

@ -3,7 +3,7 @@
{% block maincontent %} {% block maincontent %}
<div class="col wide"> <div class="col wide">
<h2 class="section tabs"> <h2 class="section tabs">
<a href="{% url 'user_dashboard' %}">{% trans "My Dashboard" %}</a> | {% trans "My Profile" %} <a href="{% url "user_dashboard" %}">{% trans "My Dashboard" %}</a> | {% trans "My Profile" %}
</h2> </h2>
</div> </div>
<div class="wrapper"> <div class="wrapper">
@ -16,7 +16,7 @@
<form id="user_form" method="post"> <form id="user_form" method="post">
{% csrf_token %} {% csrf_token %}
{{ form }} {{ form }}
<input type="submit" value="{% trans 'Save' %}" /> <input type="submit" value="{% trans "Save" %}" />
</form> </form>
</div> </div>
{% if backends.backends|length %} {% if backends.backends|length %}

View file

@ -16,9 +16,15 @@
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
{# See https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs #} {# See https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs #}
<link rel="icon" href="{{ STATIC_URL }}umap/favicons/favicon.ico" sizes="32x32"> <link rel="icon"
<link rel="icon" href="{{ STATIC_URL }}umap/favicons/icon.svg" type="image/svg+xml"> href="{{ STATIC_URL }}umap/favicons/favicon.ico"
<link rel="apple-touch-icon" href="{{ STATIC_URL }}umap/favicons/apple-touch-icon.png"><!-- 180×180 --> sizes="32x32">
<link rel="icon"
href="{{ STATIC_URL }}umap/favicons/icon.svg"
type="image/svg+xml">
<link rel="apple-touch-icon"
href="{{ STATIC_URL }}umap/favicons/apple-touch-icon.png">
<!-- 180×180 -->
<link rel="manifest" href="/manifest.webmanifest"> <link rel="manifest" href="/manifest.webmanifest">
</head> </head>
<body class="{% block body_class %}{% endblock body_class %}"> <body class="{% block body_class %}{% endblock body_class %}">

View file

@ -10,53 +10,50 @@ from ..views import _urls_for_js
register = template.Library() register = template.Library()
@register.inclusion_tag('umap/css.html') @register.inclusion_tag("umap/css.html")
def umap_css(): def umap_css():
return { return {"STATIC_URL": settings.STATIC_URL}
"STATIC_URL": settings.STATIC_URL
}
@register.inclusion_tag('umap/js.html') @register.inclusion_tag("umap/js.html")
def umap_js(locale=None): def umap_js(locale=None):
return { return {"STATIC_URL": settings.STATIC_URL, "locale": locale}
"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): def map_fragment(map_instance, **kwargs):
layers = DataLayer.objects.filter(map=map_instance) layers = DataLayer.objects.filter(map=map_instance)
datalayer_data = [c.metadata() for c in layers] datalayer_data = [c.metadata() for c in layers]
map_settings = map_instance.settings map_settings = map_instance.settings
if "properties" not in map_settings: if "properties" not in map_settings:
map_settings['properties'] = {} map_settings["properties"] = {}
map_settings['properties'].update({ map_settings["properties"].update(
'tilelayers': [TileLayer.get_default().json], {
'datalayers': datalayer_data, "tilelayers": [TileLayer.get_default().json],
'urls': _urls_for_js(), "datalayers": datalayer_data,
'STATIC_URL': settings.STATIC_URL, "urls": _urls_for_js(),
"editMode": 'disabled', "STATIC_URL": settings.STATIC_URL,
'hash': False, "editMode": "disabled",
'attributionControl': False, "hash": False,
'scrollWheelZoom': False, "attributionControl": False,
'umapAttributionControl': False, "scrollWheelZoom": False,
'noControl': True, "umapAttributionControl": False,
'umap_id': map_instance.pk, "noControl": True,
'onLoadPanel': "none", "umap_id": map_instance.pk,
'captionBar': False, "onLoadPanel": "none",
'default_iconUrl': "%sumap/img/marker.png" % settings.STATIC_URL, "captionBar": False,
'slideshow': {} "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"].update(kwargs)
prefix = kwargs.pop("prefix", None) or "map"
page = kwargs.pop("page", None) or ""
unique_id = prefix + str(page) + "_" + str(map_instance.pk) unique_id = prefix + str(page) + "_" + str(map_instance.pk)
return { return {
"map_settings": json.dumps(map_settings), "map_settings": json.dumps(map_settings),
"map": map_instance, "map": map_instance,
"unique_id": unique_id "unique_id": unique_id,
} }
@ -73,7 +70,7 @@ def tilelayer_preview(tilelayer):
@register.filter @register.filter
def notag(s): def notag(s):
return s.replace('<', '&lt;') return s.replace("<", "&lt;")
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
@ -86,5 +83,6 @@ def paginate_querystring(context, page):
@register.filter @register.filter
def ipdb(what): def ipdb(what):
import ipdb import ipdb
ipdb.set_trace() ipdb.set_trace()
return '' return ""

View file

@ -1,5 +1,5 @@
import json
import copy import copy
import json
import factory import factory
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model

View file

@ -77,4 +77,3 @@ def datalayer(map):
@pytest.fixture @pytest.fixture
def tilelayer(): def tilelayer():
return TileLayerFactory() return TileLayerFactory()

View file

@ -1,8 +1,8 @@
from pathlib import Path from pathlib import Path
import pytest import pytest
from playwright.sync_api import expect
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from playwright.sync_api import expect
from umap.models import Map, Pictogram from umap.models import Map, Pictogram

View file

@ -1,8 +1,8 @@
from pathlib import Path from pathlib import Path
import pytest import pytest
from playwright.sync_api import expect
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from playwright.sync_api import expect
from umap.models import Map, Pictogram from umap.models import Map, Pictogram

View file

@ -5,15 +5,17 @@ from umap.settings.base import * # pylint: disable=W0614,W0401
SECRET_KEY = "justfortests" SECRET_KEY = "justfortests"
COMPRESS_ENABLED = False COMPRESS_ENABLED = False
FROM_EMAIL = "test@test.org" 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 = { DATABASES = {
"default": { "default": {
"ENGINE": "django.contrib.gis.db.backends.postgis", "ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": "umap", "NAME": "postgres",
"PORT": 5433, "USER": "postgres",
"USER": "travis", "HOST": "localhost",
"PORT": 5432,
"PASSWORD": "postgres",
} }
} }

View file

@ -3,9 +3,10 @@ import os
import pytest import pytest
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from .base import DataLayerFactory, MapFactory
from umap.models import DataLayer, Map from umap.models import DataLayer, Map
from .base import DataLayerFactory, MapFactory
pytestmark = pytest.mark.django_db 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 settings.UMAP_KEEP_VERSIONS = 3
root = datalayer.storage_root() root = datalayer.storage_root()
before = len(datalayer.geojson.storage.listdir(root)[1]) before = len(datalayer.geojson.storage.listdir(root)[1])
newer = f'{root}/{datalayer.pk}_1440924889.geojson' newer = f"{root}/{datalayer.pk}_1440924889.geojson"
medium = f'{root}/{datalayer.pk}_1440923687.geojson' medium = f"{root}/{datalayer.pk}_1440923687.geojson"
older = f'{root}/{datalayer.pk}_1440918637.geojson' older = f"{root}/{datalayer.pk}_1440918637.geojson"
other = f'{root}/123456_1440918637.geojson' other = f"{root}/123456_1440918637.geojson"
for path in [medium, newer, older, other]: for path in [medium, newer, older, other]:
datalayer.geojson.storage.save(path, ContentFile("{}")) 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 assert len(datalayer.geojson.storage.listdir(root)[1]) == 8 + before
datalayer.save() datalayer.save()
files = datalayer.geojson.storage.listdir(root)[1] files = datalayer.geojson.storage.listdir(root)[1]

View file

@ -50,11 +50,13 @@ def test_editors_can_edit_if_status_editors(map, user):
assert map.can_edit(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.owner = None
map.edit_status = map.ANONYMOUS map.edit_status = map.ANONYMOUS
map.save() 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 = rf.get(url)
request.user = user request.user = user
assert map.can_edit(user, request) 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): def test_clone_should_return_new_instance(map, user):
clone = map.clone() clone = map.clone()
assert map.pk != clone.pk 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.settings == clone.settings
assert map.center == clone.center assert map.center == clone.center
assert map.zoom == clone.zoom 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): def test_publicmanager_should_get_only_public_maps(map, user, licence):
map.share_status = map.PUBLIC map.share_status = map.PUBLIC
open_map = MapFactory(owner=user, licence=licence, share_status=Map.OPEN) open_map = MapFactory(owner=user, licence=licence, share_status=Map.OPEN)
private_map = MapFactory(owner=user, licence=licence, private_map = MapFactory(owner=user, licence=licence, share_status=Map.PRIVATE)
share_status=Map.PRIVATE)
assert map in Map.public.all() assert map in Map.public.all()
assert open_map not in Map.public.all() assert open_map not in Map.public.all()
assert private_map not in Map.public.all() assert private_map not in Map.public.all()

View file

@ -3,8 +3,8 @@ import json
import pytest import pytest
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core import mail from django.core import mail
from django.urls import reverse
from django.core.signing import Signer from django.core.signing import Signer
from django.urls import reverse
from umap.models import DataLayer, Map, Star 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 assert response.status_code == 403
def test_non_editor_cannot_access_map_if_share_status_private( def test_non_editor_cannot_access_map_if_share_status_private(client, map, user): # noqa
client, map, user
): # noqa
url = reverse("map", args=(map.slug, map.pk)) url = reverse("map", args=(map.slug, map.pk))
map.share_status = map.PRIVATE map.share_status = map.PRIVATE
map.save() map.save()
@ -355,9 +353,7 @@ def test_anonymous_update_without_cookie_fails(client, anonymap, post_data): #
@pytest.mark.usefixtures("allow_anonymous") @pytest.mark.usefixtures("allow_anonymous")
def test_anonymous_update_with_cookie_should_work( def test_anonymous_update_with_cookie_should_work(cookieclient, anonymap, post_data): # noqa
cookieclient, anonymap, post_data
): # noqa
url = reverse("map_update", kwargs={"map_id": anonymap.pk}) url = reverse("map_update", kwargs={"map_id": anonymap.pk})
# POST only mendatory fields # POST only mendatory fields
name = "new map name" 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") @pytest.mark.usefixtures("allow_anonymous")
def test_clone_map_should_be_possible_if_edit_status_is_anonymous( def test_clone_map_should_be_possible_if_edit_status_is_anonymous(client, anonymap): # noqa
client, anonymap
): # noqa
assert Map.objects.count() == 1 assert Map.objects.count() == 1
url = reverse("map_clone", kwargs={"map_id": anonymap.pk}) url = reverse("map_clone", kwargs={"map_id": anonymap.pk})
anonymap.edit_status = anonymap.ANONYMOUS anonymap.edit_status = anonymap.ANONYMOUS

View file

@ -6,16 +6,22 @@ pytestmark = pytest.mark.django_db
def test_tilelayer_json(): def test_tilelayer_json():
tilelayer = TileLayerFactory(attribution='Attribution', maxZoom=19, tilelayer = TileLayerFactory(
minZoom=0, name='Name', rank=1, tms=True, attribution="Attribution",
url_template='http://{s}.x.fr/{z}/{x}/{y}') maxZoom=19,
minZoom=0,
name="Name",
rank=1,
tms=True,
url_template="http://{s}.x.fr/{z}/{x}/{y}",
)
assert tilelayer.json == { assert tilelayer.json == {
'attribution': 'Attribution', "attribution": "Attribution",
'id': tilelayer.id, "id": tilelayer.id,
'maxZoom': 19, "maxZoom": 19,
'minZoom': 0, "minZoom": 0,
'name': 'Name', "name": "Name",
'rank': 1, "rank": 1,
'tms': True, "tms": True,
'url_template': 'http://{s}.x.fr/{z}/{x}/{y}' "url_template": "http://{s}.x.fr/{z}/{x}/{y}",
} }

View file

@ -1,17 +1,18 @@
import json import json
import socket import socket
from datetime import date, timedelta from datetime import date, datetime, timedelta
import pytest import pytest
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user, get_user_model from django.contrib.auth import get_user, get_user_model
from django.urls import reverse
from django.test import RequestFactory from django.test import RequestFactory
from django.urls import reverse
from django.utils.timezone import make_aware
from umap import VERSION from umap import VERSION
from umap.views import validate_url from umap.views import validate_url
from .base import UserFactory, MapFactory from .base import MapFactory, UserFactory
User = get_user_model() User = get_user_model()
@ -186,9 +187,9 @@ def test_stats_empty(client):
@pytest.mark.django_db @pytest.mark.django_db
def test_stats_basic(client, map, datalayer, user2): 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() map.owner.save()
user2.last_login = date.today() - timedelta(days=8) user2.last_login = make_aware(datetime.now()) - timedelta(days=8)
user2.save() user2.save()
response = client.get(reverse("stats")) response = client.get(reverse("stats"))
assert json.loads(response.content.decode()) == { assert json.loads(response.content.decode()) == {

View file

@ -1,5 +1,4 @@
from django.conf import settings 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.i18n import i18n_patterns
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin 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.auth.decorators import login_required
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles.urls import staticfiles_urlpatterns 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.cache import cache_control, cache_page, never_cache
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic.base import RedirectView from django.views.generic.base import RedirectView
from . import views from . import views
from .decorators import ( from .decorators import (
jsonize_view,
login_required_if_not_anonymous_allowed,
can_edit_map, can_edit_map,
can_view_map, can_view_map,
jsonize_view,
login_required_if_not_anonymous_allowed,
) )
from .utils import decorated_patterns from .utils import decorated_patterns

View file

@ -1,8 +1,7 @@
import gzip import gzip
import os import os
from django.urls import get_resolver from django.urls import URLPattern, URLResolver, get_resolver
from django.urls import URLPattern, URLResolver
def get_uri_template(urlname, args=None, prefix=""): def get_uri_template(urlname, args=None, prefix=""):

View file

@ -3,16 +3,17 @@ import mimetypes
import os import os
import re import re
import socket import socket
from datetime import date, timedelta from datetime import datetime, timedelta
from http.client import InvalidURL from http.client import InvalidURL
from pathlib import Path from pathlib import Path
from urllib.error import URLError from urllib.error import HTTPError, URLError
from urllib.parse import quote from urllib.parse import quote, urlparse
from urllib.request import Request, build_opener
from django.conf import settings from django.conf import settings
from django.contrib import messages 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 get_user_model
from django.contrib.auth import logout as do_logout
from django.contrib.gis.measure import D from django.contrib.gis.measure import D
from django.contrib.postgres.search import SearchQuery, SearchVector from django.contrib.postgres.search import SearchQuery, SearchVector
from django.contrib.staticfiles.storage import staticfiles_storage 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.urls import reverse, reverse_lazy
from django.utils.encoding import smart_bytes from django.utils.encoding import smart_bytes
from django.utils.http import http_date 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 gettext as _
from django.utils.translation import to_locale from django.utils.translation import to_locale
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
@ -45,13 +47,13 @@ from django.views.generic.list import ListView
from . import VERSION from . import VERSION
from .forms import ( from .forms import (
DEFAULT_CENTER,
DEFAULT_LATITUDE, DEFAULT_LATITUDE,
DEFAULT_LONGITUDE, DEFAULT_LONGITUDE,
DEFAULT_CENTER,
DataLayerForm,
DataLayerPermissionsForm,
AnonymousDataLayerPermissionsForm, AnonymousDataLayerPermissionsForm,
AnonymousMapPermissionsForm, AnonymousMapPermissionsForm,
DataLayerForm,
DataLayerPermissionsForm,
FlatErrorList, FlatErrorList,
MapSettingsForm, MapSettingsForm,
SendLinkForm, SendLinkForm,
@ -61,16 +63,6 @@ from .forms import (
from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer
from .utils import get_uri_template, gzip_file, is_ajax 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() User = get_user_model()
@ -403,7 +395,7 @@ def _urls_for_js(urls=None):
""" """
if urls is None: if urls is None:
# prevent circular import # prevent circular import
from .urls import urlpatterns, i18n_urls from .urls import i18n_urls, urlpatterns
urls = [ urls = [
url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None) url.name for url in urlpatterns + i18n_urls if getattr(url, "name", None)
@ -1019,7 +1011,7 @@ class PictogramJSONList(ListView):
def stats(request): def stats(request):
last_week = date.today() - timedelta(days=7) last_week = make_aware(datetime.now()) - timedelta(days=7)
return simple_json_response( return simple_json_response(
**{ **{
"version": VERSION, "version": VERSION,

View file

@ -15,13 +15,13 @@ framework.
""" """
import os import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.environ.setdefault("DJANGO_SETTINGS_MODULE", "umap.settings")
"umap.settings")
# This application object is used by any WSGI server configured to use this # This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION # file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here. # setting points here.
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() application = get_wsgi_application()
# Apply WSGI middleware here. # Apply WSGI middleware here.