Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Création d'une arborescence pour la FAQ #172

Closed
wants to merge 54 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
7b2c9fe
Rename variable to make it's type less confusing
thibault Jun 16, 2023
03c3fcc
Generate a map preview (WIP)
thibault Jun 16, 2023
4f8dfe2
Use gis admin class for maps
thibault Jun 21, 2023
5f4bae1
Use native pgis method for union-izing map polygons
thibault Jun 21, 2023
ec0d2df
Use constant for better self-documentation
thibault Jun 21, 2023
21d7903
Better map import transaction management
thibault Jun 21, 2023
bd2c93b
Create action to only generate map preview
thibault Jun 21, 2023
40e479a
Improve method to simplify the generated polygon
thibault Jun 21, 2023
71f1a1d
Make sure abstract classes cannot be instanciated
thibault Jun 22, 2023
3b67113
Make sure criteria instances access the perimeter
thibault Jun 22, 2023
2d16d05
Annotate perimeters with full map when it exists
thibault Jun 22, 2023
e69c5b2
Display a map for the sage regulation
thibault Jun 22, 2023
e85b0f4
run js file through djlint
thibault Jun 22, 2023
f20e18f
Fix a tiny typo
thibault Jun 22, 2023
2824d83
Update translations
thibault Jun 22, 2023
700fd02
Add new field
thibault Jun 22, 2023
c560024
Split the faq page into three pages
thibault Mar 3, 2023
2872bcc
Create index for faq pages
thibault Mar 3, 2023
9e56049
Update all faq links
thibault Mar 3, 2023
2ffcfbe
Add faq breadcrumbs
thibault Mar 3, 2023
db07e5f
Fix pep8 errors
thibault Mar 3, 2023
ee9bfab
Update typographic vertical grid for all site
thibault Mar 3, 2023
15c4541
Fix the img ratio workaround
thibault Mar 3, 2023
5065f46
Update cerfa image and reference
thibault Mar 3, 2023
11d8991
Use side menu for the faq
thibault Apr 24, 2023
b4f9ebb
Display current faq page as active
thibault Apr 24, 2023
11e231c
Create new model for site news
thibault Apr 25, 2023
762df50
Create news page and rss feed
thibault Apr 25, 2023
0817504
Fix pep8 errors
thibault Apr 25, 2023
91c29f2
Add title to news items
thibault May 22, 2023
24aaf47
Fix sonarcloud warnings
thibault May 22, 2023
aaf0993
Make the creation date updatable
thibault May 22, 2023
f974884
Update loi_sur_leau.html
nenj Jun 19, 2023
c0122c4
Update natura_2000.html
nenj Jun 19, 2023
299bdae
Update natura_2000.html
nenj Jun 19, 2023
5654d1c
Update natura_2000.html
nenj Jun 19, 2023
2d65006
Update loi_sur_leau.html
nenj Jun 19, 2023
4679487
Update natura_2000.html
nenj Jun 19, 2023
16f6418
Update natura_2000.html
nenj Jun 19, 2023
56133f3
Update loi_sur_leau.html
nenj Jun 19, 2023
f841f98
Update loi_sur_leau.html
nenj Jun 19, 2023
453f396
Update natura_2000.html
nenj Jun 19, 2023
bd3bc5c
Update natura_2000.html
nenj Jun 19, 2023
6df3685
Update natura_2000.html
nenj Jun 19, 2023
30971d0
Update loi_sur_leau.html
nenj Jun 19, 2023
a0aeaef
Update natura_2000.html
nenj Jun 19, 2023
062fbee
Update natura_2000.html
nenj Jun 19, 2023
9cb8224
Update loi_sur_leau.html
nenj Jun 19, 2023
5fda063
Run templates through djlint
thibault Jun 22, 2023
1229f10
Fix a bug of html problems and reindent all
thibault Jun 22, 2023
1fa0d99
Run djlint on faq templates
thibault Jun 23, 2023
8e9d589
Reduce faq content column width
thibault Jun 23, 2023
b5ebcaa
Add missing nbsp
thibault Jun 23, 2023
8a77926
Update some bits of text
thibault Jun 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions envergo/geodata/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from envergo.geodata.forms import DepartmentForm
from envergo.geodata.models import Department, Map, Parcel, Zone
from envergo.geodata.tasks import process_shapefile_map
from envergo.geodata.tasks import generate_map_preview, process_shapefile_map
from envergo.geodata.utils import count_features, extract_shapefile


Expand Down Expand Up @@ -60,7 +60,7 @@ def queryset(self, request, queryset):


@admin.register(Map)
class MapAdmin(admin.ModelAdmin):
class MapAdmin(gis_admin.GISModelAdmin):
form = MapForm
list_display = [
"name",
Expand All @@ -80,7 +80,7 @@ class MapAdmin(admin.ModelAdmin):
"task_status",
"import_error_msg",
]
actions = ["process"]
actions = ["process", "generate_preview"]
exclude = ["task_id"]
search_fields = ["name", "display_name"]
list_filter = ["import_status", "map_type", "data_type", DepartmentsListFilter]
Expand Down Expand Up @@ -161,6 +161,18 @@ def process(self, request, queryset):
)
self.message_user(request, msg, level=messages.INFO)

@admin.action(description=_("Generate the simplified preview geometry"))
def generate_preview(self, request, queryset):
if queryset.count() > 1:
error = _("Please only select one map for this action.")
self.message_user(request, error, level=messages.ERROR)
return

map = queryset[0]
generate_map_preview.delay(map.id)
msg = _("The map preview will be updated soon.")
self.message_user(request, msg, level=messages.INFO)

@admin.display(description=_("Extracted zones"))
def zone_count(self, obj):
count = Zone.objects.filter(map=obj).count()
Expand Down
20 changes: 20 additions & 0 deletions envergo/geodata/migrations/0003_map_geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2 on 2023-06-16 12:36

import django.contrib.gis.db.models.fields
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("geodata", "0002_map_imported_zones"),
]

operations = [
migrations.AddField(
model_name="map",
name="geometry",
field=django.contrib.gis.db.models.fields.MultiPolygonField(
geography=True, null=True, srid=4326, verbose_name="Simplified geometry"
),
),
]
4 changes: 3 additions & 1 deletion envergo/geodata/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ class Map(models.Model):
choices=DEPARTMENT_CHOICES,
),
)

geometry = gis_models.MultiPolygonField(
_("Simplified geometry"), geography=True, null=True
)
created_at = models.DateTimeField(_("Date created"), default=timezone.now)
expected_zones = models.IntegerField(_("Expected zones"), default=0)
imported_zones = models.IntegerField(_("Imported zones"), null=True, blank=True)
Expand Down
20 changes: 18 additions & 2 deletions envergo/geodata/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,36 @@

from config.celery_app import app
from envergo.geodata.models import STATUSES, Map
from envergo.geodata.utils import process_shapefile
from envergo.geodata.utils import process_shapefile, simplify_map

logger = logging.getLogger(__name__)


@app.task(bind=True)
@transaction.atomic
def process_shapefile_map(task, map_id):
logger.info(f"Starting import on map {map_id}")

map = Map.objects.get(pk=map_id)

# Store the task data in the model, so we can display progression
# in the admin page.
map.task_id = task.request.id
map.import_error_msg = ""
map.import_status = None
map.save()

map.zones.all().delete()
# Proceed with the map import
try:
with transaction.atomic():
map.zones.all().delete()
process_shapefile(map, map.file, task)
map.geometry = simplify_map(map)
except Exception as e:
map.import_error_msg = f"Erreur d'import ({e})"
logger.error(map.import_error_msg)

# Update the map status and metadata
nb_imported_zones = map.zones.all().count()
if map.expected_zones == nb_imported_zones:
map.import_status = STATUSES.success
Expand All @@ -38,3 +45,12 @@ def process_shapefile_map(task, map_id):
map.task_id = None
map.imported_zones = nb_imported_zones
map.save()


@app.task(bind=True)
def generate_map_preview(task, map_id):
logger.info(f"Starting preview generation on map {map_id}")

map = Map.objects.get(pk=map_id)
map.geometry = simplify_map(map)
map.save()
53 changes: 50 additions & 3 deletions envergo/geodata/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@

import requests
from django.contrib.gis.gdal import DataSource
from django.contrib.gis.geos import GEOSGeometry
from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon
from django.contrib.gis.utils.layermapping import LayerMapping
from django.core.serializers import serialize
from django.db import connection
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _

from envergo.geodata.models import Zone

logger = logging.getLogger(__name__)

EPSG_WGS84 = 4326


class CeleryDebugStream:
"""A sys.stdout proxy that also updates the celery task states.
Expand Down Expand Up @@ -49,6 +52,8 @@ def write(self, msg):


class CustomMapping(LayerMapping):
"""A custom LayerMapping that allows to pass extra arguments to the generated model."""

def __init__(self, *args, **kwargs):
self.extra_kwargs = kwargs.pop("extra_kwargs")
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -127,8 +132,6 @@ def to_geojson(obj, geometry_field="geometry"):
srid.
"""

EPSG_WGS84 = 4326

if isinstance(obj, (QuerySet, list)):
geojson = serialize("geojson", obj, geometry_field=geometry_field)
elif hasattr(obj, "geojson"):
Expand Down Expand Up @@ -191,3 +194,47 @@ def merge_geometries(polygons):
pass

return merged


def simplify_map(map):
"""Generates a simplified geometry for the entire map.

This methods takes a map and generates a single polygon that is the union
of all the polygons in the map.

We also simplify the polygon because this is for display purpose only.

We use native postgis methods those operations, because it's way faster.

As for simplification, we don't preserve topology (ST_Simplify instead of
ST_SimplifyPreserveTopology) because we want to be able to drop small
holes in the polygon.

Because of that, we also have to call ST_MakeValid to avoid returning invalid
polygons."""

with connection.cursor() as cursor:
cursor.execute(
"""
SELECT
ST_AsText(
ST_MakeValid(
ST_Simplify(
ST_Union(z.geometry::geometry),
0.0001
),
'method=structure keepcollapsed=false'
)
)
AS polygon
FROM geodata_zone as z
WHERE z.map_id = %s
""",
[map.id],
)
row = cursor.fetchone()

polygon = GEOSGeometry(row[0], srid=EPSG_WGS84)
if isinstance(polygon, Polygon):
polygon = MultiPolygon(polygon)
return polygon
17 changes: 17 additions & 0 deletions envergo/moulinette/migrations/0005_contact_regulation_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2 on 2023-06-22 12:40

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("moulinette", "0004_alter_contact_options"),
]

operations = [
migrations.AddField(
model_name="contact",
name="regulation_url",
field=models.URLField(blank=True, verbose_name="Regulation URL"),
),
]
12 changes: 9 additions & 3 deletions envergo/moulinette/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance as D
from django.db import models
from django.db.models import F
from django.db.models import Case, F, When
from django.db.models.functions import Cast
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -158,7 +158,7 @@ def __init__(self, data, raw_data):
self.catalog["config"] = self.department.moulinette_config

self.perimeters = self.get_perimeters()
self.criterions = self.get_criterions()
self.criterions_classes = self.get_criterions()

# This is a clear case of circular references, since the Moulinette
# holds references to the regulations it's computing, but regulations and
Expand Down Expand Up @@ -251,7 +251,12 @@ def get_perimeters(self):
Perimeter.objects.filter(
map__zones__geometry__dwithin=(coords, F("activation_distance"))
)
.annotate(geometry=F("map__zones__geometry"))
.annotate(
geometry=Case(
When(map__geometry__isnull=False, then=F("map__geometry")),
default=F("map__zones__geometry"),
)
)
.annotate(distance=Distance("map__zones__geometry", coords))
.order_by("distance", "map__name")
.select_related("map", "contact")
Expand Down Expand Up @@ -449,6 +454,7 @@ class Contact(models.Model):
)
name = models.CharField(_("Name"), max_length=256)
url = models.URLField(_("URL"), blank=True)
regulation_url = models.URLField(_("Regulation URL"), blank=True)
address_md = models.TextField(_("Address"))
address_html = models.TextField(_("Address HTML"), blank=True)

Expand Down
28 changes: 20 additions & 8 deletions envergo/moulinette/regulations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from abc import ABC
from dataclasses import dataclass
from enum import Enum
from functools import cached_property
Expand All @@ -21,18 +22,24 @@ def __str__(self):
return self.text


class MoulinetteRegulation:
"""Run the moulinette for a single regulation (e.g Loi sur l'eau)."""
class MoulinetteRegulation(ABC):
"""Run the moulinette for a single regulation (e.g Loi sur l'eau).

This class is meant to be inherited to implement actual regulations.
"""

# Implement this in subclasses
criterion_classes = []

def __init__(self, moulinette):
self.moulinette = moulinette
self.moulinette.catalog.update(self.get_catalog_data())

# Instanciate the criterions
self.criterions = [
Criterion(moulinette)
for Criterion in self.criterion_classes
if Criterion in moulinette.criterions
perimeter.criterion(moulinette, perimeter)
for perimeter in moulinette.perimeters
if perimeter.criterion in self.criterion_classes
]

def get_catalog_data(self):
Expand Down Expand Up @@ -119,7 +126,11 @@ def _get_map(self):

@dataclass
class MapPolygon:
"""Data that can be displayed and labeled on a leaflet map as a polygon."""
"""Data that can be displayed and labeled on a leaflet map as a polygon.

A `MapPolygon is meant to represent a single entry on a map:
a polygon with a given color and label.
"""

perimeters: list # List of `envergo.geofr.Perimeter` objects
color: str
Expand Down Expand Up @@ -185,7 +196,7 @@ def sources(self):
return maps


class MoulinetteCriterion:
class MoulinetteCriterion(ABC):
"""Run a single moulinette check."""

# Prevent template engine to instanciate the class since we sometimes want
Expand All @@ -198,9 +209,10 @@ class MoulinetteCriterion:
# "Nomenclature réglementations & critères" document.
CODES = ["soumis", "non_soumis", "action_requise", "non_concerne"]

def __init__(self, moulinette):
def __init__(self, moulinette, perimeter):
self.moulinette = moulinette
self.moulinette.catalog.update(self.get_catalog_data())
self.perimeter = perimeter

def get_catalog_data(self):
"""Get data to inject to the global catalog."""
Expand Down
27 changes: 27 additions & 0 deletions envergo/moulinette/regulations/sage.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,30 @@ def result(self):
result = RESULTS.non_disponible

return result

def _get_map(self):
# Let's find the first map that we can display
perimeter = next(
(
criterion.perimeter
for criterion in self.criterions
if criterion.perimeter.map.display_for_user
and criterion.perimeter.map.geometry
),
None,
)
if not perimeter:
return None

map_polygons = [MapPolygon([perimeter], "red", "Sage")]
caption = "Le projet se situe dans le périmètre du Sage."

map = Map(
center=self.catalog["coords"],
entries=map_polygons,
caption=caption,
truncate=False,
zoom=None,
)

return map
10 changes: 10 additions & 0 deletions envergo/pages/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.contrib import admin

from envergo.pages.models import NewsItem


@admin.register(NewsItem)
class NewsItemAdmin(admin.ModelAdmin):
list_display = ["title", "created_at"]
search_fields = ["title", "content_md"]
fields = ["title", "content_md", "created_at"]
Loading
Loading