Compare commits
2 Commits
master
...
test-actio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e14f0700cf | ||
|
|
814b2bb15f |
48
.env.sample
48
.env.sample
@ -1,48 +0,0 @@
|
|||||||
# General
|
|
||||||
SECRET_KEY=CHANGE_ME
|
|
||||||
DEBUG=True
|
|
||||||
ALLOWED_HOSTS=127.0.0.1,localhost,example.org
|
|
||||||
BASE_URL=http://localhost:8002
|
|
||||||
ADMINS=Admin1:mail@example.org,Admin2:mail2@example.org
|
|
||||||
|
|
||||||
# Database
|
|
||||||
DB_USER=postgres
|
|
||||||
DB_PASSWORD=
|
|
||||||
DB_NAME=konova
|
|
||||||
DB_HOST=127.0.0.1
|
|
||||||
DB_PORT=5432
|
|
||||||
|
|
||||||
# Redis (for celery)
|
|
||||||
REDIS_HOST=CHANGE_ME
|
|
||||||
REDIS_PORT=CHANGE_ME
|
|
||||||
|
|
||||||
# E-Mail
|
|
||||||
SMTP_HOST=localhost
|
|
||||||
SMTP_PORT=25
|
|
||||||
REPLY_TO_ADDR=ksp-servicestelle@sgdnord.rlp.de
|
|
||||||
DEFAULT_FROM_EMAIL=service@ksp.de
|
|
||||||
|
|
||||||
# Proxy
|
|
||||||
PROXY=CHANGE_ME
|
|
||||||
MAP_PROXY_HOST_WHITELIST=CHANGE_ME_1,CHANGE_ME_2
|
|
||||||
GEOPORTAL_RLP_USER=CHANGE_ME
|
|
||||||
GEOPORTAL_RLP_PASSWORD=CHANGE_ME
|
|
||||||
|
|
||||||
# Schneider
|
|
||||||
SCHNEIDER_BASE_URL=https://schneider.naturschutz.rlp.de
|
|
||||||
SCHNEIDER_AUTH_TOKEN=CHANGE_ME
|
|
||||||
SCHNEIDER_AUTH_HEADER=auth
|
|
||||||
|
|
||||||
# SSO
|
|
||||||
SSO_SERVER_BASE_URL=https://login.naturschutz.rlp.de
|
|
||||||
OAUTH_CODE_VERIFIER=CHANGE_ME
|
|
||||||
OAUTH_CLIENT_ID=CHANGE_ME
|
|
||||||
OAUTH_CLIENT_SECRET=CHANGE_ME
|
|
||||||
PROPAGATION_SECRET=CHANGE_ME
|
|
||||||
|
|
||||||
# RabbitMQ
|
|
||||||
## For connections to EGON
|
|
||||||
EGON_RABBITMQ_HOST=CHANGE_ME
|
|
||||||
EGON_RABBITMQ_PORT=CHANGE_ME
|
|
||||||
EGON_RABBITMQ_USER=CHANGE_ME
|
|
||||||
EGON_RABBITMQ_PW=CHANGE_ME
|
|
||||||
19
.gitea/workflows/demo.yaml
Normal file
19
.gitea/workflows/demo.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Gitea Actions Demo
|
||||||
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Explore-Gitea-Actions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
- run: echo "This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||||
|
- run: echo "The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: https://github.com/actions/checkout@v3
|
||||||
|
- run: echo "The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
- run: echo "The workflow is now ready to test your code on the runner."
|
||||||
|
- name: List files in the repository
|
||||||
|
run: |
|
||||||
|
ls ${{ gitea.workspace }}
|
||||||
|
- run: echo "This job's status is ${{ job.status }}."
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,4 +3,3 @@
|
|||||||
/.idea/
|
/.idea/
|
||||||
/.coverage
|
/.coverage
|
||||||
/htmlcov/
|
/htmlcov/
|
||||||
/.env
|
|
||||||
|
|||||||
15
api/admin.py
15
api/admin.py
@ -1,6 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from api.models.token import APIUserToken, OAuthToken
|
from api.models.token import APIUserToken
|
||||||
|
|
||||||
|
|
||||||
class APITokenAdmin(admin.ModelAdmin):
|
class APITokenAdmin(admin.ModelAdmin):
|
||||||
@ -17,17 +17,4 @@ class APITokenAdmin(admin.ModelAdmin):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class OAuthTokenAdmin(admin.ModelAdmin):
|
|
||||||
list_display = [
|
|
||||||
"access_token",
|
|
||||||
"refresh_token",
|
|
||||||
"expires_on",
|
|
||||||
]
|
|
||||||
search_fields = [
|
|
||||||
"access_token",
|
|
||||||
"refresh_token",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(APIUserToken, APITokenAdmin)
|
admin.site.register(APIUserToken, APITokenAdmin)
|
||||||
admin.site.register(OAuthToken, OAuthTokenAdmin)
|
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
# Generated by Django 5.0.4 on 2024-04-30 07:20
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0002_alter_apiusertoken_valid_until'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='OAuthToken',
|
|
||||||
fields=[
|
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
|
||||||
('access_token', models.CharField(db_comment='OAuth access token', max_length=255)),
|
|
||||||
('refresh_token', models.CharField(db_comment='OAuth refresh token', max_length=255)),
|
|
||||||
('expires_on', models.DateTimeField(db_comment='When the token will be expired')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,14 +1,7 @@
|
|||||||
import json
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.timezone import now
|
|
||||||
|
|
||||||
from konova.models import UuidModel
|
|
||||||
from konova.sub_settings.sso_settings import OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, SSO_SERVER_BASE
|
|
||||||
from konova.utils.generators import generate_token
|
from konova.utils.generators import generate_token
|
||||||
|
|
||||||
|
|
||||||
@ -51,129 +44,5 @@ class APIUserToken(models.Model):
|
|||||||
if token_obj.valid_until is not None and token_obj.valid_until < _today:
|
if token_obj.valid_until is not None and token_obj.valid_until < _today:
|
||||||
raise PermissionError("Token validity expired")
|
raise PermissionError("Token validity expired")
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise PermissionError("Token unknown")
|
raise PermissionError("Credentials invalid")
|
||||||
return token_obj.user
|
return token_obj.user
|
||||||
|
|
||||||
|
|
||||||
class OAuthToken(UuidModel):
|
|
||||||
access_token = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
blank=False,
|
|
||||||
null=False,
|
|
||||||
db_comment="OAuth access token"
|
|
||||||
)
|
|
||||||
refresh_token = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
blank=False,
|
|
||||||
null=False,
|
|
||||||
db_comment="OAuth refresh token"
|
|
||||||
)
|
|
||||||
expires_on = models.DateTimeField(
|
|
||||||
db_comment="When the token will be expired"
|
|
||||||
)
|
|
||||||
|
|
||||||
ASSUMED_LATENCY = 1000 # assumed latency between creation and receiving of an access token
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.access_token)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_access_token_response(access_token_data: str, received_on):
|
|
||||||
"""
|
|
||||||
Creates an OAuthToken based on retrieved access token data (OAuth2.0 specification)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
access_token_data (str): OAuth2.0 response data
|
|
||||||
received_on (): Timestamp when the response has been received
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
oauth_token = OAuthToken()
|
|
||||||
data = json.loads(access_token_data)
|
|
||||||
|
|
||||||
oauth_token.access_token = data.get("access_token")
|
|
||||||
oauth_token.refresh_token = data.get("refresh_token")
|
|
||||||
|
|
||||||
expires_on = received_on + timedelta(
|
|
||||||
seconds=(data.get("expires_in") + OAuthToken.ASSUMED_LATENCY)
|
|
||||||
)
|
|
||||||
oauth_token.expires_on = expires_on
|
|
||||||
|
|
||||||
return oauth_token
|
|
||||||
|
|
||||||
def refresh(self):
|
|
||||||
url = f"{SSO_SERVER_BASE}o/token/"
|
|
||||||
params = {
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
"refresh_token": self.refresh_token,
|
|
||||||
"client_id": OAUTH_CLIENT_ID,
|
|
||||||
"client_secret": OAUTH_CLIENT_SECRET
|
|
||||||
}
|
|
||||||
response = requests.post(
|
|
||||||
url,
|
|
||||||
params
|
|
||||||
)
|
|
||||||
_now = now()
|
|
||||||
is_response_invalid = response.status_code != 200
|
|
||||||
if is_response_invalid:
|
|
||||||
raise RuntimeError(f"Refreshing token not possible: {response.status_code}")
|
|
||||||
|
|
||||||
response_content = response.content.decode("utf-8")
|
|
||||||
response_content = json.loads(response_content)
|
|
||||||
|
|
||||||
access_token = response_content.get("access_token")
|
|
||||||
refresh_token = response_content.get("refresh_token")
|
|
||||||
expires_in = response_content.get("expires")
|
|
||||||
|
|
||||||
self.access_token = access_token
|
|
||||||
self.refresh_token = refresh_token
|
|
||||||
self.expires_in = expires_in
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def update_and_get_user(self):
|
|
||||||
from user.models import User
|
|
||||||
url = f"{SSO_SERVER_BASE}users/oauth/data/"
|
|
||||||
|
|
||||||
access_token = self.access_token
|
|
||||||
response = requests.get(
|
|
||||||
url,
|
|
||||||
headers={
|
|
||||||
"Authorization": f"Bearer {access_token}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
is_response_code_invalid = response.status_code != 200
|
|
||||||
if is_response_code_invalid:
|
|
||||||
raise RuntimeError(f"OAuth user data fetching unsuccessful: {response.status_code}")
|
|
||||||
|
|
||||||
response_content = response.content.decode("utf-8")
|
|
||||||
response_content = json.loads(response_content)
|
|
||||||
user = User.oauth_update_user(response_content)
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
||||||
def revoke(self) -> int:
|
|
||||||
""" Revokes the OAuth2 token of the user
|
|
||||||
|
|
||||||
(/o/revoke_token/ indeed removes the corresponding access token on provider side and invalidates the
|
|
||||||
submitted refresh token in one step)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
revocation_status_code (int): HTTP status code for revocation of refresh_token
|
|
||||||
"""
|
|
||||||
revoke_url = f"{SSO_SERVER_BASE}o/revoke_token/"
|
|
||||||
token = self.refresh_token
|
|
||||||
revocation_status_code = requests.post(
|
|
||||||
revoke_url,
|
|
||||||
data={
|
|
||||||
'token': token,
|
|
||||||
'token_type_hint': "refresh_token",
|
|
||||||
},
|
|
||||||
auth=(OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET),
|
|
||||||
).status_code
|
|
||||||
|
|
||||||
return revocation_status_code
|
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from django.contrib.gis import geos
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
||||||
from konova.models import Geometry
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
|
|
||||||
|
|
||||||
class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||||
@ -64,7 +64,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.intervention.geometry.geom)
|
self.assertEqual(put_geom, self.intervention.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.intervention.title)
|
self.assertEqual(put_props["title"], self.intervention.title)
|
||||||
self.assertNotEqual(modified_on, self.intervention.modified)
|
self.assertNotEqual(modified_on, self.intervention.modified)
|
||||||
@ -94,7 +94,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.compensation.geometry.geom)
|
self.assertEqual(put_geom, self.compensation.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.compensation.title)
|
self.assertEqual(put_props["title"], self.compensation.title)
|
||||||
self.assertNotEqual(modified_on, self.compensation.modified)
|
self.assertNotEqual(modified_on, self.compensation.modified)
|
||||||
@ -124,7 +124,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.eco_account.geometry.geom)
|
self.assertEqual(put_geom, self.eco_account.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.eco_account.title)
|
self.assertEqual(put_props["title"], self.eco_account.title)
|
||||||
self.assertNotEqual(modified_on, self.eco_account.modified)
|
self.assertNotEqual(modified_on, self.eco_account.modified)
|
||||||
@ -156,7 +156,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.ema.geometry.geom)
|
self.assertEqual(put_geom, self.ema.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.ema.title)
|
self.assertEqual(put_props["title"], self.ema.title)
|
||||||
self.assertNotEqual(modified_on, self.ema.modified)
|
self.assertNotEqual(modified_on, self.ema.modified)
|
||||||
|
|||||||
@ -11,9 +11,8 @@ from abc import abstractmethod
|
|||||||
from django.contrib.gis import geos
|
from django.contrib.gis import geos
|
||||||
from django.contrib.gis.geos import GEOSGeometry
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from konova.models import Geometry
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
from konova.utils.message_templates import DATA_UNSHARED
|
from konova.utils.message_templates import DATA_UNSHARED
|
||||||
|
|
||||||
|
|
||||||
@ -33,8 +32,8 @@ class AbstractModelAPISerializer:
|
|||||||
self.lookup = {
|
self.lookup = {
|
||||||
"id": None, # must be set
|
"id": None, # must be set
|
||||||
"deleted__isnull": True,
|
"deleted__isnull": True,
|
||||||
|
"users__in": [], # must be set
|
||||||
}
|
}
|
||||||
self.shared_lookup = Q() # must be set, so user or team based share will be used properly
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -77,11 +76,7 @@ class AbstractModelAPISerializer:
|
|||||||
else:
|
else:
|
||||||
# Return certain object
|
# Return certain object
|
||||||
self.lookup["id"] = _id
|
self.lookup["id"] = _id
|
||||||
|
self.lookup["users__in"] = [user]
|
||||||
self.shared_lookup = Q(
|
|
||||||
Q(users__in=[user]) |
|
|
||||||
Q(teams__in=list(user.shared_teams))
|
|
||||||
)
|
|
||||||
|
|
||||||
def fetch_and_serialize(self):
|
def fetch_and_serialize(self):
|
||||||
""" Serializes the model entry according to the given lookup data
|
""" Serializes the model entry according to the given lookup data
|
||||||
@ -91,13 +86,7 @@ class AbstractModelAPISerializer:
|
|||||||
Returns:
|
Returns:
|
||||||
serialized_data (dict)
|
serialized_data (dict)
|
||||||
"""
|
"""
|
||||||
entries = self.model.objects.filter(
|
entries = self.model.objects.filter(**self.lookup).order_by("id")
|
||||||
**self.lookup
|
|
||||||
).filter(
|
|
||||||
self.shared_lookup
|
|
||||||
).order_by(
|
|
||||||
"id"
|
|
||||||
).distinct()
|
|
||||||
self.paginator = Paginator(entries, self.rpp)
|
self.paginator = Paginator(entries, self.rpp)
|
||||||
requested_entries = self.paginator.page(self.page_number)
|
requested_entries = self.paginator.page(self.page_number)
|
||||||
|
|
||||||
@ -145,8 +134,8 @@ class AbstractModelAPISerializer:
|
|||||||
if isinstance(geojson, dict):
|
if isinstance(geojson, dict):
|
||||||
geojson = json.dumps(geojson)
|
geojson = json.dumps(geojson)
|
||||||
geometry = geos.fromstr(geojson)
|
geometry = geos.fromstr(geojson)
|
||||||
geometry = Geometry.cast_to_rlp_srid(geometry)
|
if geometry.srid != DEFAULT_SRID_RLP:
|
||||||
geometry = Geometry.cast_to_multipolygon(geometry)
|
geometry.transform(DEFAULT_SRID_RLP)
|
||||||
return geometry
|
return geometry
|
||||||
|
|
||||||
def _get_obj_from_db(self, id, user):
|
def _get_obj_from_db(self, id, user):
|
||||||
|
|||||||
@ -6,7 +6,6 @@ Created on: 24.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin
|
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin
|
||||||
from compensation.models import Compensation
|
from compensation.models import Compensation
|
||||||
@ -22,10 +21,8 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
|
|
||||||
def prepare_lookup(self, id, user):
|
def prepare_lookup(self, id, user):
|
||||||
super().prepare_lookup(id, user)
|
super().prepare_lookup(id, user)
|
||||||
self.shared_lookup = Q(
|
del self.lookup["users__in"]
|
||||||
Q(intervention__users__in=[user]) |
|
self.lookup["intervention__users__in"] = [user]
|
||||||
Q(intervention__teams__in=user.shared_teams)
|
|
||||||
)
|
|
||||||
|
|
||||||
def intervention_to_json(self, entry):
|
def intervention_to_json(self, entry):
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -6,7 +6,6 @@ Created on: 28.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from api.utils.serializer.v1.serializer import DeductableAPISerializerV1Mixin, AbstractModelAPISerializerV1
|
from api.utils.serializer.v1.serializer import DeductableAPISerializerV1Mixin, AbstractModelAPISerializerV1
|
||||||
from compensation.models import EcoAccountDeduction, EcoAccount
|
from compensation.models import EcoAccountDeduction, EcoAccount
|
||||||
@ -29,11 +28,9 @@ class DeductionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
super().prepare_lookup(_id, user)
|
super().prepare_lookup(_id, user)
|
||||||
|
del self.lookup["users__in"]
|
||||||
del self.lookup["deleted__isnull"]
|
del self.lookup["deleted__isnull"]
|
||||||
self.shared_lookup = Q(
|
self.lookup["intervention__users__in"] = [user]
|
||||||
Q(intervention__users__in=[user]) |
|
|
||||||
Q(intervention__teams__in=user.shared_teams)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _model_to_geo_json(self, entry):
|
def _model_to_geo_json(self, entry):
|
||||||
""" Adds the basic data
|
""" Adds the basic data
|
||||||
|
|||||||
@ -16,8 +16,7 @@ from api.utils.serializer.serializer import AbstractModelAPISerializer
|
|||||||
from codelist.models import KonovaCode
|
from codelist.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
|
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
|
||||||
CODELIST_LAW_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, \
|
CODELIST_LAW_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, \
|
||||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_HANDLER_ID, \
|
CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_HANDLER_ID
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
|
|
||||||
from compensation.models import CompensationAction, UnitChoices, CompensationState
|
from compensation.models import CompensationAction, UnitChoices, CompensationState
|
||||||
from intervention.models import Responsibility, Legal, Handler
|
from intervention.models import Responsibility, Legal, Handler
|
||||||
from konova.models import Deadline, DeadlineType
|
from konova.models import Deadline, DeadlineType
|
||||||
@ -348,7 +347,7 @@ class AbstractCompensationAPISerializerV1Mixin:
|
|||||||
try:
|
try:
|
||||||
biotope_type = entry["biotope"]
|
biotope_type = entry["biotope"]
|
||||||
biotope_details = [
|
biotope_details = [
|
||||||
self._konova_code_from_json(e, CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID) for e in entry["biotope_details"]
|
self._konova_code_from_json(e, CODELIST_BIOTOPES_EXTRA_CODES_ID) for e in entry["biotope_details"]
|
||||||
]
|
]
|
||||||
surface = float(entry["surface"])
|
surface = float(entry["surface"])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
@ -23,6 +23,11 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.lookup = {
|
||||||
|
"id": None, # must be set in subclasses
|
||||||
|
"deleted__isnull": True,
|
||||||
|
"users__in": [], # must be set in subclasses
|
||||||
|
}
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.serializer = self.serializer()
|
self.serializer = self.serializer()
|
||||||
|
|
||||||
|
|||||||
@ -50,19 +50,14 @@ class AbstractAPIView(View):
|
|||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
# Fetch the proper user from the given request header token
|
# Fetch the proper user from the given request header token
|
||||||
token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
|
ksp_token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
|
||||||
ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
|
ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
|
||||||
|
token_user = APIUserToken.get_user_from_token(ksp_token)
|
||||||
|
|
||||||
if not token and not ksp_user:
|
if ksp_user != token_user.username:
|
||||||
bearer_token = request.headers.get("authorization", None)
|
|
||||||
if not bearer_token:
|
|
||||||
raise PermissionError("No token provided")
|
|
||||||
token = bearer_token.split(" ")[1]
|
|
||||||
|
|
||||||
token_user = APIUserToken.get_user_from_token(token)
|
|
||||||
if ksp_user and ksp_user != token_user.username:
|
|
||||||
raise PermissionError(f"Invalid token for {ksp_user}")
|
raise PermissionError(f"Invalid token for {ksp_user}")
|
||||||
self.user = token_user
|
else:
|
||||||
|
self.user = token_user
|
||||||
|
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
if not self.user.is_default_user():
|
if not self.user.is_default_user():
|
||||||
|
|||||||
@ -9,8 +9,7 @@ import collections
|
|||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
from codelist.settings import CODELIST_BIOTOPES_ID, \
|
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
|
|
||||||
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
from codelist.autocomplete.base import KonovaCodeAutocomplete
|
||||||
from konova.utils.message_templates import UNGROUPED
|
from konova.utils.message_templates import UNGROUPED
|
||||||
|
|
||||||
@ -85,11 +84,11 @@ class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
|
|||||||
Due to limitations of the django dal package, we need to subclass for each code list
|
Due to limitations of the django dal package, we need to subclass for each code list
|
||||||
"""
|
"""
|
||||||
group_by_related = "parent"
|
group_by_related = "parent"
|
||||||
related_field_name = "short_name"
|
related_field_name = "long_name"
|
||||||
paginate_by = 200
|
paginate_by = 200
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.c = CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
|
self.c = CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def order_by(self, qs):
|
def order_by(self, qs):
|
||||||
@ -104,11 +103,8 @@ class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
|
|||||||
qs (QuerySet): The ordered queryset
|
qs (QuerySet): The ordered queryset
|
||||||
"""
|
"""
|
||||||
return qs.order_by(
|
return qs.order_by(
|
||||||
"short_name",
|
"long_name",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_result_label(self, result):
|
def get_result_label(self, result):
|
||||||
return f"{result.long_name} ({result.short_name})"
|
return f"{result.long_name} ({result.short_name})"
|
||||||
|
|
||||||
def get_selected_result_label(self, result):
|
|
||||||
return f"{result.parent.short_name} > {result.long_name} ({result.short_name})"
|
|
||||||
@ -13,7 +13,7 @@ from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERV
|
|||||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_HANDLER_ID, \
|
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_HANDLER_ID, \
|
||||||
CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \
|
CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \
|
||||||
CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \
|
CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \
|
||||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
|
CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||||
from konova.management.commands.setup import BaseKonovaCommand
|
from konova.management.commands.setup import BaseKonovaCommand
|
||||||
from konova.settings import PROXIES
|
from konova.settings import PROXIES
|
||||||
|
|
||||||
@ -34,7 +34,6 @@ class Command(BaseKonovaCommand):
|
|||||||
CODELIST_REGISTRATION_OFFICE_ID,
|
CODELIST_REGISTRATION_OFFICE_ID,
|
||||||
CODELIST_BIOTOPES_ID,
|
CODELIST_BIOTOPES_ID,
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_ID,
|
CODELIST_BIOTOPES_EXTRA_CODES_ID,
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID,
|
|
||||||
CODELIST_LAW_ID,
|
CODELIST_LAW_ID,
|
||||||
CODELIST_HANDLER_ID,
|
CODELIST_HANDLER_ID,
|
||||||
CODELIST_COMPENSATION_ACTION_ID,
|
CODELIST_COMPENSATION_ACTION_ID,
|
||||||
@ -56,7 +55,7 @@ class Command(BaseKonovaCommand):
|
|||||||
content = result.content.decode("utf-8")
|
content = result.content.decode("utf-8")
|
||||||
root = etree.fromstring(content)
|
root = etree.fromstring(content)
|
||||||
items = root.findall("item")
|
items = root.findall("item")
|
||||||
self._write_warning(" Found {} codes. Process now...".format(len(items)))
|
self._write_warning("Found {} codes. Process now...".format(len(items)))
|
||||||
|
|
||||||
code_list = KonovaCodeList.objects.get_or_create(
|
code_list = KonovaCodeList.objects.get_or_create(
|
||||||
id=list_id,
|
id=list_id,
|
||||||
@ -75,15 +74,15 @@ class Command(BaseKonovaCommand):
|
|||||||
if items is None:
|
if items is None:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self._write_warning(" --- Found {} subcodes. Process now...".format(len(items)))
|
self._write_warning(" --- Found {} subcodes. Process now...".format(len(items)))
|
||||||
for element in items:
|
for element in items:
|
||||||
children = element.find("items")
|
children = element.find("items")
|
||||||
_id = element.find("id").text
|
_id = element.find("id").text
|
||||||
atom_id = element.find("atomid").text
|
atom_id = element.find("atomid").text
|
||||||
selectable = element.find("selectable").text.lower()
|
selectable = element.find("selectable").text.lower()
|
||||||
selectable = bool_map.get(selectable, False)
|
selectable = bool_map.get(selectable, False)
|
||||||
short_name = element.find("shortname").text or ""
|
short_name = element.find("shortname").text
|
||||||
long_name = element.find("longname").text or ""
|
long_name = element.find("longname").text
|
||||||
is_archived = bool_map.get((element.find("archive").text.lower()), False)
|
is_archived = bool_map.get((element.find("archive").text.lower()), False)
|
||||||
|
|
||||||
code = KonovaCode.objects.get_or_create(
|
code = KonovaCode.objects.get_or_create(
|
||||||
|
|||||||
@ -1,60 +0,0 @@
|
|||||||
# Generated by Django 5.0.7 on 2024-08-06 13:40
|
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.db import migrations
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from codelist.settings import CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_975_to_288(apps, schema_editor):
|
|
||||||
KonovaCodeList = apps.get_model('codelist', 'KonovaCodeList')
|
|
||||||
CompensationState = apps.get_model('compensation', 'CompensationState')
|
|
||||||
|
|
||||||
try:
|
|
||||||
list_288 = KonovaCodeList.objects.get(
|
|
||||||
id=CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
|
|
||||||
).codes.all()
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise AssertionError("KonovaCodeList 288 does not exist. Did you run 'update_codelist' before migrating?")
|
|
||||||
|
|
||||||
states_with_extra_code = CompensationState.objects.filter(
|
|
||||||
~Q(biotope_type_details=None)
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"... Found {states_with_extra_code.count()} biotope state entries")
|
|
||||||
for state in states_with_extra_code:
|
|
||||||
extra_codes_975 = state.biotope_type_details.filter(
|
|
||||||
code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_ID]
|
|
||||||
)
|
|
||||||
count_extra_codes_975 = extra_codes_975.count()
|
|
||||||
if count_extra_codes_975 > 0:
|
|
||||||
print(f" --> Found {count_extra_codes_975} codes from list 975 in biotope entry {state.id}")
|
|
||||||
extra_codes_288 = []
|
|
||||||
for extra_code_975 in extra_codes_975:
|
|
||||||
atom_id = extra_code_975.atom_id
|
|
||||||
codes_from_288 = list_288.filter(
|
|
||||||
atom_id=atom_id,
|
|
||||||
)
|
|
||||||
extra_codes_288 += codes_from_288
|
|
||||||
|
|
||||||
state.biotope_type_details.set(extra_codes_288)
|
|
||||||
print(" --> Migrated to list 288 for all biotope entries")
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('codelist', '0001_initial'),
|
|
||||||
('compensation', '0003_auto_20220202_0846'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# If migration of codelist is not necessary, this variable can shut down the logic whilst not disturbing the
|
|
||||||
# migration history
|
|
||||||
EXECUTE_CODELIST_MIGRATION = True
|
|
||||||
|
|
||||||
operations = []
|
|
||||||
|
|
||||||
if EXECUTE_CODELIST_MIGRATION:
|
|
||||||
operations.append(migrations.RunPython(migrate_975_to_288))
|
|
||||||
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 5.0.8 on 2024-08-26 16:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('codelist', '0002_migrate_975_to_288'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='konovacode',
|
|
||||||
name='long_name',
|
|
||||||
field=models.CharField(blank=True, default="", max_length=1000),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='konovacode',
|
|
||||||
name='short_name',
|
|
||||||
field=models.CharField(blank=True, default="", help_text='Short version of long name', max_length=500),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -25,11 +25,13 @@ class KonovaCode(models.Model):
|
|||||||
)
|
)
|
||||||
short_name = models.CharField(
|
short_name = models.CharField(
|
||||||
max_length=500,
|
max_length=500,
|
||||||
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Short version of long name",
|
help_text="Short version of long name",
|
||||||
)
|
)
|
||||||
long_name = models.CharField(
|
long_name = models.CharField(
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="",
|
help_text="",
|
||||||
)
|
)
|
||||||
@ -48,28 +50,12 @@ class KonovaCode(models.Model):
|
|||||||
|
|
||||||
def __str__(self, with_parent: bool = True):
|
def __str__(self, with_parent: bool = True):
|
||||||
ret_val = ""
|
ret_val = ""
|
||||||
|
if self.parent and self.parent.long_name and with_parent:
|
||||||
long_name = self.long_name
|
ret_val += self.parent.long_name + " > "
|
||||||
short_name = self.short_name
|
ret_val += self.long_name
|
||||||
|
if self.short_name and self.short_name != self.long_name:
|
||||||
both_names_exist = long_name is not None and short_name is not None
|
# Only add short name, if we won't have stupid repition like 'thing a (thing a)' due to misused long-short names
|
||||||
|
ret_val += f" ({self.short_name})"
|
||||||
if both_names_exist:
|
|
||||||
if with_parent and self.parent:
|
|
||||||
parent_short_name_exists = self.parent.short_name is not None
|
|
||||||
parent_long_name_exists = self.parent.long_name is not None
|
|
||||||
if parent_long_name_exists:
|
|
||||||
ret_val += self.parent.long_name + " > "
|
|
||||||
elif parent_short_name_exists:
|
|
||||||
ret_val += self.parent.short_name + " > "
|
|
||||||
|
|
||||||
ret_val += long_name
|
|
||||||
|
|
||||||
if short_name and short_name != long_name:
|
|
||||||
ret_val += f" ({short_name})"
|
|
||||||
else:
|
|
||||||
ret_val += str(long_name or short_name)
|
|
||||||
|
|
||||||
return ret_val
|
return ret_val
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -89,8 +75,7 @@ class KonovaCode(models.Model):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
children = KonovaCode.objects.filter(
|
children = KonovaCode.objects.filter(
|
||||||
parent=self,
|
parent=self
|
||||||
is_archived=False,
|
|
||||||
).order_by(
|
).order_by(
|
||||||
order_by
|
order_by
|
||||||
)
|
)
|
||||||
|
|||||||
@ -15,8 +15,7 @@ CODELIST_CONSERVATION_OFFICE_ID = 907 # CLNaturschutzbehörden
|
|||||||
CODELIST_REGISTRATION_OFFICE_ID = 1053 # CLZulassungsbehörden
|
CODELIST_REGISTRATION_OFFICE_ID = 1053 # CLZulassungsbehörden
|
||||||
CODELIST_BIOTOPES_ID = 654 # CL_Biotoptypen
|
CODELIST_BIOTOPES_ID = 654 # CL_Biotoptypen
|
||||||
CODELIST_AFTER_STATE_BIOTOPES_ID = 974 # CL-KSP_ZielBiotoptypen - USAGE HAS BEEN DROPPED IN 2022 IN FAVOR OF 654
|
CODELIST_AFTER_STATE_BIOTOPES_ID = 974 # CL-KSP_ZielBiotoptypen - USAGE HAS BEEN DROPPED IN 2022 IN FAVOR OF 654
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_ID = 975 # CLZusatzbezeichnung - Subset of 288. Migration usage 975->288 in 08/2024
|
CODELIST_BIOTOPES_EXTRA_CODES_ID = 975 # CLZusatzbezeichnung
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID = 288 # CLBiotoptypZusatzcode
|
|
||||||
CODELIST_LAW_ID = 1048 # CLVerfahrensrecht
|
CODELIST_LAW_ID = 1048 # CLVerfahrensrecht
|
||||||
CODELIST_PROCESS_TYPE_ID = 44382 # CLVerfahrenstyp
|
CODELIST_PROCESS_TYPE_ID = 44382 # CLVerfahrenstyp
|
||||||
|
|
||||||
|
|||||||
@ -14,8 +14,7 @@ from django.shortcuts import render
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from codelist.models import KonovaCode
|
from codelist.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_BIOTOPES_ID, \
|
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
|
|
||||||
from intervention.inputs import CompensationStateTreeRadioSelect
|
from intervention.inputs import CompensationStateTreeRadioSelect
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.forms.modals import RemoveModalForm, BaseModalForm
|
from konova.forms.modals import RemoveModalForm, BaseModalForm
|
||||||
@ -44,7 +43,7 @@ class NewCompensationStateModalForm(BaseModalForm):
|
|||||||
queryset=KonovaCode.objects.filter(
|
queryset=KonovaCode.objects.filter(
|
||||||
is_archived=False,
|
is_archived=False,
|
||||||
is_leaf=True,
|
is_leaf=True,
|
||||||
code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID],
|
code_lists__in=[CODELIST_BIOTOPES_EXTRA_CODES_ID],
|
||||||
),
|
),
|
||||||
widget=autocomplete.ModelSelect2Multiple(
|
widget=autocomplete.ModelSelect2Multiple(
|
||||||
url="codelist:biotope-extra-type-autocomplete",
|
url="codelist:biotope-extra-type-autocomplete",
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.0.8 on 2024-08-26 16:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('codelist', '0003_alter_konovacode_long_name_and_more'),
|
|
||||||
('compensation', '0015_alter_compensation_after_states_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='compensationstate',
|
|
||||||
name='biotope_type_details',
|
|
||||||
field=models.ManyToManyField(blank=True, limit_choices_to={'code_lists__in': [288], 'is_archived': False, 'is_selectable': True}, related_name='+', to='codelist.konovacode'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -6,10 +6,10 @@ Created on: 16.11.21
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from codelist.models import KonovaCode
|
from codelist.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_BIOTOPES_ID, \
|
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||||
CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID
|
|
||||||
from compensation.managers import CompensationStateManager
|
from compensation.managers import CompensationStateManager
|
||||||
from konova.models import UuidModel
|
from konova.models import UuidModel
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ class CompensationState(UuidModel):
|
|||||||
KonovaCode,
|
KonovaCode,
|
||||||
blank=True,
|
blank=True,
|
||||||
limit_choices_to={
|
limit_choices_to={
|
||||||
"code_lists__in": [CODELIST_BIOTOPES_EXTRA_CODES_FULL_ID],
|
"code_lists__in": [CODELIST_BIOTOPES_EXTRA_CODES_ID],
|
||||||
"is_selectable": True,
|
"is_selectable": True,
|
||||||
"is_archived": False,
|
"is_archived": False,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-action' obj.id %}" title="{% trans 'Add new action' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-action' obj.id %}" title="{% trans 'Add new action' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'seedling' %}
|
{% fa5_icon 'seedling' %}
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Comment' %}
|
{% trans 'Comment' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -52,7 +52,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for detail in action.action_type_details.all %}
|
{% for detail in action.action_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.long_name }} > {{detail}}">{{ detail.parent.long_name }} > {{detail.long_name}}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
|
<button data-form-url="{% url 'compensation:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
{% fa5_icon 'file-alt' %}
|
{% fa5_icon 'file-alt' %}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:resubmission-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:resubmission-create' obj.id %}">
|
||||||
{% fa5_icon 'bell' %}
|
{% fa5_icon 'bell' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-deadline' obj.id %}" title="{% trans 'Add new deadline' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-deadline' obj.id %}" title="{% trans 'Add new deadline' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'calendar-check' %}
|
{% fa5_icon 'calendar-check' %}
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Comment' %}
|
{% trans 'Comment' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
|
<button data-form-url="{% url 'compensation:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'file' %}
|
{% fa5_icon 'file' %}
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Comment' %}
|
{% trans 'Comment' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -59,7 +59,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
<button data-form-url="{% url 'compensation:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-state' obj.id %}" title="{% trans 'Add new state after' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-state' obj.id %}" title="{% trans 'Add new state after' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'layer-group' %}
|
{% fa5_icon 'layer-group' %}
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Surface' %}
|
{% trans 'Surface' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -51,14 +51,14 @@
|
|||||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||||
<br>
|
<br>
|
||||||
{% for detail in state.biotope_type_details.all %}
|
{% for detail in state.biotope_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.short_name }} > {{detail}}">{{ detail.parent.short_name }} > {{ detail.long_name }}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
<button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-state' obj.id %}?before=true" title="{% trans 'Add new state before' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:new-state' obj.id %}?before=true" title="{% trans 'Add new state before' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'layer-group' %}
|
{% fa5_icon 'layer-group' %}
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Surface' %}
|
{% trans 'Surface' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -51,14 +51,14 @@
|
|||||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||||
<br>
|
<br>
|
||||||
{% for detail in state.biotope_type_details.all %}
|
{% for detail in state.biotope_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.short_name }} > {{detail}}">{{ detail.parent.short_name }} > {{ detail.long_name }}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
<button data-form-url="{% url 'compensation:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -123,7 +123,7 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
{% for user in obj.intervention.shared_users %}
|
{% for user in obj.intervention.shared_users %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-action' obj.id %}" title="{% trans 'Add new action' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-action' obj.id %}" title="{% trans 'Add new action' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'seedling' %}
|
{% fa5_icon 'seedling' %}
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Comment' %}
|
{% trans 'Comment' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -51,7 +51,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for detail in action.action_type_details.all %}
|
{% for detail in action.action_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.long_name }} > {{detail}}">{{ detail.parent.long_name }} > {{detail.long_name}}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -63,7 +63,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:acc:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
|
<button data-form-url="{% url 'compensation:acc:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
{% fa5_icon 'file-alt' %}
|
{% fa5_icon 'file-alt' %}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:acc:resubmission-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'compensation:acc:resubmission-create' obj.id %}">
|
||||||
{% fa5_icon 'bell' %}
|
{% fa5_icon 'bell' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-deadline' obj.id %}" title="{% trans 'Add new deadline' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-deadline' obj.id %}" title="{% trans 'Add new deadline' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'calendar-check' %}
|
{% fa5_icon 'calendar-check' %}
|
||||||
@ -58,7 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:acc:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
|
<button data-form-url="{% url 'compensation:acc:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -61,7 +61,7 @@
|
|||||||
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
|
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
|
||||||
<td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
|
<td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared or is_default_member and user in deduction.intervention.shared_users %}
|
{% if is_default_member and has_access or is_default_member and user in deduction.intervention.shared_users %}
|
||||||
<button data-form-url="{% url 'compensation:acc:edit-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
|
<button data-form-url="{% url 'compensation:acc:edit-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'file' %}
|
{% fa5_icon 'file' %}
|
||||||
@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:acc:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
<button data-form-url="{% url 'compensation:acc:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-state' obj.id %}" title="{% trans 'Add new state after' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-state' obj.id %}" title="{% trans 'Add new state after' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'layer-group' %}
|
{% fa5_icon 'layer-group' %}
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Surface' %}
|
{% trans 'Surface' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -51,14 +51,14 @@
|
|||||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||||
<br>
|
<br>
|
||||||
{% for detail in state.biotope_type_details.all %}
|
{% for detail in state.biotope_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.short_name }} > {{detail}}">{{ detail.parent.short_name }} > {{ detail.long_name }}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
<button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-state' obj.id %}?before=true" title="{% trans 'Add new state before' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-state' obj.id %}?before=true" title="{% trans 'Add new state before' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'layer-group' %}
|
{% fa5_icon 'layer-group' %}
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Surface' %}
|
{% trans 'Surface' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -51,14 +51,14 @@
|
|||||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||||
<br>
|
<br>
|
||||||
{% for detail in state.biotope_type_details.all %}
|
{% for detail in state.biotope_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.short_name }} > {{detail}}">{{ detail.parent.short_name }} > {{ detail.long_name }}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
<button data-form-url="{% url 'compensation:acc:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -101,7 +101,7 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
{% for user in obj.users.all %}
|
{% for user in obj.users.all %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
post_data = {
|
post_data = {
|
||||||
"identifier": test_id,
|
"identifier": test_id,
|
||||||
"title": test_title,
|
"title": test_title,
|
||||||
"output": geom_json,
|
"geom": geom_json,
|
||||||
"intervention": self.intervention.id,
|
"intervention": self.intervention.id,
|
||||||
}
|
}
|
||||||
pre_creation_intervention_log_count = self.intervention.log.count()
|
pre_creation_intervention_log_count = self.intervention.log.count()
|
||||||
@ -94,7 +94,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
post_data = {
|
post_data = {
|
||||||
"identifier": test_id,
|
"identifier": test_id,
|
||||||
"title": test_title,
|
"title": test_title,
|
||||||
"output": geom_json,
|
"geom": geom_json,
|
||||||
}
|
}
|
||||||
pre_creation_intervention_log_count = self.intervention.log.count()
|
pre_creation_intervention_log_count = self.intervention.log.count()
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
"title": new_title,
|
"title": new_title,
|
||||||
"intervention": self.intervention.id, # just keep the intervention as it is
|
"intervention": self.intervention.id, # just keep the intervention as it is
|
||||||
"comment": new_comment,
|
"comment": new_comment,
|
||||||
"output": geojson,
|
"geom": geojson,
|
||||||
}
|
}
|
||||||
self.client_user.post(url, post_data)
|
self.client_user.post(url, post_data)
|
||||||
self.compensation.refresh_from_db()
|
self.compensation.refresh_from_db()
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
post_data = {
|
post_data = {
|
||||||
"identifier": test_id,
|
"identifier": test_id,
|
||||||
"title": test_title,
|
"title": test_title,
|
||||||
"output": geom_json,
|
"geom": geom_json,
|
||||||
"surface": test_deductable_surface,
|
"surface": test_deductable_surface,
|
||||||
"conservation_office": test_conservation_office.id
|
"conservation_office": test_conservation_office.id
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
new_title = self.create_dummy_string()
|
new_title = self.create_dummy_string()
|
||||||
new_identifier = self.create_dummy_string()
|
new_identifier = self.create_dummy_string()
|
||||||
new_comment = self.create_dummy_string()
|
new_comment = self.create_dummy_string()
|
||||||
new_geometry = self.create_dummy_geometry()
|
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
|
||||||
test_conservation_office = self.get_conservation_office_code()
|
test_conservation_office = self.get_conservation_office_code()
|
||||||
test_deductable_surface = self.eco_account.deductable_surface + 100
|
test_deductable_surface = self.eco_account.deductable_surface + 100
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
"identifier": new_identifier,
|
"identifier": new_identifier,
|
||||||
"title": new_title,
|
"title": new_title,
|
||||||
"comment": new_comment,
|
"comment": new_comment,
|
||||||
"output": self.create_geojson(new_geometry),
|
"geom": new_geometry.geojson,
|
||||||
"surface": test_deductable_surface,
|
"surface": test_deductable_surface,
|
||||||
"conservation_office": test_conservation_office.id
|
"conservation_office": test_conservation_office.id
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
|||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
|
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
|
||||||
RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
|
RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
|
||||||
COMPENSATION_ADDED_TEMPLATE, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
COMPENSATION_ADDED_TEMPLATE, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -103,19 +103,11 @@ def new_view(request: HttpRequest, intervention_id: str = None):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
|
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("compensation:detail", id=comp.id)
|
return redirect("compensation:detail", id=comp.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
@ -187,19 +179,11 @@ def edit_view(request: HttpRequest, id: str):
|
|||||||
if intervention_is_checked:
|
if intervention_is_checked:
|
||||||
messages.info(request, CHECK_STATE_RESET)
|
messages.info(request, CHECK_STATE_RESET)
|
||||||
messages.success(request, _("Compensation {} edited").format(comp.identifier))
|
messages.success(request, _("Compensation {} edited").format(comp.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("compensation:detail", id=comp.id)
|
return redirect("compensation:detail", id=comp.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
@ -275,7 +259,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
"last_checked_tooltip": last_checked_tooltip,
|
"last_checked_tooltip": last_checked_tooltip,
|
||||||
"geom_form": geom_form,
|
"geom_form": geom_form,
|
||||||
"parcels": parcels,
|
"parcels": parcels,
|
||||||
"is_entry_shared": is_data_shared,
|
"has_access": is_data_shared,
|
||||||
"actions": actions,
|
"actions": actions,
|
||||||
"before_states": before_states,
|
"before_states": before_states,
|
||||||
"after_states": after_states,
|
"after_states": after_states,
|
||||||
|
|||||||
@ -12,12 +12,11 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from compensation.models import Compensation
|
from compensation.models import Compensation
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import uuid_required
|
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.generators import generate_qr_code
|
from konova.utils.generators import generate_qr_code
|
||||||
|
|
||||||
@uuid_required
|
|
||||||
def report_view(request: HttpRequest, id: str):
|
def report_view(request: HttpRequest, id: str):
|
||||||
""" Renders the public report view
|
""" Renders the public report view
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ def report_view(request: HttpRequest, id: str):
|
|||||||
"img": qrcode_img_lanis,
|
"img": qrcode_img_lanis,
|
||||||
"url": qrcode_lanis_url,
|
"url": qrcode_lanis_url,
|
||||||
},
|
},
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
"has_access": False, # disables action buttons during rendering
|
||||||
"before_states": before_states,
|
"before_states": before_states,
|
||||||
"after_states": after_states,
|
"after_states": after_states,
|
||||||
"geom_form": geom_form,
|
"geom_form": geom_form,
|
||||||
|
|||||||
@ -23,7 +23,7 @@ from konova.forms import SimpleGeomForm
|
|||||||
from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP
|
from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
|
from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
|
||||||
IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -84,19 +84,11 @@ def new_view(request: HttpRequest):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
messages.success(request, _("Eco-Account {} added").format(acc.identifier))
|
messages.success(request, _("Eco-Account {} added").format(acc.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("compensation:acc:detail", id=acc.id)
|
return redirect("compensation:acc:detail", id=acc.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
@ -164,19 +156,11 @@ def edit_view(request: HttpRequest, id: str):
|
|||||||
# The data form takes the geom form for processing, as well as the performing user
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
acc = data_form.save(request.user, geom_form)
|
acc = data_form.save(request.user, geom_form)
|
||||||
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
|
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("compensation:acc:detail", id=acc.id)
|
return redirect("compensation:acc:detail", id=acc.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
@ -253,7 +237,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
"obj": acc,
|
"obj": acc,
|
||||||
"geom_form": geom_form,
|
"geom_form": geom_form,
|
||||||
"parcels": parcels,
|
"parcels": parcels,
|
||||||
"is_entry_shared": is_data_shared,
|
"has_access": is_data_shared,
|
||||||
"before_states": before_states,
|
"before_states": before_states,
|
||||||
"after_states": after_states,
|
"after_states": after_states,
|
||||||
"sum_before_states": sum_before_states,
|
"sum_before_states": sum_before_states,
|
||||||
|
|||||||
@ -12,13 +12,11 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import uuid_required
|
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.generators import generate_qr_code
|
from konova.utils.generators import generate_qr_code
|
||||||
|
|
||||||
|
|
||||||
@uuid_required
|
|
||||||
def report_view(request: HttpRequest, id: str):
|
def report_view(request: HttpRequest, id: str):
|
||||||
""" Renders the public report view
|
""" Renders the public report view
|
||||||
|
|
||||||
@ -75,7 +73,7 @@ def report_view(request: HttpRequest, id: str):
|
|||||||
"img": qrcode_img_lanis,
|
"img": qrcode_img_lanis,
|
||||||
"url": qrcode_lanis_url,
|
"url": qrcode_lanis_url,
|
||||||
},
|
},
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
"has_access": False, # disables action buttons during rendering
|
||||||
"before_states": before_states,
|
"before_states": before_states,
|
||||||
"after_states": after_states,
|
"after_states": after_states,
|
||||||
"geom_form": geom_form,
|
"geom_form": geom_form,
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-action' obj.id %}" title="{% trans 'Add new action' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-action' obj.id %}" title="{% trans 'Add new action' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'seedling' %}
|
{% fa5_icon 'seedling' %}
|
||||||
@ -49,7 +49,7 @@
|
|||||||
<hr>
|
<hr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for detail in action.action_type_details.all %}
|
{% for detail in action.action_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.long_name }} > {{detail}}">{{ detail.parent.long_name }} > {{detail.long_name}}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -61,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'ema:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
|
<button data-form-url="{% url 'ema:action-edit' obj.id action.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit action' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
{% fa5_icon 'file-alt' %}
|
{% fa5_icon 'file-alt' %}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
|
||||||
{% fa5_icon 'bell' %}
|
{% fa5_icon 'bell' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-deadline' obj.id %}" title="{% trans 'Add new deadline' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-deadline' obj.id %}" title="{% trans 'Add new deadline' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'calendar-check' %}
|
{% fa5_icon 'calendar-check' %}
|
||||||
@ -58,7 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'ema:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
|
<button data-form-url="{% url 'ema:deadline-edit' obj.id deadline.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit deadline' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'file' %}
|
{% fa5_icon 'file' %}
|
||||||
@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'ema:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
<button data-form-url="{% url 'ema:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-state' obj.id %}" title="{% trans 'Add new state after' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-state' obj.id %}" title="{% trans 'Add new state after' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'layer-group' %}
|
{% fa5_icon 'layer-group' %}
|
||||||
@ -49,14 +49,14 @@
|
|||||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||||
<br>
|
<br>
|
||||||
{% for detail in state.biotope_type_details.all %}
|
{% for detail in state.biotope_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.short_name }} > {{detail}}">{{ detail.parent.short_name }} > {{ detail.long_name }}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
<button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-state' obj.id %}?before=true" title="{% trans 'Add new state before' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'ema:new-state' obj.id %}?before=true" title="{% trans 'Add new state before' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'layer-group' %}
|
{% fa5_icon 'layer-group' %}
|
||||||
@ -49,14 +49,14 @@
|
|||||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||||
<br>
|
<br>
|
||||||
{% for detail in state.biotope_type_details.all %}
|
{% for detail in state.biotope_type_details.all %}
|
||||||
<span class="badge badge-pill rlp-r" title="{{ detail.parent.short_name }} > {{detail}}">{{ detail.parent.short_name }} > {{ detail.long_name }}</span>
|
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
<button data-form-url="{% url 'ema:state-edit' obj.id state.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit state' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -87,7 +87,7 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
{% for user in obj.users.all %}
|
{% for user in obj.users.all %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
post_data = {
|
post_data = {
|
||||||
"identifier": test_id,
|
"identifier": test_id,
|
||||||
"title": test_title,
|
"title": test_title,
|
||||||
"output": geom_json,
|
"geom": geom_json,
|
||||||
"conservation_office": test_conservation_office.id
|
"conservation_office": test_conservation_office.id
|
||||||
}
|
}
|
||||||
self.client_user.post(new_url, post_data)
|
self.client_user.post(new_url, post_data)
|
||||||
@ -84,7 +84,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
new_title = self.create_dummy_string()
|
new_title = self.create_dummy_string()
|
||||||
new_identifier = self.create_dummy_string()
|
new_identifier = self.create_dummy_string()
|
||||||
new_comment = self.create_dummy_string()
|
new_comment = self.create_dummy_string()
|
||||||
new_geometry = self.create_dummy_geometry() # Create an empty geometry
|
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
|
||||||
test_conservation_office = self.get_conservation_office_code()
|
test_conservation_office = self.get_conservation_office_code()
|
||||||
|
|
||||||
check_on_elements = {
|
check_on_elements = {
|
||||||
@ -99,7 +99,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
"identifier": new_identifier,
|
"identifier": new_identifier,
|
||||||
"title": new_title,
|
"title": new_title,
|
||||||
"comment": new_comment,
|
"comment": new_comment,
|
||||||
"output": self.create_geojson(new_geometry),
|
"geom": new_geometry.geojson,
|
||||||
"conservation_office": test_conservation_office.id
|
"conservation_office": test_conservation_office.id
|
||||||
}
|
}
|
||||||
self.client_user.post(url, post_data)
|
self.client_user.post(url, post_data)
|
||||||
|
|||||||
@ -48,7 +48,7 @@ class NewEmaFormTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
geom_form_data = json.loads(geom_form_data)
|
geom_form_data = json.loads(geom_form_data)
|
||||||
geom_form_data = {
|
geom_form_data = {
|
||||||
"output": json.dumps(geom_form_data)
|
"geom": json.dumps(geom_form_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
geom_form = SimpleGeomForm(geom_form_data)
|
geom_form = SimpleGeomForm(geom_form_data)
|
||||||
@ -116,7 +116,7 @@ class EditEmaFormTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
geom_form_data = json.loads(geom_form_data)
|
geom_form_data = json.loads(geom_form_data)
|
||||||
geom_form_data = {
|
geom_form_data = {
|
||||||
"output": json.dumps(geom_form_data)
|
"geom": json.dumps(geom_form_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
geom_form = SimpleGeomForm(geom_form_data)
|
geom_form = SimpleGeomForm(geom_form_data)
|
||||||
|
|||||||
@ -24,7 +24,7 @@ from konova.forms.modals import RemoveModalForm
|
|||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
|
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
|
||||||
DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -84,17 +84,11 @@ def new_view(request: HttpRequest):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
messages.success(request, _("EMA {} added").format(ema.identifier))
|
messages.success(request, _("EMA {} added").format(ema.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("ema:detail", id=ema.id)
|
return redirect("ema:detail", id=ema.id)
|
||||||
else:
|
else:
|
||||||
@ -148,7 +142,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
geom_form = SimpleGeomForm(instance=ema)
|
geom_form = SimpleGeomForm(instance=ema)
|
||||||
parcels = ema.get_underlying_parcels()
|
parcels = ema.get_underlying_parcels()
|
||||||
_user = request.user
|
_user = request.user
|
||||||
is_entry_shared = ema.is_shared_with(_user)
|
is_data_shared = ema.is_shared_with(_user)
|
||||||
|
|
||||||
# Order states according to surface
|
# Order states according to surface
|
||||||
before_states = ema.before_states.all().order_by("-surface")
|
before_states = ema.before_states.all().order_by("-surface")
|
||||||
@ -173,7 +167,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
"obj": ema,
|
"obj": ema,
|
||||||
"geom_form": geom_form,
|
"geom_form": geom_form,
|
||||||
"parcels": parcels,
|
"parcels": parcels,
|
||||||
"is_entry_shared": is_entry_shared,
|
"has_access": is_data_shared,
|
||||||
"before_states": before_states,
|
"before_states": before_states,
|
||||||
"after_states": after_states,
|
"after_states": after_states,
|
||||||
"sum_before_states": sum_before_states,
|
"sum_before_states": sum_before_states,
|
||||||
@ -221,19 +215,11 @@ def edit_view(request: HttpRequest, id: str):
|
|||||||
# The data form takes the geom form for processing, as well as the performing user
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
ema = data_form.save(request.user, geom_form)
|
ema = data_form.save(request.user, geom_form)
|
||||||
messages.success(request, _("EMA {} edited").format(ema.identifier))
|
messages.success(request, _("EMA {} edited").format(ema.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("ema:detail", id=ema.id)
|
return redirect("ema:detail", id=ema.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
|
|||||||
@ -12,12 +12,11 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from ema.models import Ema
|
from ema.models import Ema
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import uuid_required
|
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.generators import generate_qr_code
|
from konova.utils.generators import generate_qr_code
|
||||||
|
|
||||||
@uuid_required
|
|
||||||
def report_view(request:HttpRequest, id: str):
|
def report_view(request:HttpRequest, id: str):
|
||||||
""" Renders the public report view
|
""" Renders the public report view
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ def report_view(request:HttpRequest, id: str):
|
|||||||
"img": qrcode_img_lanis,
|
"img": qrcode_img_lanis,
|
||||||
"url": qrcode_lanis_url
|
"url": qrcode_lanis_url
|
||||||
},
|
},
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
"has_access": False, # disables action buttons during rendering
|
||||||
"before_states": before_states,
|
"before_states": before_states,
|
||||||
"after_states": after_states,
|
"after_states": after_states,
|
||||||
"geom_form": geom_form,
|
"geom_form": geom_form,
|
||||||
|
|||||||
@ -5,8 +5,6 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 30.11.20
|
Created on: 30.11.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from konova.sub_settings.django_settings import env
|
|
||||||
|
|
||||||
INTERVENTION_IDENTIFIER_LENGTH = 6
|
INTERVENTION_IDENTIFIER_LENGTH = 6
|
||||||
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
|
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
|
||||||
|
|
||||||
@ -16,7 +14,7 @@ INTERVENTION_LANIS_LAYER_NAME_UNRECORDED_OLD_ENTRY = "eiv_unrecorded_old_entries
|
|||||||
|
|
||||||
# EGON connection settings via rabbitmq
|
# EGON connection settings via rabbitmq
|
||||||
# NEEDED FOR BACKWARDS COMPATIBILITY
|
# NEEDED FOR BACKWARDS COMPATIBILITY
|
||||||
EGON_RABBITMQ_HOST = env("EGON_RABBITMQ_HOST")
|
EGON_RABBITMQ_HOST = "CHANGE_ME"
|
||||||
EGON_RABBITMQ_PORT = env("EGON_RABBITMQ_PORT")
|
EGON_RABBITMQ_PORT = "CHANGE_ME"
|
||||||
EGON_RABBITMQ_USER = env("EGON_RABBITMQ_USER")
|
EGON_RABBITMQ_USER = "CHANGE_ME"
|
||||||
EGON_RABBITMQ_PW = env("EGON_RABBITMQ_PW")
|
EGON_RABBITMQ_PW = "CHANGE_ME"
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<a href="{% url 'compensation:new' obj.id %}" title="{% trans 'Add new compensation' %}">
|
<a href="{% url 'compensation:new' obj.id %}" title="{% trans 'Add new compensation' %}">
|
||||||
<button class="btn btn-outline-default">
|
<button class="btn btn-outline-default">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
@ -32,7 +32,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Title' %}
|
{% trans 'Title' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -51,7 +51,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="align-middle">{{ comp.title }}</td>
|
<td class="align-middle">{{ comp.title }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'intervention:remove-compensation' obj.id comp.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove compensation' %}">
|
<button data-form-url="{% url 'intervention:remove-compensation' obj.id comp.id %}" class="btn btn-default btn-modal float-right" title="{% trans 'Remove compensation' %}">
|
||||||
{% fa5_icon 'trash' %}
|
{% fa5_icon 'trash' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
{% fa5_icon 'file-alt' %}
|
{% fa5_icon 'file-alt' %}
|
||||||
</button>
|
</button>
|
||||||
</a>
|
</a>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'intervention:resubmission-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'intervention:resubmission-create' obj.id %}">
|
||||||
{% fa5_icon 'bell' %}
|
{% fa5_icon 'bell' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:new-deduction' obj.id %}" title="{% trans 'Add new deduction' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:new-deduction' obj.id %}" title="{% trans 'Add new deduction' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'tree' %}
|
{% fa5_icon 'tree' %}
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Created' %}
|
{% trans 'Created' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -56,7 +56,7 @@
|
|||||||
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
|
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
|
||||||
<td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
|
<td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'intervention:edit-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
|
<button data-form-url="{% url 'intervention:edit-deduction' obj.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:new-doc' obj.id %}" title="{% trans 'Add new document' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'file' %}
|
{% fa5_icon 'file' %}
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Comment' %}
|
{% trans 'Comment' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -66,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'intervention:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
<button data-form-url="{% url 'intervention:edit-doc' obj.id doc.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit document' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:pay:new' obj.id %}" title="{% trans 'Add new payment' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:pay:new' obj.id %}" title="{% trans 'Add new payment' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'money-bill-wave' %}
|
{% fa5_icon 'money-bill-wave' %}
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<th class="w-50" scope="col">
|
<th class="w-50" scope="col">
|
||||||
{% trans 'Comment' %}
|
{% trans 'Comment' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -46,24 +46,16 @@
|
|||||||
{% for pay in obj.payments.all %}
|
{% for pay in obj.payments.all %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% if is_entry_shared %}
|
{{ pay.amount|floatformat:2 }} €
|
||||||
{{ pay.amount|floatformat:2 }} €
|
|
||||||
{% else %}
|
|
||||||
***
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">{{ pay.due_on|default_if_none:"---" }}</td>
|
<td class="align-middle">{{ pay.due_on|default_if_none:"---" }}</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
<div class="scroll-150">
|
<div class="scroll-150">
|
||||||
{% if is_entry_shared %}
|
{{ pay.comment }}
|
||||||
{{ pay.comment }}
|
|
||||||
{% else %}
|
|
||||||
{% trans 'This data is not shared with you' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'compensation:pay:edit' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit payment' %}">
|
<button data-form-url="{% url 'compensation:pay:edit' obj.id pay.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit payment' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
{% comment %}
|
{% comment %}
|
||||||
Only show add-button if no revocation exists, yet.
|
Only show add-button if no revocation exists, yet.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% if is_default_member and is_entry_shared and not obj.legal.revocation %}
|
{% if is_default_member and has_access and not obj.legal.revocation %}
|
||||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:new-revocation' obj.id %}" title="{% trans 'Add revocation' %}">
|
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:new-revocation' obj.id %}" title="{% trans 'Add revocation' %}">
|
||||||
{% fa5_icon 'plus' %}
|
{% fa5_icon 'plus' %}
|
||||||
{% fa5_icon 'ban' %}
|
{% fa5_icon 'ban' %}
|
||||||
@ -36,7 +36,7 @@
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
{% trans 'Comment' %}
|
{% trans 'Comment' %}
|
||||||
</th>
|
</th>
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<th class="w-10" scope="col">
|
<th class="w-10" scope="col">
|
||||||
<span class="float-right">
|
<span class="float-right">
|
||||||
{% trans 'Action' %}
|
{% trans 'Action' %}
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle float-right">
|
<td class="align-middle float-right">
|
||||||
{% if is_default_member and is_entry_shared %}
|
{% if is_default_member and has_access %}
|
||||||
<button data-form-url="{% url 'intervention:edit-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit revocation' %}">
|
<button data-form-url="{% url 'intervention:edit-revocation' obj.id rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit revocation' %}">
|
||||||
{% fa5_icon 'edit' %}
|
{% fa5_icon 'edit' %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -129,7 +129,7 @@
|
|||||||
{% include 'user/includes/team_data_modal_button.html' %}
|
{% include 'user/includes/team_data_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<hr>
|
<hr>
|
||||||
{% if is_entry_shared %}
|
{% if has_access %}
|
||||||
{% for user in obj.users.all %}
|
{% for user in obj.users.all %}
|
||||||
{% include 'user/includes/contact_modal_button.html' %}
|
{% include 'user/includes/contact_modal_button.html' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
post_data = {
|
post_data = {
|
||||||
"identifier": test_id,
|
"identifier": test_id,
|
||||||
"title": test_title,
|
"title": test_title,
|
||||||
"output": geom_json,
|
"geom": geom_json,
|
||||||
}
|
}
|
||||||
response = self.client_user.post(
|
response = self.client_user.post(
|
||||||
new_url,
|
new_url,
|
||||||
|
|||||||
@ -62,7 +62,7 @@ class NewInterventionFormTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
geom_form_data = json.loads(geom_form_data)
|
geom_form_data = json.loads(geom_form_data)
|
||||||
geom_form_data = {
|
geom_form_data = {
|
||||||
"output": json.dumps(geom_form_data)
|
"geom": json.dumps(geom_form_data)
|
||||||
}
|
}
|
||||||
geom_form = SimpleGeomForm(geom_form_data)
|
geom_form = SimpleGeomForm(geom_form_data)
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ class EditInterventionFormTestCase(NewInterventionFormTestCase):
|
|||||||
)
|
)
|
||||||
geom_form_data = json.loads(geom_form_data)
|
geom_form_data = json.loads(geom_form_data)
|
||||||
geom_form_data = {
|
geom_form_data = {
|
||||||
"output": json.dumps(geom_form_data)
|
"geom": json.dumps(geom_form_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
geom_form = SimpleGeomForm(geom_form_data)
|
geom_form = SimpleGeomForm(geom_form_data)
|
||||||
@ -124,7 +124,7 @@ class EditInterventionFormTestCase(NewInterventionFormTestCase):
|
|||||||
self.assertIsNotNone(obj.responsible.handler)
|
self.assertIsNotNone(obj.responsible.handler)
|
||||||
self.assertEqual(obj.title, data["title"])
|
self.assertEqual(obj.title, data["title"])
|
||||||
self.assertEqual(obj.comment, data["comment"])
|
self.assertEqual(obj.comment, data["comment"])
|
||||||
self.assert_equal_geometries(test_geom, obj.geometry.geom)
|
self.assertTrue(test_geom.equals_exact(obj.geometry.geom, 0.000001))
|
||||||
|
|
||||||
self.assertEqual(obj.legal.binding_date, today)
|
self.assertEqual(obj.legal.binding_date, today)
|
||||||
self.assertEqual(obj.legal.registration_date, today)
|
self.assertEqual(obj.legal.registration_date, today)
|
||||||
|
|||||||
@ -23,8 +23,7 @@ from konova.forms.modals import RemoveModalForm
|
|||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
|
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
|
||||||
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED, \
|
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
GEOMETRIES_IGNORED_TEMPLATE
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -89,19 +88,11 @@ def new_view(request: HttpRequest):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
messages.success(request, _("Intervention {} added").format(intervention.identifier))
|
messages.success(request, _("Intervention {} added").format(intervention.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("intervention:detail", id=intervention.id)
|
return redirect("intervention:detail", id=intervention.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
@ -194,7 +185,7 @@ def detail_view(request: HttpRequest, id: str):
|
|||||||
"last_checked": last_checked,
|
"last_checked": last_checked,
|
||||||
"last_checked_tooltip": last_checked_tooltip,
|
"last_checked_tooltip": last_checked_tooltip,
|
||||||
"compensations": compensations,
|
"compensations": compensations,
|
||||||
"is_entry_shared": is_data_shared,
|
"has_access": is_data_shared,
|
||||||
"geom_form": geom_form,
|
"geom_form": geom_form,
|
||||||
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
"is_zb_member": _user.in_group(ZB_GROUP),
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
@ -245,19 +236,11 @@ def edit_view(request: HttpRequest, id: str):
|
|||||||
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
|
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
|
||||||
if intervention_is_checked:
|
if intervention_is_checked:
|
||||||
messages.info(request, CHECK_STATE_RESET)
|
messages.info(request, CHECK_STATE_RESET)
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect("intervention:detail", id=intervention.id)
|
return redirect("intervention:detail", id=intervention.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from celery import Celery
|
from celery import Celery
|
||||||
from konova.sub_settings.django_settings import env
|
|
||||||
|
|
||||||
# Set the default Django settings module for the 'celery' program.
|
# Set the default Django settings module for the 'celery' program.
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'konova.settings')
|
||||||
@ -18,7 +17,7 @@ app.config_from_object('django.conf:settings', namespace='CELERY')
|
|||||||
app.autodiscover_tasks()
|
app.autodiscover_tasks()
|
||||||
|
|
||||||
# Declare redis as broker
|
# Declare redis as broker
|
||||||
app.conf.broker_url = f'redis://{env("REDIS_HOST")}:{env.int("REDIS_PORT")}/0'
|
app.conf.broker_url = 'redis://localhost:6379/0'
|
||||||
|
|
||||||
|
|
||||||
@app.task(bind=True)
|
@app.task(bind=True)
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from bootstrap_modal_forms.mixins import is_ajax
|
from bootstrap_modal_forms.mixins import is_ajax
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import BadRequest
|
from django.http import Http404
|
||||||
from django.shortcuts import redirect, get_object_or_404, render
|
from django.shortcuts import redirect, get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -185,7 +185,7 @@ def uuid_required(function):
|
|||||||
try:
|
try:
|
||||||
uuid = UUID(uuid)
|
uuid = UUID(uuid)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise BadRequest(
|
raise Http404(
|
||||||
"Invalid UUID"
|
"Invalid UUID"
|
||||||
)
|
)
|
||||||
return function(request, *args, **kwargs)
|
return function(request, *args, **kwargs)
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
|
||||||
Created on: 19.08.24
|
|
||||||
|
|
||||||
"""
|
|
||||||
import django_filters
|
|
||||||
from django import forms
|
|
||||||
from django.db.models import QuerySet, Q
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class UserLoggedTableFilterMixin(django_filters.FilterSet):
|
|
||||||
ul = django_filters.CharFilter(
|
|
||||||
method="filter_user_log",
|
|
||||||
label=_(""),
|
|
||||||
label_suffix=_(""),
|
|
||||||
widget=forms.TextInput(
|
|
||||||
attrs={
|
|
||||||
"placeholder": _("Logged user"),
|
|
||||||
"title": _("Search for entries where this person has been participated according to log history"),
|
|
||||||
"class": "form-control",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
def filter_user_log(self, queryset, name, value) -> QuerySet:
|
|
||||||
""" Filters queryset depending on value of input
|
|
||||||
|
|
||||||
Args:
|
|
||||||
queryset (QuerySet): Incoming (prefiltered) queryset
|
|
||||||
name (str): Name of input field
|
|
||||||
value (str): Value of input field
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
value = value.replace(",", " ")
|
|
||||||
value = value.strip()
|
|
||||||
values = value.split(" ")
|
|
||||||
|
|
||||||
q = Q()
|
|
||||||
for val in values:
|
|
||||||
q &= (
|
|
||||||
Q(log__user__username__icontains=val) |
|
|
||||||
Q(log__user__first_name__icontains=val) |
|
|
||||||
Q(log__user__last_name__icontains=val)
|
|
||||||
)
|
|
||||||
|
|
||||||
queryset = queryset.filter(q)
|
|
||||||
return queryset
|
|
||||||
@ -14,7 +14,6 @@ from konova.filters.mixins.office import ConservationOfficeTableFilterMixin, Reg
|
|||||||
from konova.filters.mixins.record import RecordableTableFilterMixin
|
from konova.filters.mixins.record import RecordableTableFilterMixin
|
||||||
from konova.filters.mixins.self_created import SelfCreatedTableFilterMixin
|
from konova.filters.mixins.self_created import SelfCreatedTableFilterMixin
|
||||||
from konova.filters.mixins.share import ShareableTableFilterMixin
|
from konova.filters.mixins.share import ShareableTableFilterMixin
|
||||||
from konova.filters.mixins.user_log import UserLoggedTableFilterMixin
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractTableFilter(django_filters.FilterSet):
|
class AbstractTableFilter(django_filters.FilterSet):
|
||||||
@ -41,8 +40,7 @@ class SelectionTableFilter(RegistrationOfficeTableFilterMixin,
|
|||||||
|
|
||||||
class QueryTableFilter(KeywordTableFilterMixin,
|
class QueryTableFilter(KeywordTableFilterMixin,
|
||||||
FileNumberTableFilterMixin,
|
FileNumberTableFilterMixin,
|
||||||
GeoReferencedTableFilterMixin,
|
GeoReferencedTableFilterMixin):
|
||||||
UserLoggedTableFilterMixin):
|
|
||||||
""" TableFilter holding different filter options for query related filtering
|
""" TableFilter holding different filter options for query related filtering
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -25,16 +25,15 @@ class SimpleGeomForm(BaseForm):
|
|||||||
""" A geometry form for rendering geometry read-only using a widget
|
""" A geometry form for rendering geometry read-only using a widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
read_only: bool = True
|
read_only = True
|
||||||
_geometry_simplified: bool = False
|
geometry_simplified = False
|
||||||
output = JSONField(
|
geom = JSONField(
|
||||||
label=_("Geometry"),
|
label=_("Geometry"),
|
||||||
help_text=_(""),
|
help_text=_(""),
|
||||||
label_suffix="",
|
label_suffix="",
|
||||||
required=False,
|
required=False,
|
||||||
disabled=False,
|
disabled=False,
|
||||||
)
|
)
|
||||||
_num_geometries_ignored: int = 0
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.read_only = kwargs.pop("read_only", True)
|
self.read_only = kwargs.pop("read_only", True)
|
||||||
@ -49,33 +48,33 @@ class SimpleGeomForm(BaseForm):
|
|||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
||||||
self._set_geojson_properties(geojson, title=self.instance.identifier or None)
|
|
||||||
geom = json.dumps(geojson)
|
geom = json.dumps(geojson)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||||
geom = ""
|
geom = ""
|
||||||
self.empty = True
|
self.empty = True
|
||||||
|
|
||||||
self.initialize_form_field("output", geom)
|
self.initialize_form_field("geom", geom)
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
super().is_valid()
|
super().is_valid()
|
||||||
is_valid = True
|
is_valid = True
|
||||||
|
|
||||||
# Get geojson from form
|
# Get geojson from form
|
||||||
geom = self.data.get("output", None)
|
geom = self.data["geom"]
|
||||||
if geom is None or len(geom) == 0:
|
if geom is None or len(geom) == 0:
|
||||||
# empty geometry is a valid geometry
|
# empty geometry is a valid geometry
|
||||||
self.cleaned_data["output"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
|
self.cleaned_data["geom"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
|
||||||
return is_valid
|
return is_valid
|
||||||
geom = json.loads(geom)
|
geom = json.loads(geom)
|
||||||
|
|
||||||
# Write submitted data back into form field to make sure invalid geometry
|
# Write submitted data back into form field to make sure invalid geometry
|
||||||
# will be rendered again on failed submit
|
# will be rendered again on failed submit
|
||||||
self.initialize_form_field("output", self.data["output"])
|
self.initialize_form_field("geom", self.data["geom"])
|
||||||
|
|
||||||
# Initialize features list with empty MultiPolygon, so that an empty input will result in a
|
# Read geojson into gdal geometry
|
||||||
# proper empty MultiPolygon object
|
# HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
|
||||||
|
# this case)
|
||||||
features = []
|
features = []
|
||||||
features_json = geom.get("features", [])
|
features_json = geom.get("features", [])
|
||||||
accepted_ogr_types = [
|
accepted_ogr_types = [
|
||||||
@ -98,41 +97,33 @@ class SimpleGeomForm(BaseForm):
|
|||||||
g = self.__flatten_geom_to_2D(g)
|
g = self.__flatten_geom_to_2D(g)
|
||||||
|
|
||||||
if g.geom_type not in accepted_ogr_types:
|
if g.geom_type not in accepted_ogr_types:
|
||||||
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
|
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||||
is_valid &= False
|
is_valid &= False
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
is_area_valid = self.__is_area_valid(g)
|
is_valid &= self.__is_area_valid(g)
|
||||||
if not is_area_valid:
|
|
||||||
# Geometries with an invalid size will not be saved to the db
|
|
||||||
# We assume these are malicious snippets which are not supposed to be in the geometry in the first place
|
|
||||||
self._num_geometries_ignored += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
g = Polygon.from_ewkt(g.ewkt)
|
polygon = Polygon.from_ewkt(g.ewkt)
|
||||||
is_valid &= g.valid
|
is_valid &= polygon.valid
|
||||||
if not g.valid:
|
if not polygon.valid:
|
||||||
self.add_error("output", g.valid_reason)
|
self.add_error("geom", polygon.valid_reason)
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
if isinstance(g, Polygon):
|
features.append(polygon)
|
||||||
features.append(g)
|
|
||||||
elif isinstance(g, MultiPolygon):
|
|
||||||
features.extend(list(g))
|
|
||||||
|
|
||||||
# Unionize all geometry features into one new MultiPolygon
|
# Unionize all geometry features into one new MultiPolygon
|
||||||
if features:
|
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
||||||
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
|
for feature in features:
|
||||||
else:
|
form_geom = form_geom.union(feature)
|
||||||
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
|
||||||
|
|
||||||
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
||||||
form_geom = Geometry.cast_to_multipolygon(form_geom)
|
if form_geom.geom_type != "MultiPolygon":
|
||||||
|
form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
# Write unioned Multipolygon into cleaned data
|
# Write unioned Multipolygon into cleaned data
|
||||||
if self.cleaned_data is None:
|
if self.cleaned_data is None:
|
||||||
self.cleaned_data = {}
|
self.cleaned_data = {}
|
||||||
self.cleaned_data["output"] = form_geom.ewkt
|
self.cleaned_data["geom"] = form_geom.ewkt
|
||||||
|
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
@ -142,7 +133,7 @@ class SimpleGeomForm(BaseForm):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
geom = self.cleaned_data.get("output")
|
geom = self.cleaned_data.get("geom")
|
||||||
g = gdal.OGRGeometry(geom, srs=DEFAULT_SRID_RLP)
|
g = gdal.OGRGeometry(geom, srs=DEFAULT_SRID_RLP)
|
||||||
num_vertices = g.num_coords
|
num_vertices = g.num_coords
|
||||||
|
|
||||||
@ -155,6 +146,15 @@ class SimpleGeomForm(BaseForm):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
is_area_valid = geom.area > 1 # > 1m² (SRID:25832)
|
is_area_valid = geom.area > 1 # > 1m² (SRID:25832)
|
||||||
|
|
||||||
|
if not is_area_valid:
|
||||||
|
self.add_error(
|
||||||
|
"geom",
|
||||||
|
_("Geometry must be greater than 1m². Currently is {}m²").format(
|
||||||
|
float(geom.area)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return is_area_valid
|
return is_area_valid
|
||||||
|
|
||||||
def __simplify_geometry(self, geom, max_vert: int):
|
def __simplify_geometry(self, geom, max_vert: int):
|
||||||
@ -192,14 +192,14 @@ class SimpleGeomForm(BaseForm):
|
|||||||
if self.instance is None or self.instance.geometry is None:
|
if self.instance is None or self.instance.geometry is None:
|
||||||
raise LookupError
|
raise LookupError
|
||||||
geometry = self.instance.geometry
|
geometry = self.instance.geometry
|
||||||
geometry.geom = self.cleaned_data.get("output", MultiPolygon(srid=DEFAULT_SRID_RLP))
|
geometry.geom = self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP))
|
||||||
geometry.modified = action
|
geometry.modified = action
|
||||||
|
|
||||||
geometry.save()
|
geometry.save()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
# No geometry or linked instance holding a geometry exist --> create a new one!
|
# No geometry or linked instance holding a geometry exist --> create a new one!
|
||||||
geometry = Geometry.objects.create(
|
geometry = Geometry.objects.create(
|
||||||
geom=self.cleaned_data.get("output", MultiPolygon(srid=DEFAULT_SRID_RLP)),
|
geom=self.cleaned_data.get("geom", MultiPolygon(srid=DEFAULT_SRID_RLP)),
|
||||||
created=action,
|
created=action,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -207,29 +207,13 @@ class SimpleGeomForm(BaseForm):
|
|||||||
if not is_vertices_num_valid:
|
if not is_vertices_num_valid:
|
||||||
geometry.geom = self.__simplify_geometry(geometry.geom, max_vert=GEOM_MAX_VERTICES)
|
geometry.geom = self.__simplify_geometry(geometry.geom, max_vert=GEOM_MAX_VERTICES)
|
||||||
geometry.save()
|
geometry.save()
|
||||||
self._geometry_simplified = True
|
self.geometry_simplified = True
|
||||||
|
|
||||||
# Start parcel update and geometry conflict checking procedure in a background process
|
# Start parcel update and geometry conflict checking procedure in a background process
|
||||||
celery_update_parcels.delay(geometry.id)
|
celery_update_parcels.delay(geometry.id)
|
||||||
celery_check_for_geometry_conflicts.delay(geometry.id)
|
celery_check_for_geometry_conflicts.delay(geometry.id)
|
||||||
return geometry
|
return geometry
|
||||||
|
|
||||||
def get_num_geometries_ignored(self):
|
|
||||||
""" Returns the number of geometries which had to be ignored for various reasons
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._num_geometries_ignored
|
|
||||||
|
|
||||||
def has_geometry_simplified(self):
|
|
||||||
""" Returns whether the geometry has been simplified or not.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._geometry_simplified
|
|
||||||
|
|
||||||
def __flatten_geom_to_2D(self, geom):
|
def __flatten_geom_to_2D(self, geom):
|
||||||
"""
|
"""
|
||||||
Enforces a given OGRGeometry from higher dimensions into 2D
|
Enforces a given OGRGeometry from higher dimensions into 2D
|
||||||
@ -239,20 +223,3 @@ class SimpleGeomForm(BaseForm):
|
|||||||
g_wkt = wkt_w.write(geom.geos).decode("utf-8")
|
g_wkt = wkt_w.write(geom.geos).decode("utf-8")
|
||||||
geom = gdal.OGRGeometry(g_wkt)
|
geom = gdal.OGRGeometry(g_wkt)
|
||||||
return geom
|
return geom
|
||||||
|
|
||||||
def _set_geojson_properties(self, geojson: dict, title: str = None):
|
|
||||||
""" Toggles the editable property of the geojson for proper handling in map client
|
|
||||||
|
|
||||||
Args:
|
|
||||||
geojson (dict): The GeoJson
|
|
||||||
title (str): An alternative title for the geometry
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
geojson (dict): The altered GeoJson
|
|
||||||
"""
|
|
||||||
features = geojson.get("features", [])
|
|
||||||
for feature in features:
|
|
||||||
feature["properties"]["editable"] = not self.read_only
|
|
||||||
if title:
|
|
||||||
feature["properties"]["title"] = title
|
|
||||||
return geojson
|
|
||||||
|
|||||||
@ -6,11 +6,11 @@ Created on: 26.10.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import zipfile
|
import zipfile
|
||||||
from datetime import datetime
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.datetime_safe import datetime
|
||||||
|
|
||||||
from analysis.utils.excel.excel import TempExcelFile
|
from analysis.utils.excel.excel import TempExcelFile
|
||||||
from analysis.utils.report import TimespanReport
|
from analysis.utils.report import TimespanReport
|
||||||
|
|||||||
@ -1,88 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
||||||
Created on: 04.01.22
|
|
||||||
|
|
||||||
"""
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.contrib.gis.db.models.functions import Area
|
|
||||||
|
|
||||||
from konova.management.commands.setup import BaseKonovaCommand
|
|
||||||
from konova.models import Geometry, ParcelIntersection
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseKonovaCommand):
|
|
||||||
help = "Recalculates parcels for entries with geometry but missing parcel information"
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument(
|
|
||||||
"--force-all",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="If Attribute set, all entries parcels will be recalculated"
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
try:
|
|
||||||
self.recalculate_parcels(options)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self._break_line()
|
|
||||||
exit(-1)
|
|
||||||
|
|
||||||
def recalculate_parcels(self, options: dict):
|
|
||||||
force_all = options.get("force_all", False)
|
|
||||||
|
|
||||||
geometry_objects = Geometry.objects.filter(
|
|
||||||
geom__isempty=False,
|
|
||||||
).exclude(
|
|
||||||
geom=None
|
|
||||||
)
|
|
||||||
|
|
||||||
if not force_all:
|
|
||||||
# Fetch all intersections
|
|
||||||
intersection_objs = ParcelIntersection.objects.filter(
|
|
||||||
geometry__in=geometry_objects
|
|
||||||
)
|
|
||||||
# Just take the geometry ids, which seem to have intersections
|
|
||||||
geom_with_intersection_ids = intersection_objs.values_list(
|
|
||||||
"geometry__id",
|
|
||||||
flat=True
|
|
||||||
)
|
|
||||||
# ... and resolve into Geometry objects again ...
|
|
||||||
intersected_geom_objs = Geometry.objects.filter(
|
|
||||||
id__in=geom_with_intersection_ids
|
|
||||||
)
|
|
||||||
# ... to be able to use the way more efficient difference() function ...
|
|
||||||
geometry_objects_ids = geometry_objects.difference(intersected_geom_objs).values_list("id", flat=True)
|
|
||||||
# ... so we can resolve these into proper Geometry objects again for further annotation usage
|
|
||||||
geometry_objects = Geometry.objects.filter(id__in=geometry_objects_ids)
|
|
||||||
|
|
||||||
self._write_warning("=== Update parcels and districts ===")
|
|
||||||
# Order geometries by size to process smaller once at first
|
|
||||||
geometries = geometry_objects.annotate(
|
|
||||||
area=Area("geom")
|
|
||||||
).order_by(
|
|
||||||
'area'
|
|
||||||
)
|
|
||||||
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
|
|
||||||
i = 0
|
|
||||||
num_geoms = geometries.count()
|
|
||||||
geoms_with_errors = {}
|
|
||||||
for geometry in geometries:
|
|
||||||
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
|
|
||||||
try:
|
|
||||||
geometry.update_parcels()
|
|
||||||
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
|
|
||||||
except Exception as e:
|
|
||||||
geoms_with_errors[geometry.id] = str(e)
|
|
||||||
i += 1
|
|
||||||
self._write_warning(f"--- {i}/{num_geoms} processed")
|
|
||||||
|
|
||||||
self._write_success("Updating parcels done!")
|
|
||||||
|
|
||||||
for key, val in geoms_with_errors.items():
|
|
||||||
self._write_error(f" Error on {key}: {val}")
|
|
||||||
self._write_success(f"{num_geoms - len(geoms_with_errors)} geometries successfuly recalculated!")
|
|
||||||
self._break_line()
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: ksp-servicestelle@sgdnord.rlp.de
|
|
||||||
Created on: 18.06.24
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
|
|
||||||
from intervention.models import Intervention
|
|
||||||
from konova.management.commands.setup import BaseKonovaCommand
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseKonovaCommand):
|
|
||||||
help = "Send specific intervention entries to EGON if there are any payments on them"
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
try:
|
|
||||||
parser.add_argument("--intervention-ids", type=str)
|
|
||||||
except ValueError as e:
|
|
||||||
self._write_error(f"Argument error: {e}")
|
|
||||||
exit(-1)
|
|
||||||
|
|
||||||
def __handle_arguments(self, options):
|
|
||||||
self.intervention_ids = options["intervention_ids"] or ""
|
|
||||||
self.intervention_ids = self.intervention_ids.split(",")
|
|
||||||
self.intervention_ids = [x.strip() for x in self.intervention_ids]
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
try:
|
|
||||||
self.__handle_arguments(options)
|
|
||||||
interventions = self.get_interventions()
|
|
||||||
self.process_egon_sending(interventions)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self._break_line()
|
|
||||||
exit(-1)
|
|
||||||
|
|
||||||
def get_interventions(self) -> QuerySet:
|
|
||||||
"""
|
|
||||||
Getter for interventions, defined by parameter 'intervention-ids'
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
interventions (QuerySet): The interventions
|
|
||||||
"""
|
|
||||||
interventions = Intervention.objects.filter(
|
|
||||||
id__in=self.intervention_ids,
|
|
||||||
)
|
|
||||||
self._write_success(f"... Found {interventions.count()} interventions")
|
|
||||||
return interventions
|
|
||||||
|
|
||||||
def process_egon_sending(self, interventions: QuerySet):
|
|
||||||
for intervention in interventions:
|
|
||||||
intervention.send_data_to_egon()
|
|
||||||
self._write_warning(f"... {intervention.identifier} has been sent to EGON (if it has payments)")
|
|
||||||
51
konova/management/commands/test_identifier_generating.py
Normal file
51
konova/management/commands/test_identifier_generating.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 19.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
|
from intervention.models import Intervention
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Performs test on collisions using the identifier generation"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
identifiers = {}
|
||||||
|
max_iterations = 100000
|
||||||
|
try:
|
||||||
|
collisions = 0
|
||||||
|
len_ids = len(identifiers)
|
||||||
|
while len_ids < max_iterations:
|
||||||
|
tmp_intervention = Intervention()
|
||||||
|
_id = tmp_intervention.generate_new_identifier()
|
||||||
|
len_ids = len(identifiers)
|
||||||
|
if _id not in identifiers:
|
||||||
|
if len_ids % (max_iterations/5) == 0:
|
||||||
|
print(len_ids)
|
||||||
|
identifiers[_id] = None
|
||||||
|
else:
|
||||||
|
collisions += 1
|
||||||
|
print("+++ Collision after {} identifiers +++".format(len_ids))
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._break_line()
|
||||||
|
exit(-1)
|
||||||
|
print(
|
||||||
|
"\n{} collisions in {} identifiers; Collision rate {}%".format(
|
||||||
|
collisions,
|
||||||
|
len_ids,
|
||||||
|
(collisions / len_ids)*100,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _break_line(self):
|
||||||
|
""" Simply prints a line break
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write("\n")
|
||||||
54
konova/management/commands/update_all_parcels.py
Normal file
54
konova/management/commands/update_all_parcels.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 04.01.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.contrib.gis.db.models.functions import Area
|
||||||
|
|
||||||
|
from konova.management.commands.setup import BaseKonovaCommand
|
||||||
|
from konova.models import Geometry, Parcel, District
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseKonovaCommand):
|
||||||
|
help = "Checks the database' sanity and removes unused entries"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
self.update_all_parcels()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._break_line()
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
def update_all_parcels(self):
|
||||||
|
num_parcels_before = Parcel.objects.count()
|
||||||
|
num_districts_before = District.objects.count()
|
||||||
|
self._write_warning("=== Update parcels and districts ===")
|
||||||
|
# Order geometries by size to process smaller once at first
|
||||||
|
geometries = Geometry.objects.all().exclude(
|
||||||
|
geom=None
|
||||||
|
).annotate(area=Area("geom")).order_by(
|
||||||
|
'area'
|
||||||
|
)
|
||||||
|
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
|
||||||
|
i = 0
|
||||||
|
num_geoms = geometries.count()
|
||||||
|
for geometry in geometries:
|
||||||
|
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
|
||||||
|
geometry.update_parcels()
|
||||||
|
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
|
||||||
|
i += 1
|
||||||
|
self._write_warning(f"--- {i}/{num_geoms} processed")
|
||||||
|
|
||||||
|
num_parcels_after = Parcel.objects.count()
|
||||||
|
num_districts_after = District.objects.count()
|
||||||
|
if num_parcels_after != num_parcels_before:
|
||||||
|
self._write_error(f"Parcels have changed: {num_parcels_before} to {num_parcels_after} entries. You should run the sanitize command.")
|
||||||
|
if num_districts_after != num_districts_before:
|
||||||
|
self._write_error(f"Districts have changed: {num_districts_before} to {num_districts_after} entries. You should run the sanitize command.")
|
||||||
|
|
||||||
|
self._write_success("Updating parcels done!")
|
||||||
|
self._break_line()
|
||||||
@ -8,10 +8,9 @@ Created on: 15.11.21
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django.contrib.gis.db.models import MultiPolygonField
|
from django.contrib.gis.db.models import MultiPolygonField
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.gis.geos import MultiPolygon
|
|
||||||
|
|
||||||
from konova.models import BaseResource, UuidModel
|
from konova.models import BaseResource, UuidModel
|
||||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
@ -224,17 +223,6 @@ class Geometry(BaseResource):
|
|||||||
)
|
)
|
||||||
parcel_obj.updated_on = _now
|
parcel_obj.updated_on = _now
|
||||||
parcels_to_update.append(parcel_obj)
|
parcels_to_update.append(parcel_obj)
|
||||||
except MultipleObjectsReturned:
|
|
||||||
parcel_obj = Parcel.make_unique(
|
|
||||||
district=district,
|
|
||||||
municipal=municipal,
|
|
||||||
parcel_group=parcel_group,
|
|
||||||
flr=flr_val,
|
|
||||||
flrstck_nnr=flrstck_nnr,
|
|
||||||
flrstck_zhlr=flrstck_zhlr,
|
|
||||||
)
|
|
||||||
parcel_obj.updated_on = _now
|
|
||||||
parcels_to_update.append(parcel_obj)
|
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
# If not existing, create object but do not commit, yet
|
# If not existing, create object but do not commit, yet
|
||||||
parcel_obj = Parcel(
|
parcel_obj = Parcel(
|
||||||
@ -343,7 +331,6 @@ class Geometry(BaseResource):
|
|||||||
{
|
{
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
"geometry": json.loads(p.json),
|
"geometry": json.loads(p.json),
|
||||||
"properties": {},
|
|
||||||
}
|
}
|
||||||
for p in polygons
|
for p in polygons
|
||||||
]
|
]
|
||||||
@ -379,42 +366,13 @@ class Geometry(BaseResource):
|
|||||||
diff = geom_envelope - self.geom
|
diff = geom_envelope - self.geom
|
||||||
|
|
||||||
if diff.area == 0:
|
if diff.area == 0:
|
||||||
complexity_factor = 1
|
ratio = 1
|
||||||
else:
|
else:
|
||||||
complexity_factor = self.geom.area / diff.area
|
ratio = self.geom.area / diff.area
|
||||||
|
|
||||||
|
complexity_factor = 1 - ratio
|
||||||
return complexity_factor
|
return complexity_factor
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cast_to_multipolygon(input_geom):
|
|
||||||
""" If input_geom is not a MultiPolygon, cast to MultiPolygon
|
|
||||||
|
|
||||||
Args:
|
|
||||||
input_geom ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
output_geom
|
|
||||||
"""
|
|
||||||
output_geom = input_geom
|
|
||||||
if not isinstance(input_geom, MultiPolygon):
|
|
||||||
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
|
|
||||||
return output_geom
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cast_to_rlp_srid(input_geom):
|
|
||||||
""" If input_geom is not of RLP SRID (25832), cast to RLP SRID
|
|
||||||
|
|
||||||
Args:
|
|
||||||
input_geom ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
output_geom
|
|
||||||
"""
|
|
||||||
output_geom = input_geom
|
|
||||||
if output_geom.srid != DEFAULT_SRID_RLP:
|
|
||||||
output_geom.transform(DEFAULT_SRID_RLP)
|
|
||||||
return output_geom
|
|
||||||
|
|
||||||
|
|
||||||
class GeometryConflict(UuidModel):
|
class GeometryConflict(UuidModel):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -5,7 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 16.12.21
|
Created on: 16.12.21
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.db import models, transaction
|
from django.db import models
|
||||||
|
|
||||||
from konova.models import UuidModel
|
from konova.models import UuidModel
|
||||||
|
|
||||||
@ -158,46 +158,6 @@ class Parcel(UuidModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.parcel_group} | {self.flr} | {self.flrstck_zhlr} | {self.flrstck_nnr}"
|
return f"{self.parcel_group} | {self.flr} | {self.flrstck_zhlr} | {self.flrstck_nnr}"
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def make_unique(cls, **kwargs):
|
|
||||||
""" Checks for duplicates of a Parcel, choose a (now) unique one,
|
|
||||||
repairs relations for ParcelIntersection and removes duplicates.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
unique_true (Parcel): The new unique 'true one'
|
|
||||||
"""
|
|
||||||
parcel_objs = Parcel.objects.filter(**kwargs)
|
|
||||||
|
|
||||||
if not parcel_objs.exists():
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Get one of the found parcels and use it as new 'true one'
|
|
||||||
unique_parcel = parcel_objs.first()
|
|
||||||
# separate it from the rest
|
|
||||||
parcel_objs = parcel_objs.exclude(id=unique_parcel.id)
|
|
||||||
|
|
||||||
if not parcel_objs.exists():
|
|
||||||
# There are no duplicates - all good, just return
|
|
||||||
return unique_parcel
|
|
||||||
|
|
||||||
# Fetch existing intersections, which still point on the duplicated parcels
|
|
||||||
intersection_objs = ParcelIntersection.objects.filter(
|
|
||||||
parcel__in=parcel_objs
|
|
||||||
)
|
|
||||||
|
|
||||||
# Change each intersection, so they point on the 'true one' parcel from now on
|
|
||||||
for intersection in intersection_objs:
|
|
||||||
intersection.parcel = unique_parcel
|
|
||||||
intersection.save()
|
|
||||||
|
|
||||||
# Remove the duplicated parcels
|
|
||||||
parcel_objs.delete()
|
|
||||||
|
|
||||||
return unique_parcel
|
|
||||||
|
|
||||||
|
|
||||||
class ParcelIntersection(UuidModel):
|
class ParcelIntersection(UuidModel):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -18,6 +18,7 @@ from konova.sub_settings.proxy_settings import *
|
|||||||
from konova.sub_settings.sso_settings import *
|
from konova.sub_settings.sso_settings import *
|
||||||
from konova.sub_settings.table_settings import *
|
from konova.sub_settings.table_settings import *
|
||||||
from konova.sub_settings.lanis_settings import *
|
from konova.sub_settings.lanis_settings import *
|
||||||
|
from konova.sub_settings.wfs_parcel_settings import *
|
||||||
from konova.sub_settings.logging_settings import *
|
from konova.sub_settings.logging_settings import *
|
||||||
|
|
||||||
# Max upload size for POST forms
|
# Max upload size for POST forms
|
||||||
|
|||||||
78
konova/sso/sso.py
Normal file
78
konova/sso/sso.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 17.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.urls import re_path
|
||||||
|
from django.views import View
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from itsdangerous import TimedSerializer
|
||||||
|
from simple_sso.sso_client.client import Client
|
||||||
|
|
||||||
|
from user.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class PropagateView(View):
|
||||||
|
""" View used to receive propagated sso-server user data
|
||||||
|
|
||||||
|
"""
|
||||||
|
client = None
|
||||||
|
signer = None
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.signer = TimedSerializer(self.client.private_key)
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
user_data = request.body
|
||||||
|
user_data = self.signer.loads(user_data)
|
||||||
|
self.client.build_user(user_data)
|
||||||
|
return HttpResponse(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
class KonovaSSOClient(Client):
|
||||||
|
""" Konova specialized derivative of general sso.Client.
|
||||||
|
|
||||||
|
Adds some custom behaviour for konova usage.
|
||||||
|
|
||||||
|
"""
|
||||||
|
propagate_view = PropagateView
|
||||||
|
|
||||||
|
def get_urls(self):
|
||||||
|
urls = super().get_urls()
|
||||||
|
urls += re_path(r'^propagate/$', self.propagate_view.as_view(client=self), name='simple-sso-propagate'),
|
||||||
|
return urls
|
||||||
|
|
||||||
|
def build_user(self, user_data):
|
||||||
|
""" Creates a user or updates user data
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_data ():
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=user_data['username'])
|
||||||
|
# Update user data, excluding some changes
|
||||||
|
skipable_attrs = {
|
||||||
|
"username",
|
||||||
|
"is_staff",
|
||||||
|
"is_superuser",
|
||||||
|
}
|
||||||
|
for _attr, _val in user_data.items():
|
||||||
|
if _attr in skipable_attrs:
|
||||||
|
continue
|
||||||
|
setattr(user, _attr, _val)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
user = User(**user_data)
|
||||||
|
user.set_unusable_password()
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
@ -278,18 +278,4 @@ Similar to bootstraps 'shadow-lg'
|
|||||||
}
|
}
|
||||||
.alert{
|
.alert{
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Overwrites netgis.css attributes
|
|
||||||
*/
|
|
||||||
.netgis-gradient-a{
|
|
||||||
/*
|
|
||||||
Overwrites gradient used on default css of netgis map client
|
|
||||||
*/
|
|
||||||
background: var(--rlp-red) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.netgis-menu{
|
|
||||||
z-index: 1 !important;
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB |
@ -10,8 +10,6 @@ For the full list of settings and their values, see
|
|||||||
https://docs.djangoproject.com/en/3.1/ref/settings/
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import environ
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.conf.locale.de import formats as de_formats
|
from django.conf.locale.de import formats as de_formats
|
||||||
|
|
||||||
@ -26,28 +24,32 @@ BASE_DIR = os.path.dirname(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
env = environ.Env()
|
# Quick-start development settings - unsuitable for production
|
||||||
# Take environment variables from .env.dev file
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = env("SECRET_KEY")
|
SECRET_KEY = '5=9-)2)h$u9=!zrhia9=lj-2#cpcb8=#$7y+)l$5tto$3q(n_+'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = env.bool("DEBUG", default=False)
|
DEBUG = True
|
||||||
|
|
||||||
ADMINS = [x.split(':') for x in env.list('ADMINS')]
|
ADMINS = [
|
||||||
|
('KSP-Servicestelle', 'ksp-servicestelle@sgdnord.rlp.de'),
|
||||||
|
]
|
||||||
|
|
||||||
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
|
BASE_URL = "http://localhost:8001"
|
||||||
|
|
||||||
BASE_URL = env("BASE_URL")
|
ALLOWED_HOSTS = [
|
||||||
|
"127.0.0.1",
|
||||||
|
"localhost",
|
||||||
|
]
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
BASE_URL
|
"http://localhost", # not only host but schema (http/s) as well!
|
||||||
]
|
]
|
||||||
|
|
||||||
# Authentication settings
|
# Authentication settings
|
||||||
LOGIN_URL = "/oauth/login/"
|
LOGIN_URL = "/login/"
|
||||||
|
|
||||||
# Session settings
|
# Session settings
|
||||||
SESSION_COOKIE_AGE = 60 * 60 # 60 minutes
|
SESSION_COOKIE_AGE = 60 * 60 # 60 minutes
|
||||||
@ -66,6 +68,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.gis',
|
'django.contrib.gis',
|
||||||
'django.contrib.humanize',
|
'django.contrib.humanize',
|
||||||
|
'simple_sso.sso_server',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
'bootstrap_modal_forms',
|
'bootstrap_modal_forms',
|
||||||
'fontawesome_5',
|
'fontawesome_5',
|
||||||
@ -80,6 +83,10 @@ INSTALLED_APPS = [
|
|||||||
'analysis',
|
'analysis',
|
||||||
'api',
|
'api',
|
||||||
]
|
]
|
||||||
|
if DEBUG:
|
||||||
|
INSTALLED_APPS += [
|
||||||
|
'debug_toolbar',
|
||||||
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
@ -91,6 +98,10 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
if DEBUG:
|
||||||
|
MIDDLEWARE += [
|
||||||
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'konova.urls'
|
ROOT_URLCONF = 'konova.urls'
|
||||||
|
|
||||||
@ -120,11 +131,10 @@ WSGI_APPLICATION = 'konova.wsgi.application'
|
|||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||||
'NAME': env("DB_NAME"),
|
'NAME': 'konova',
|
||||||
'USER': env("DB_USER"),
|
'USER': 'postgres',
|
||||||
'PASSWORD': env("DB_PASSWORD"),
|
'HOST': '127.0.0.1',
|
||||||
'HOST': env("DB_HOST"),
|
'PORT': '5432',
|
||||||
'PORT': env("DB_PORT"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
@ -188,8 +198,31 @@ STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
|||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
os.path.join(BASE_DIR, 'konova/static'),
|
os.path.join(BASE_DIR, 'konova/static'),
|
||||||
os.path.join(BASE_DIR, 'templates/map/client'), # NETGIS map client files
|
os.path.join(BASE_DIR, 'templates/map/client'), # NETGIS map client files
|
||||||
|
os.path.join(BASE_DIR, 'templates/map/client/libs'), # NETGIS map client files
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# DJANGO DEBUG TOOLBAR
|
||||||
|
INTERNAL_IPS = [
|
||||||
|
"127.0.0.1"
|
||||||
|
]
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
|
"DISABLE_PANELS": {
|
||||||
|
'debug_toolbar.panels.versions.VersionsPanel',
|
||||||
|
'debug_toolbar.panels.timer.TimerPanel',
|
||||||
|
'debug_toolbar.panels.settings.SettingsPanel',
|
||||||
|
'debug_toolbar.panels.headers.HeadersPanel',
|
||||||
|
'debug_toolbar.panels.request.RequestPanel',
|
||||||
|
'debug_toolbar.panels.sql.SQLPanel',
|
||||||
|
'debug_toolbar.panels.staticfiles.StaticFilesPanel',
|
||||||
|
'debug_toolbar.panels.templates.TemplatesPanel',
|
||||||
|
'debug_toolbar.panels.cache.CachePanel',
|
||||||
|
'debug_toolbar.panels.signals.SignalsPanel',
|
||||||
|
'debug_toolbar.panels.logging.LoggingPanel',
|
||||||
|
'debug_toolbar.panels.redirects.RedirectsPanel',
|
||||||
|
'debug_toolbar.panels.profiling.ProfilingPanel',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# EMAIL (see https://docs.djangoproject.com/en/dev/topics/email/)
|
# EMAIL (see https://docs.djangoproject.com/en/dev/topics/email/)
|
||||||
|
|
||||||
# CHANGE_ME !!! ONLY FOR DEVELOPMENT !!!
|
# CHANGE_ME !!! ONLY FOR DEVELOPMENT !!!
|
||||||
@ -197,10 +230,13 @@ if DEBUG:
|
|||||||
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend'
|
||||||
EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
|
EMAIL_FILE_PATH = '/tmp/app-messages' # change this to a proper location
|
||||||
|
|
||||||
DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL") # The default email address for the 'from' element
|
DEFAULT_FROM_EMAIL = "service@ksp.de" # The default email address for the 'from' element
|
||||||
SERVER_EMAIL = DEFAULT_FROM_EMAIL # The default email sender address, which is used by Django to send errors via mail
|
SERVER_EMAIL = DEFAULT_FROM_EMAIL # The default email sender address, which is used by Django to send errors via mail
|
||||||
EMAIL_HOST = env("SMTP_HOST")
|
EMAIL_HOST = "localhost"
|
||||||
EMAIL_REPLY_TO = env("REPLY_TO_ADDR")
|
EMAIL_REPLY_TO = "ksp-servicestelle@sgdnord.rlp.de"
|
||||||
EMAIL_PORT = env("SMTP_PORT")
|
SUPPORT_MAIL_RECIPIENT = EMAIL_REPLY_TO
|
||||||
|
EMAIL_PORT = "25"
|
||||||
|
#EMAIL_HOST_USER = ""
|
||||||
|
#EMAIL_HOST_PASSWORD = ""
|
||||||
EMAIL_USE_TLS = False
|
EMAIL_USE_TLS = False
|
||||||
EMAIL_USE_SSL = False
|
EMAIL_USE_SSL = False
|
||||||
|
|||||||
@ -5,7 +5,6 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 31.01.22
|
Created on: 31.01.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from konova.sub_settings.django_settings import env
|
|
||||||
|
|
||||||
# MAPS
|
# MAPS
|
||||||
DEFAULT_LAT = 50.00
|
DEFAULT_LAT = 50.00
|
||||||
@ -29,6 +28,3 @@ LANIS_ZOOM_LUT = {
|
|||||||
1000: 30,
|
1000: 30,
|
||||||
500: 31,
|
500: 31,
|
||||||
}
|
}
|
||||||
|
|
||||||
MAP_PROXY_HOST_WHITELIST = env.list("MAP_PROXY_HOST_WHITELIST")
|
|
||||||
i = 0
|
|
||||||
@ -5,13 +5,12 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 31.01.22
|
Created on: 31.01.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from konova.sub_settings.django_settings import env
|
|
||||||
|
|
||||||
proxy = env("PROXY")
|
proxy = ""
|
||||||
PROXIES = {
|
PROXIES = {
|
||||||
"http": proxy,
|
"http": proxy,
|
||||||
"https": proxy,
|
"https": proxy,
|
||||||
}
|
}
|
||||||
|
|
||||||
GEOPORTAL_RLP_USER = env("GEOPORTAL_RLP_USER")
|
CLIENT_PROXY_AUTH_USER = "CHANGE_ME"
|
||||||
GEOPORTAL_RLP_PASSWORD = env("GEOPORTAL_RLP_PASSWORD")
|
CLIENT_PROXY_AUTH_PASSWORD = "CHANGE_ME"
|
||||||
@ -5,8 +5,7 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 14.12.22
|
Created on: 14.12.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from konova.sub_settings.django_settings import env
|
|
||||||
|
|
||||||
base_url = env("SCHNEIDER_BASE_URL")
|
base_url = "http://127.0.0.1:8002"
|
||||||
auth_header = env("SCHNEIDER_AUTH_HEADER")
|
auth_header = "auth"
|
||||||
auth_header_token = env("SCHNEIDER_AUTH_TOKEN")
|
auth_header_token = "CHANGE_ME"
|
||||||
|
|||||||
@ -5,16 +5,9 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 31.01.22
|
Created on: 31.01.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from konova.sub_settings.django_settings import env
|
|
||||||
|
|
||||||
# SSO settings
|
# SSO settings
|
||||||
SSO_SERVER_BASE = env("SSO_SERVER_BASE_URL")
|
SSO_SERVER_BASE = "http://127.0.0.1:8000/"
|
||||||
SSO_SERVER = f"{SSO_SERVER_BASE}sso/"
|
SSO_SERVER = f"{SSO_SERVER_BASE}sso/"
|
||||||
|
SSO_PRIVATE_KEY = "CHANGE_ME"
|
||||||
# OAuth settings
|
SSO_PUBLIC_KEY = "CHANGE_ME"
|
||||||
OAUTH_CODE_VERIFIER = env("OAUTH_CODE_VERIFIER")
|
|
||||||
|
|
||||||
OAUTH_CLIENT_ID = env("OAUTH_CLIENT_ID")
|
|
||||||
OAUTH_CLIENT_SECRET = env("OAUTH_CLIENT_SECRET")
|
|
||||||
|
|
||||||
PROPAGATION_SECRET = env("PROPAGATION_SECRET")
|
|
||||||
12
konova/sub_settings/wfs_parcel_settings.py
Normal file
12
konova/sub_settings/wfs_parcel_settings.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 31.01.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Parcel WFS settings
|
||||||
|
PARCEL_WFS_BASE_URL = "https://www.geoportal.rlp.de/registry/wfs/519"
|
||||||
|
PARCEL_WFS_USER = "ksp"
|
||||||
|
PARCEL_WFS_PW = "CHANGE_ME"
|
||||||
@ -7,7 +7,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def celery_update_parcels(geometry_id: str, recheck: bool = True):
|
def celery_update_parcels(geometry_id: str, recheck: bool = True):
|
||||||
from konova.models import Geometry
|
from konova.models import Geometry, ParcelIntersection
|
||||||
try:
|
try:
|
||||||
geom = Geometry.objects.get(id=geometry_id)
|
geom = Geometry.objects.get(id=geometry_id)
|
||||||
geom.parcels.clear()
|
geom.parcels.clear()
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="scroll-150 font-italic">
|
<div class="scroll-150 font-italic">
|
||||||
{{obj.comment|linebreaks}}
|
{{obj.comment}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -124,7 +124,6 @@ class GeometryTestCase(BaseTestCase):
|
|||||||
{
|
{
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
"geometry": json.loads(p.json),
|
"geometry": json.loads(p.json),
|
||||||
"properties": {}
|
|
||||||
}
|
}
|
||||||
for p in polygons
|
for p in polygons
|
||||||
]
|
]
|
||||||
|
|||||||
@ -469,7 +469,7 @@ class BaseTestCase(TestCase):
|
|||||||
eco_account.save()
|
eco_account.save()
|
||||||
return eco_account
|
return eco_account
|
||||||
|
|
||||||
def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon, tolerance=0.001):
|
def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon, tolerance = 0.001):
|
||||||
""" Assert for geometries to be equal
|
""" Assert for geometries to be equal
|
||||||
|
|
||||||
Transforms the geometries to matching srids before checking
|
Transforms the geometries to matching srids before checking
|
||||||
@ -491,10 +491,7 @@ class BaseTestCase(TestCase):
|
|||||||
# transformation from one coordinate system into the other, which is valid
|
# transformation from one coordinate system into the other, which is valid
|
||||||
geom1.transform(geom2.srid)
|
geom1.transform(geom2.srid)
|
||||||
geom2.transform(geom1.srid)
|
geom2.transform(geom1.srid)
|
||||||
self.assertTrue(
|
self.assertTrue(geom1.equals_exact(geom2, tolerance) or geom2.equals_exact(geom1, tolerance))
|
||||||
geom1.equals_exact(geom2, tolerance=tolerance),
|
|
||||||
msg=f"Difference is {abs(geom1.area - geom2.area)} with {geom1.area} and {geom2.area} in a tolerance of {tolerance}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseViewTestCase(BaseTestCase):
|
class BaseViewTestCase(BaseTestCase):
|
||||||
@ -512,7 +509,7 @@ class BaseViewTestCase(BaseTestCase):
|
|||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.login_url = reverse("oauth-login")
|
self.login_url = reverse("simple-sso-login")
|
||||||
|
|
||||||
def assert_url_success(self, client: Client, urls: list):
|
def assert_url_success(self, client: Client, urls: list):
|
||||||
""" Assert for all given urls a direct 200 response
|
""" Assert for all given urls a direct 200 response
|
||||||
|
|||||||
@ -13,19 +13,21 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
import debug_toolbar
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from konova.settings import SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY, DEBUG
|
||||||
|
from konova.sso.sso import KonovaSSOClient
|
||||||
from konova.views.logout import LogoutView
|
from konova.views.logout import LogoutView
|
||||||
from konova.views.geometry import GeomParcelsView, GeomParcelsContentView
|
from konova.views.geometry import GeomParcelsView, GeomParcelsContentView
|
||||||
from konova.views.home import HomeView
|
from konova.views.home import HomeView
|
||||||
from konova.views.map_proxy import ClientProxyParcelSearch, ClientProxyParcelWFS
|
from konova.views.map_proxy import ClientProxyParcelSearch, ClientProxyParcelWFS
|
||||||
from konova.views.oauth import OAuthLoginView, OAuthCallbackView
|
|
||||||
|
|
||||||
|
sso_client = KonovaSSOClient(SSO_SERVER, SSO_PUBLIC_KEY, SSO_PRIVATE_KEY)
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('oauth/callback/', OAuthCallbackView.as_view(), name="oauth-callback"),
|
path('login/', include(sso_client.get_urls())),
|
||||||
path('oauth/login/', OAuthLoginView.as_view(), name="oauth-login"),
|
|
||||||
path('logout/', LogoutView.as_view(), name="logout"),
|
path('logout/', LogoutView.as_view(), name="logout"),
|
||||||
path('', HomeView.as_view(), name="home"),
|
path('', HomeView.as_view(), name="home"),
|
||||||
path('intervention/', include("intervention.urls")),
|
path('intervention/', include("intervention.urls")),
|
||||||
@ -42,6 +44,10 @@ urlpatterns = [
|
|||||||
path('client/proxy/wfs', ClientProxyParcelWFS.as_view(), name="client-proxy-wfs"),
|
path('client/proxy/wfs', ClientProxyParcelWFS.as_view(), name="client-proxy-wfs"),
|
||||||
]
|
]
|
||||||
|
|
||||||
handler400 = "konova.views.error.get_400_view"
|
if DEBUG:
|
||||||
|
urlpatterns += [
|
||||||
|
path('__debug__/', include(debug_toolbar.urls)),
|
||||||
|
]
|
||||||
|
|
||||||
handler404 = "konova.views.error.get_404_view"
|
handler404 = "konova.views.error.get_404_view"
|
||||||
handler500 = "konova.views.error.get_500_view"
|
handler500 = "konova.views.error.get_500_view"
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from django.core.mail import send_mail
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL, EMAIL_REPLY_TO
|
from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL, EMAIL_REPLY_TO, SUPPORT_MAIL_RECIPIENT
|
||||||
|
|
||||||
|
|
||||||
class Mailer:
|
class Mailer:
|
||||||
@ -416,7 +416,7 @@ class Mailer:
|
|||||||
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
"EMAIL_REPLY_TO": EMAIL_REPLY_TO,
|
||||||
}
|
}
|
||||||
msg = render_to_string("email/api/verify_token.html", context)
|
msg = render_to_string("email/api/verify_token.html", context)
|
||||||
user_mail_address = [EMAIL_REPLY_TO]
|
user_mail_address = [SUPPORT_MAIL_RECIPIENT]
|
||||||
self.send(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("Request for new API token"),
|
_("Request for new API token"),
|
||||||
|
|||||||
@ -83,7 +83,6 @@ EDITED_GENERAL_DATA = _("Edited general data")
|
|||||||
# Geometry
|
# Geometry
|
||||||
GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
|
GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
|
||||||
GEOMETRY_SIMPLIFIED = _("The geometry contained more than {} vertices. It had to be simplified to match the allowed limit of {} vertices.").format(GEOM_MAX_VERTICES, GEOM_MAX_VERTICES)
|
GEOMETRY_SIMPLIFIED = _("The geometry contained more than {} vertices. It had to be simplified to match the allowed limit of {} vertices.").format(GEOM_MAX_VERTICES, GEOM_MAX_VERTICES)
|
||||||
GEOMETRIES_IGNORED_TEMPLATE = _("The geometry contained {} parts which have been detected as invalid (e.g. too small to be valid). These parts have been removed. Please check the stored geometry.")
|
|
||||||
|
|
||||||
# INTERVENTION
|
# INTERVENTION
|
||||||
INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations")
|
INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations")
|
||||||
@ -92,6 +91,3 @@ INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations"
|
|||||||
DATA_CHECKED_ON_TEMPLATE = _("Checked on {} by {}")
|
DATA_CHECKED_ON_TEMPLATE = _("Checked on {} by {}")
|
||||||
DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by {}")
|
DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by {}")
|
||||||
DATA_IS_UNCHECKED = _("Current data not checked yet")
|
DATA_IS_UNCHECKED = _("Current data not checked yet")
|
||||||
|
|
||||||
# API TOKEN SETTINGS
|
|
||||||
NEW_API_TOKEN_GENERATED = _("New token generated. Administrators need to validate.")
|
|
||||||
78
konova/utils/messenger.py
Normal file
78
konova/utils/messenger.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 17.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from collections import Iterable
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from user.models import User
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.settings import SSO_SERVER_BASE, SSO_PUBLIC_KEY, PROXIES
|
||||||
|
from konova.sub_settings.context_settings import BASE_TITLE_SHORT
|
||||||
|
|
||||||
|
|
||||||
|
class Messenger:
|
||||||
|
""" Used to send messages to the SSO server.
|
||||||
|
|
||||||
|
Messages can be seen by the user the next time they login on their SSO dashboard.
|
||||||
|
Documentation for SSO Server-Client communication can be found here:
|
||||||
|
https://git.naturschutz.rlp.de/SGD-Nord/arnova/wiki/Messages
|
||||||
|
|
||||||
|
"""
|
||||||
|
server_url = "{}communication/message/".format(SSO_SERVER_BASE)
|
||||||
|
|
||||||
|
def __init__(self, users: Iterable, subject: str = None, body: str = None, type: str = None):
|
||||||
|
self.users = users
|
||||||
|
self.msg_subject = subject
|
||||||
|
self.msg_body = body
|
||||||
|
self.msg_type = type
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
""" Sends a message
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.msg_body is None or len(self.msg_body) == 0:
|
||||||
|
raise AttributeError("No message body set")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"x-services-public-key": SSO_PUBLIC_KEY
|
||||||
|
}
|
||||||
|
for user in self.users:
|
||||||
|
data = {
|
||||||
|
"type": self.msg_type,
|
||||||
|
"sender": BASE_TITLE_SHORT,
|
||||||
|
"receiver": user.username,
|
||||||
|
"subject": self.msg_subject,
|
||||||
|
"body": self.msg_body,
|
||||||
|
}
|
||||||
|
requests.post(
|
||||||
|
self.server_url,
|
||||||
|
data=data,
|
||||||
|
headers=headers,
|
||||||
|
proxies=PROXIES
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_object_checked(self, obj_identifier: str, performing_user: User, detail_view_url: str = ""):
|
||||||
|
""" Wraps sending of a message related to the checking of an object, like an intervention
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj_identifier (str): The object's identifier (e.g. 'EIV-123'
|
||||||
|
performing_user (User): The user who performed the checking
|
||||||
|
detail_view_url (str): If a direct link to the object shall be added to the message, it can be provided here
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.msg_subject = _("{} checked").format(obj_identifier)
|
||||||
|
if len(detail_view_url) > 0:
|
||||||
|
detail_view_url = _('<a href="{}">Check it out</a>').format(detail_view_url)
|
||||||
|
self.msg_body = _("{} has been checked successfully by user {}! {}").format(
|
||||||
|
obj_identifier,
|
||||||
|
performing_user.username,
|
||||||
|
detail_view_url
|
||||||
|
)
|
||||||
|
self.send()
|
||||||
@ -11,8 +11,6 @@ from json import JSONDecodeError
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from konova.sub_settings import schneider_settings
|
from konova.sub_settings import schneider_settings
|
||||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID
|
|
||||||
from konova.sub_settings.proxy_settings import PROXIES
|
|
||||||
|
|
||||||
|
|
||||||
class ParcelFetcher:
|
class ParcelFetcher:
|
||||||
@ -35,7 +33,6 @@ class ParcelFetcher:
|
|||||||
if geom.area < buffer_threshold:
|
if geom.area < buffer_threshold:
|
||||||
# Fallback for malicious geometries which are way too small and would disappear on negative buffering
|
# Fallback for malicious geometries which are way too small and would disappear on negative buffering
|
||||||
geom = geometry.geom
|
geom = geometry.geom
|
||||||
geom.transform(DEFAULT_SRID)
|
|
||||||
self.geojson = geom.ewkt
|
self.geojson = geom.ewkt
|
||||||
self.results = []
|
self.results = []
|
||||||
|
|
||||||
@ -46,7 +43,6 @@ class ParcelFetcher:
|
|||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
url=post_url,
|
url=post_url,
|
||||||
proxies=PROXIES,
|
|
||||||
data=self.geojson,
|
data=self.geojson,
|
||||||
headers={
|
headers={
|
||||||
self.auth_header: self.auth_header_token
|
self.auth_header: self.auth_header_token
|
||||||
@ -57,11 +53,11 @@ class ParcelFetcher:
|
|||||||
content = json.loads(response.content.decode("utf-8"))
|
content = json.loads(response.content.decode("utf-8"))
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
content = {}
|
content = {}
|
||||||
_next = content.get("next", None)
|
next = content.get("next", None)
|
||||||
fetched_parcels = content.get("results", [])
|
fetched_parcels = content.get("results", [])
|
||||||
self.results += fetched_parcels
|
self.results += fetched_parcels
|
||||||
|
|
||||||
if _next:
|
if next:
|
||||||
self.get_parcels(_next)
|
self.get_parcels(next)
|
||||||
|
|
||||||
return self.results
|
return self.results
|
||||||
@ -216,11 +216,11 @@ class TableRenderMixin:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
html = ""
|
html = ""
|
||||||
is_entry_shared = record.is_shared_with(self.user)
|
has_access = record.is_shared_with(self.user)
|
||||||
|
|
||||||
html += self.render_icn(
|
html += self.render_icn(
|
||||||
tooltip=_("Full access granted") if is_entry_shared else _("Access not granted"),
|
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||||
icn_class="fas fa-edit rlp-r-inv" if is_entry_shared else "far fa-edit",
|
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
|
||||||
)
|
)
|
||||||
return format_html(html)
|
return format_html(html)
|
||||||
|
|
||||||
|
|||||||
@ -25,20 +25,6 @@ def get_404_view(request: HttpRequest, exception=None):
|
|||||||
return render(request, "404.html", context, status=404)
|
return render(request, "404.html", context, status=404)
|
||||||
|
|
||||||
|
|
||||||
def get_400_view(request: HttpRequest, exception=None):
|
|
||||||
""" Returns a 400 handling view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request ():
|
|
||||||
exception ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
context = BaseContext.context
|
|
||||||
return render(request, "400.html", context, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
def get_500_view(request: HttpRequest):
|
def get_500_view(request: HttpRequest):
|
||||||
""" Returns a 404 handling view
|
""" Returns a 404 handling view
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.gis.geos import MultiPolygon
|
from django.contrib.gis.geos import MultiPolygon
|
||||||
from django.http import HttpResponse, HttpRequest
|
from django.http import HttpResponse, HttpRequest
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user