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
umap/remote_static
tmp/*
node_modules/*
umap/static/umap/vendors
site/*
.pytest_cache/
node_modules
umap.conf
data
### Python ###
# 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]
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/*

View file

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

25
package-lock.json generated
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"
),
),
]

View file

@ -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),
),
]

View file

@ -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=('&copy; [[http://www.openstreetmap.org/copyright|'
'OpenStreetMap]] contributors, &copy; '
'[[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=(
"&copy; [[http://www.openstreetmap.org/copyright|"
"OpenStreetMap]] contributors, &copy; "
"[[https://carto.com/attributions|CARTO]]"
),
).save()
class Migration(migrations.Migration):
dependencies = [
('umap', '0002_tilelayer_tms'),
("umap", "0002_tilelayer_tms"),
]
operations = [

View file

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

View file

@ -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",
),
]

View file

@ -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"
),
),
]

View file

@ -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",
),
),
]

View file

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

View file

@ -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"
),
),
],
),
]

View file

@ -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",
),
),
]

View file

@ -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",
),
),
]

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@
{% block maincontent %}
<div class="col wide">
<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>
</div>
<div class="wrapper">
@ -16,7 +16,7 @@
<form id="user_form" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="{% trans 'Save' %}" />
<input type="submit" value="{% trans "Save" %}" />
</form>
</div>
{% if backends.backends|length %}

View file

@ -16,9 +16,15 @@
<meta name="viewport"
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 #}
<link rel="icon" href="{{ STATIC_URL }}umap/favicons/favicon.ico" 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="icon"
href="{{ STATIC_URL }}umap/favicons/favicon.ico"
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">
</head>
<body class="{% block body_class %}{% endblock body_class %}">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,15 +5,17 @@ from umap.settings.base import * # pylint: disable=W0614,W0401
SECRET_KEY = "justfortests"
COMPRESS_ENABLED = False
FROM_EMAIL = "test@test.org"
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
if "TRAVIS" in os.environ:
if os.environ.get("GITHUB_ACTIONS", False) == "true":
DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": "umap",
"PORT": 5433,
"USER": "travis",
"NAME": "postgres",
"USER": "postgres",
"HOST": "localhost",
"PORT": 5432,
"PASSWORD": "postgres",
}
}

View file

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

View file

@ -50,11 +50,13 @@ def test_editors_can_edit_if_status_editors(map, user):
assert map.can_edit(user)
def test_logged_in_user_should_be_allowed_for_anonymous_map_with_anonymous_edit_status(map, user, rf): # noqa
def test_logged_in_user_should_be_allowed_for_anonymous_map_with_anonymous_edit_status(
map, user, rf
): # noqa
map.owner = None
map.edit_status = map.ANONYMOUS
map.save()
url = reverse('map_update', kwargs={'map_id': map.pk})
url = reverse("map_update", kwargs={"map_id": map.pk})
request = rf.get(url)
request.user = user
assert map.can_edit(user, request)
@ -70,7 +72,7 @@ def test_anonymous_user_should_not_be_allowed_for_anonymous_map(map, user, rf):
def test_clone_should_return_new_instance(map, user):
clone = map.clone()
assert map.pk != clone.pk
assert u"Clone of " + map.name == clone.name
assert "Clone of " + map.name == clone.name
assert map.settings == clone.settings
assert map.center == clone.center
assert map.zoom == clone.zoom
@ -108,8 +110,7 @@ def test_clone_should_clone_datalayers_and_features_too(map, user, datalayer):
def test_publicmanager_should_get_only_public_maps(map, user, licence):
map.share_status = map.PUBLIC
open_map = MapFactory(owner=user, licence=licence, share_status=Map.OPEN)
private_map = MapFactory(owner=user, licence=licence,
share_status=Map.PRIVATE)
private_map = MapFactory(owner=user, licence=licence, share_status=Map.PRIVATE)
assert map in Map.public.all()
assert open_map not in Map.public.all()
assert private_map not in Map.public.all()

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
from django.conf import settings
from django.urls import include, path, re_path
from django.conf.urls.i18n import i18n_patterns
from django.conf.urls.static import static
from django.contrib import admin
@ -7,16 +6,17 @@ from django.contrib.auth import views as auth_views
from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.storage import staticfiles_storage
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import include, path, re_path
from django.views.decorators.cache import cache_control, cache_page, never_cache
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic.base import RedirectView
from . import views
from .decorators import (
jsonize_view,
login_required_if_not_anonymous_allowed,
can_edit_map,
can_view_map,
jsonize_view,
login_required_if_not_anonymous_allowed,
)
from .utils import decorated_patterns

View file

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

View file

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

View file

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