Compare commits

..

No commits in common. "master" and "1.4.3" have entirely different histories.

106 changed files with 990 additions and 1460 deletions

View File

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

1
.gitignore vendored
View File

@ -3,4 +3,3 @@
/.idea/ /.idea/
/.coverage /.coverage
/htmlcov/ /htmlcov/
/.env

View File

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

View File

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

View File

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

View File

@ -11,7 +11,6 @@ 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.sub_settings.lanis_settings import DEFAULT_SRID_RLP 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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")

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

View File

@ -8,7 +8,7 @@ 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
@ -223,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(
@ -377,10 +366,11 @@ 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

View File

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

View File

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

View File

@ -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"
@ -191,6 +201,28 @@ STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'templates/map/client/libs'), # 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 !!!
@ -198,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

View File

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

View File

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

View File

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

View File

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

View 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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -91,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
View 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()

View File

@ -11,7 +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.proxy_settings import PROXIES
class ParcelFetcher: class ParcelFetcher:
@ -44,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
@ -55,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

View File

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

View File

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

View File

@ -24,13 +24,5 @@ class LogoutView(View):
Returns: Returns:
A redirect A redirect
""" """
user = request.user
try:
oauth_token = user.oauth_token
if oauth_token:
oauth_token.revoke()
except AttributeError:
pass
logout(request) logout(request)
return redirect(SSO_SERVER_BASE) return redirect(SSO_SERVER_BASE)

View File

@ -9,7 +9,6 @@ import json
from json import JSONDecodeError from json import JSONDecodeError
import requests import requests
import urllib3.util
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, HttpRequest from django.http import JsonResponse, HttpRequest
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -19,8 +18,7 @@ from django.utils.translation import gettext_lazy as _
from requests.auth import HTTPDigestAuth from requests.auth import HTTPDigestAuth
from konova.sub_settings.lanis_settings import MAP_PROXY_HOST_WHITELIST from konova.sub_settings.proxy_settings import PROXIES, CLIENT_PROXY_AUTH_USER, CLIENT_PROXY_AUTH_PASSWORD
from konova.sub_settings.proxy_settings import PROXIES, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD
class BaseClientProxyView(View): class BaseClientProxyView(View):
@ -34,13 +32,6 @@ class BaseClientProxyView(View):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def _check_with_whitelist(self, url):
parsed_url = urllib3.util.parse_url(url)
parsed_url_host = parsed_url.host
whitelist = set(MAP_PROXY_HOST_WHITELIST)
is_allowed = parsed_url_host in whitelist
return is_allowed
def perform_url_call(self, url, headers={}, auth=None): def perform_url_call(self, url, headers={}, auth=None):
""" Generic proxied call """ Generic proxied call
@ -68,11 +59,6 @@ class ClientProxyParcelSearch(BaseClientProxyView):
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
url = request.META.get("QUERY_STRING") url = request.META.get("QUERY_STRING")
is_url_allowed = self._check_with_whitelist(url)
if not is_url_allowed:
raise PermissionError(f"Proxied url '{url}' is not allowed!")
content, response_code = self.perform_url_call(url) content, response_code = self.perform_url_call(url)
try: try:
body = json.loads(content) body = json.loads(content)
@ -104,7 +90,7 @@ class ClientProxyParcelWFS(BaseClientProxyView):
url = f"{base_url}?{urlencode(params, doseq=True)}" url = f"{base_url}?{urlencode(params, doseq=True)}"
url = url.replace("typename", "typenames") url = url.replace("typename", "typenames")
auth = HTTPDigestAuth(GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD) auth = HTTPDigestAuth(CLIENT_PROXY_AUTH_USER, CLIENT_PROXY_AUTH_PASSWORD)
content, response_code = self.perform_url_call(url, auth=auth) content, response_code = self.perform_url_call(url, auth=auth)
error_detected = response_code != 200 error_detected = response_code != 200

View File

@ -1,125 +0,0 @@
"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 26.04.24
"""
import base64
import hashlib
from urllib.parse import urlencode
import requests
from django.contrib.auth import login
from django.http import HttpRequest
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.timezone import now
from django.views import View
from api.models import OAuthToken
from konova.sub_settings.django_settings import BASE_URL
from konova.sub_settings.sso_settings import SSO_SERVER_BASE, OAUTH_CODE_VERIFIER, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET
class OAuthLoginView(View):
"""
Starts OAuth Login procedure
-> AnonymousUser is redirected to SSO component using specific parameters
-> After successful login (in SSO component), user will be redirected to a specific callback url (OAuthCallbackView)
-> Callback view uses retrieved authorization token to get a proper access token from SSO component
-> SSO component answers with access token
-> OAuthCallbackView uses token in Authorization header to access user data of logged-in user in SSO component
-> OAuthCallbackView creates/updates user
-> OAuthCallbackView logs in user and redirects to default home view
"""
def __create_code_challenge(self):
"""
Creates a code verifier and code challenge for extra security.
See https://django-oauth-toolkit.readthedocs.io/en/latest/getting_started.html#authorization-code for further
information
Returns:
"""
code_verifier = OAUTH_CODE_VERIFIER
code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')
return code_verifier, code_challenge
def get(self, request: HttpRequest, *args, **kwargs):
"""
Redirects user to OAuth SSO webservice for credential based login there
Args:
request ():
*args ():
**kwargs ():
Returns:
"""
oauth_authentication_code_url = f"{SSO_SERVER_BASE}o/authorize/"
redirect_uri = f'{BASE_URL}{reverse("oauth-callback")}'
code_verifier, code_challenge = self.__create_code_challenge()
urlencode_params = urlencode(
{
"response_type": "code",
"code_challenge": code_challenge,
"code_challenge_method": "S256",
"client_id": OAUTH_CLIENT_ID,
"redirect_uri": redirect_uri,
}
)
url = f"{oauth_authentication_code_url}?{urlencode_params}"
return redirect(url)
class OAuthCallbackView(View):
"""
Callback view for OAuth2.0 authentication token.
Authentication tokens will be exchanged for access token.
Access Token will be used for fetching user data from SSO component.
User data will be used for creating/updating user data inside this app.
User will be logged-in and redirected to default home view.
"""
def get(self, request: HttpRequest, *args, **kwargs):
authentication_code = request.GET.get("code")
oauth_acces_token_url = f"{SSO_SERVER_BASE}o/token/"
callback_url = f'{BASE_URL}{reverse("oauth-callback")}'
params = {
"grant_type": "authorization_code",
"code": authentication_code,
"redirect_uri": callback_url,
"code_verifier": OAUTH_CODE_VERIFIER,
"client_id": OAUTH_CLIENT_ID,
"client_secret": OAUTH_CLIENT_SECRET
}
access_code_response = requests.post(
oauth_acces_token_url,
data=params
)
received_on = now()
access_code_response_body = access_code_response.content.decode("utf-8")
status_code_invalid = access_code_response.status_code != 200
if status_code_invalid:
raise RuntimeError(f"OAuth access token could not be fetched: {access_code_response.text}")
oauth_token = OAuthToken.from_access_token_response(access_code_response_body, received_on)
oauth_token.save()
user = oauth_token.update_and_get_user()
user.oauth_replace_token(oauth_token)
login(request, user)
return redirect("home")

Binary file not shown.

View File

@ -29,7 +29,6 @@
#: konova/filters/mixins/office.py:25 konova/filters/mixins/office.py:56 #: konova/filters/mixins/office.py:25 konova/filters/mixins/office.py:56
#: konova/filters/mixins/office.py:57 konova/filters/mixins/record.py:23 #: konova/filters/mixins/office.py:57 konova/filters/mixins/record.py:23
#: konova/filters/mixins/self_created.py:24 konova/filters/mixins/share.py:23 #: konova/filters/mixins/self_created.py:24 konova/filters/mixins/share.py:23
#: konova/filters/mixins/user_log.py:17 konova/filters/mixins/user_log.py:18
#: konova/forms/geometry_form.py:32 konova/forms/modals/document_form.py:26 #: konova/forms/geometry_form.py:32 konova/forms/modals/document_form.py:26
#: konova/forms/modals/document_form.py:36 #: konova/forms/modals/document_form.py:36
#: konova/forms/modals/document_form.py:50 #: konova/forms/modals/document_form.py:50
@ -38,14 +37,13 @@
#: konova/forms/modals/remove_form.py:23 #: konova/forms/modals/remove_form.py:23
#: konova/forms/modals/resubmission_form.py:22 #: konova/forms/modals/resubmission_form.py:22
#: konova/forms/modals/resubmission_form.py:38 konova/forms/remove_form.py:25 #: konova/forms/modals/resubmission_form.py:38 konova/forms/remove_form.py:25
#: konova/tests/unit/test_forms.py:59 user/forms/modals/api_token.py:17 #: konova/tests/unit/test_forms.py:59 user/forms/user.py:39
#: user/forms/user.py:39
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-08 15:26+0100\n" "POT-Creation-Date: 2024-02-16 09:49+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -261,7 +259,7 @@ msgstr ""
#: analysis/templates/analysis/reports/includes/eco_account/deductions.html:14 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:14
#: analysis/templates/analysis/reports/includes/eco_account/deductions.html:16 #: analysis/templates/analysis/reports/includes/eco_account/deductions.html:16
#: compensation/forms/modals/state.py:59 #: compensation/forms/modals/state.py:58
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:36 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:36
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:36 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:36
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:36
@ -374,6 +372,7 @@ msgid "Identifier"
msgstr "Kennung" msgstr "Kennung"
#: compensation/forms/compensation.py:33 intervention/forms/intervention.py:33 #: compensation/forms/compensation.py:33 intervention/forms/intervention.py:33
#: user/forms/user.py:77
msgid "Generated automatically - not editable" msgid "Generated automatically - not editable"
msgstr "Automatisch generiert - nicht bearbeitbar" msgstr "Automatisch generiert - nicht bearbeitbar"
@ -448,7 +447,7 @@ msgid "Select the intervention for which this compensation compensates"
msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist" msgstr "Wählen Sie den Eingriff, für den diese Kompensation bestimmt ist"
#: compensation/forms/compensation.py:114 #: compensation/forms/compensation.py:114
#: compensation/views/compensation/compensation.py:120 #: compensation/views/compensation/compensation.py:119
msgid "New compensation" msgid "New compensation"
msgstr "Neue Kompensation" msgstr "Neue Kompensation"
@ -475,7 +474,7 @@ msgid "When did the parties agree on this?"
msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?" msgstr "Wann wurde dieses Ökokonto offiziell vereinbart?"
#: compensation/forms/eco_account.py:72 #: compensation/forms/eco_account.py:72
#: compensation/views/eco_account/eco_account.py:101 #: compensation/views/eco_account/eco_account.py:100
msgid "New Eco-Account" msgid "New Eco-Account"
msgstr "Neues Ökokonto" msgstr "Neues Ökokonto"
@ -697,46 +696,46 @@ msgid "If there is no date you can enter, please explain why."
msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb." msgstr "Falls Sie kein Datum angeben können, erklären Sie bitte weshalb."
#: compensation/forms/modals/payment.py:108 #: compensation/forms/modals/payment.py:108
#: intervention/templates/intervention/detail/includes/payments.html:67 #: intervention/templates/intervention/detail/includes/payments.html:59
msgid "Edit payment" msgid "Edit payment"
msgstr "Zahlung bearbeiten" msgstr "Zahlung bearbeiten"
#: compensation/forms/modals/state.py:33 #: compensation/forms/modals/state.py:32
msgid "Biotope Type" msgid "Biotope Type"
msgstr "Biotoptyp" msgstr "Biotoptyp"
#: compensation/forms/modals/state.py:36 #: compensation/forms/modals/state.py:35
msgid "Select the biotope type" msgid "Select the biotope type"
msgstr "Biotoptyp wählen" msgstr "Biotoptyp wählen"
#: compensation/forms/modals/state.py:40 compensation/forms/modals/state.py:52 #: compensation/forms/modals/state.py:39 compensation/forms/modals/state.py:51
msgid "Biotope additional type" msgid "Biotope additional type"
msgstr "Zusatzbezeichnung" msgstr "Zusatzbezeichnung"
#: compensation/forms/modals/state.py:43 #: compensation/forms/modals/state.py:42
msgid "Select an additional biotope type" msgid "Select an additional biotope type"
msgstr "Zusatzbezeichnung wählen" msgstr "Zusatzbezeichnung wählen"
#: compensation/forms/modals/state.py:62 #: compensation/forms/modals/state.py:61
#: intervention/forms/modals/deduction.py:49 #: intervention/forms/modals/deduction.py:49
msgid "in m²" msgid "in m²"
msgstr "" msgstr ""
#: compensation/forms/modals/state.py:73 #: compensation/forms/modals/state.py:72
#: compensation/tests/compensation/unit/test_forms.py:175 #: compensation/tests/compensation/unit/test_forms.py:175
msgid "New state" msgid "New state"
msgstr "Neuer Zustand" msgstr "Neuer Zustand"
#: compensation/forms/modals/state.py:74 #: compensation/forms/modals/state.py:73
#: compensation/tests/compensation/unit/test_forms.py:176 #: compensation/tests/compensation/unit/test_forms.py:176
msgid "Insert data for the new state" msgid "Insert data for the new state"
msgstr "Geben Sie die Daten des neuen Zustandes ein" msgstr "Geben Sie die Daten des neuen Zustandes ein"
#: compensation/forms/modals/state.py:91 konova/forms/modals/base_form.py:32 #: compensation/forms/modals/state.py:90 konova/forms/modals/base_form.py:32
msgid "Object removed" msgid "Object removed"
msgstr "Objekt entfernt" msgstr "Objekt entfernt"
#: compensation/forms/modals/state.py:146 #: compensation/forms/modals/state.py:145
#: compensation/templates/compensation/detail/compensation/includes/states-after.html:62 #: compensation/templates/compensation/detail/compensation/includes/states-after.html:62
#: compensation/templates/compensation/detail/compensation/includes/states-before.html:62 #: compensation/templates/compensation/detail/compensation/includes/states-before.html:62
#: compensation/templates/compensation/detail/eco_account/includes/states-after.html:62 #: compensation/templates/compensation/detail/eco_account/includes/states-after.html:62
@ -1288,44 +1287,44 @@ msgstr ""
msgid "Responsible data" msgid "Responsible data"
msgstr "Daten zu den verantwortlichen Stellen" msgstr "Daten zu den verantwortlichen Stellen"
#: compensation/views/compensation/compensation.py:58 #: compensation/views/compensation/compensation.py:57
msgid "Compensations - Overview" msgid "Compensations - Overview"
msgstr "Kompensationen - Übersicht" msgstr "Kompensationen - Übersicht"
#: compensation/views/compensation/compensation.py:181 #: compensation/views/compensation/compensation.py:180
#: konova/utils/message_templates.py:40 #: konova/utils/message_templates.py:40
msgid "Compensation {} edited" msgid "Compensation {} edited"
msgstr "Kompensation {} bearbeitet" msgstr "Kompensation {} bearbeitet"
#: compensation/views/compensation/compensation.py:196 #: compensation/views/compensation/compensation.py:195
#: compensation/views/eco_account/eco_account.py:173 ema/views/ema.py:232 #: compensation/views/eco_account/eco_account.py:172 ema/views/ema.py:230
#: intervention/views/intervention.py:253 #: intervention/views/intervention.py:251
msgid "Edit {}" msgid "Edit {}"
msgstr "Bearbeite {}" msgstr "Bearbeite {}"
#: compensation/views/compensation/report.py:34 #: compensation/views/compensation/report.py:34
#: compensation/views/eco_account/report.py:34 ema/views/report.py:34 #: compensation/views/eco_account/report.py:34 ema/views/report.py:34
#: intervention/views/report.py:35 #: intervention/views/report.py:37
msgid "Report {}" msgid "Report {}"
msgstr "Bericht {}" msgstr "Bericht {}"
#: compensation/views/eco_account/eco_account.py:53 #: compensation/views/eco_account/eco_account.py:52
msgid "Eco-account - Overview" msgid "Eco-account - Overview"
msgstr "Ökokonten - Übersicht" msgstr "Ökokonten - Übersicht"
#: compensation/views/eco_account/eco_account.py:86 #: compensation/views/eco_account/eco_account.py:85
msgid "Eco-Account {} added" msgid "Eco-Account {} added"
msgstr "Ökokonto {} hinzugefügt" msgstr "Ökokonto {} hinzugefügt"
#: compensation/views/eco_account/eco_account.py:158 #: compensation/views/eco_account/eco_account.py:157
msgid "Eco-Account {} edited" msgid "Eco-Account {} edited"
msgstr "Ökokonto {} bearbeitet" msgstr "Ökokonto {} bearbeitet"
#: compensation/views/eco_account/eco_account.py:288 #: compensation/views/eco_account/eco_account.py:286
msgid "Eco-account removed" msgid "Eco-account removed"
msgstr "Ökokonto entfernt" msgstr "Ökokonto entfernt"
#: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:102 #: ema/forms.py:42 ema/tests/unit/test_forms.py:27 ema/views/ema.py:101
msgid "New EMA" msgid "New EMA"
msgstr "Neue EMA hinzufügen" msgstr "Neue EMA hinzufügen"
@ -1353,19 +1352,19 @@ msgstr ""
msgid "Payment funded compensation" msgid "Payment funded compensation"
msgstr "Ersatzzahlungsmaßnahme" msgstr "Ersatzzahlungsmaßnahme"
#: ema/views/ema.py:53 #: ema/views/ema.py:52
msgid "EMAs - Overview" msgid "EMAs - Overview"
msgstr "EMAs - Übersicht" msgstr "EMAs - Übersicht"
#: ema/views/ema.py:86 #: ema/views/ema.py:85
msgid "EMA {} added" msgid "EMA {} added"
msgstr "EMA {} hinzugefügt" msgstr "EMA {} hinzugefügt"
#: ema/views/ema.py:217 #: ema/views/ema.py:215
msgid "EMA {} edited" msgid "EMA {} edited"
msgstr "EMA {} bearbeitet" msgstr "EMA {} bearbeitet"
#: ema/views/ema.py:256 #: ema/views/ema.py:254
msgid "EMA removed" msgid "EMA removed"
msgstr "EMA entfernt" msgstr "EMA entfernt"
@ -1429,7 +1428,7 @@ msgstr "Datum Bestandskraft bzw. Rechtskraft"
#: intervention/forms/intervention.py:216 #: intervention/forms/intervention.py:216
#: intervention/tests/unit/test_forms.py:36 #: intervention/tests/unit/test_forms.py:36
#: intervention/views/intervention.py:105 #: intervention/views/intervention.py:104
msgid "New intervention" msgid "New intervention"
msgstr "Neuer Eingriff" msgstr "Neuer Eingriff"
@ -1598,12 +1597,7 @@ msgctxt "money"
msgid "Amount" msgid "Amount"
msgstr "Betrag" msgstr "Betrag"
#: intervention/templates/intervention/detail/includes/payments.html:61 #: intervention/templates/intervention/detail/includes/payments.html:62
#: konova/utils/message_templates.py:25
msgid "This data is not shared with you"
msgstr "Diese Daten sind für Sie nicht freigegeben"
#: intervention/templates/intervention/detail/includes/payments.html:70
msgid "Remove payment" msgid "Remove payment"
msgstr "Zahlung entfernen" msgstr "Zahlung entfernen"
@ -1665,19 +1659,19 @@ msgstr ""
msgid "Check performed" msgid "Check performed"
msgstr "Prüfung durchgeführt" msgstr "Prüfung durchgeführt"
#: intervention/views/intervention.py:57 #: intervention/views/intervention.py:56
msgid "Interventions - Overview" msgid "Interventions - Overview"
msgstr "Eingriffe - Übersicht" msgstr "Eingriffe - Übersicht"
#: intervention/views/intervention.py:90 #: intervention/views/intervention.py:89
msgid "Intervention {} added" msgid "Intervention {} added"
msgstr "Eingriff {} hinzugefügt" msgstr "Eingriff {} hinzugefügt"
#: intervention/views/intervention.py:236 #: intervention/views/intervention.py:234
msgid "Intervention {} edited" msgid "Intervention {} edited"
msgstr "Eingriff {} bearbeitet" msgstr "Eingriff {} bearbeitet"
#: intervention/views/intervention.py:278 #: intervention/views/intervention.py:276
msgid "{} removed" msgid "{} removed"
msgstr "{} entfernt" msgstr "{} entfernt"
@ -1787,16 +1781,6 @@ msgstr ""
"Wenn aktiviert werden auch Einträge angezeigt, die nicht für Sie freigegeben " "Wenn aktiviert werden auch Einträge angezeigt, die nicht für Sie freigegeben "
"sind" "sind"
#: konova/filters/mixins/user_log.py:21
msgid "Logged user"
msgstr "Bearbeitender Nutzer"
#: konova/filters/mixins/user_log.py:22
msgid ""
"Search for entries where this person has been participated according to log "
"history"
msgstr "Sucht nach Einträgen, an denen diese Person gearbeitet hat"
#: konova/forms/base_form.py:23 templates/form/collapsable/form.html:62 #: konova/forms/base_form.py:23 templates/form/collapsable/form.html:62
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
@ -1857,7 +1841,6 @@ msgstr ""
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen." "Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
#: konova/forms/modals/remove_form.py:22 konova/forms/remove_form.py:24 #: konova/forms/modals/remove_form.py:22 konova/forms/remove_form.py:24
#: user/forms/modals/api_token.py:16
msgid "Confirm" msgid "Confirm"
msgstr "Bestätige" msgstr "Bestätige"
@ -1928,11 +1911,11 @@ msgstr "Kontrolle am"
msgid "Other" msgid "Other"
msgstr "Sonstige" msgstr "Sonstige"
#: konova/sub_settings/django_settings.py:157 #: konova/sub_settings/django_settings.py:166
msgid "German" msgid "German"
msgstr "" msgstr ""
#: konova/sub_settings/django_settings.py:158 #: konova/sub_settings/django_settings.py:167
msgid "English" msgid "English"
msgstr "" msgstr ""
@ -2108,6 +2091,10 @@ msgstr ""
"Eintrag ist verzeichnet. Um Daten zu bearbeiten, muss der Eintrag erst " "Eintrag ist verzeichnet. Um Daten zu bearbeiten, muss der Eintrag erst "
"entzeichnet werden." "entzeichnet werden."
#: konova/utils/message_templates.py:25
msgid "This data is not shared with you"
msgstr "Diese Daten sind für Sie nicht freigegeben"
#: konova/utils/message_templates.py:26 #: konova/utils/message_templates.py:26
msgid "" msgid ""
"Remember: This data has not been shared with you, yet. This means you can " "Remember: This data has not been shared with you, yet. This means you can "
@ -2283,10 +2270,6 @@ msgstr ""
msgid "Current data not checked yet" msgid "Current data not checked yet"
msgstr "Momentane Daten noch nicht geprüft" msgstr "Momentane Daten noch nicht geprüft"
#: konova/utils/message_templates.py:96
msgid "New token generated. Administrators need to validate."
msgstr "Neuer Token generiert. Administratoren sind informiert."
#: konova/utils/messenger.py:70 #: konova/utils/messenger.py:70
msgid "{} checked" msgid "{} checked"
msgstr "{} geprüft" msgstr "{} geprüft"
@ -2325,7 +2308,7 @@ msgstr "Home"
msgid "Log" msgid "Log"
msgstr "Log" msgstr "Log"
#: konova/views/map_proxy.py:84 #: konova/views/map_proxy.py:70
msgid "" msgid ""
"The external service is currently unavailable.<br>Please try again in a few " "The external service is currently unavailable.<br>Please try again in a few "
"moments..." "moments..."
@ -2834,15 +2817,11 @@ msgstr "Mehr"
msgid "Reports" msgid "Reports"
msgstr "Berichte" msgstr "Berichte"
#: templates/navbars/navbar.html:57 #: templates/navbars/navbar.html:56 user/templates/user/index.html:31
msgid "Admin"
msgstr ""
#: templates/navbars/navbar.html:59 user/templates/user/index.html:31
msgid "Settings" msgid "Settings"
msgstr "Einstellungen" msgstr "Einstellungen"
#: templates/navbars/navbar.html:60 #: templates/navbars/navbar.html:57
msgid "Logout" msgid "Logout"
msgstr "Abmelden" msgstr "Abmelden"
@ -2875,21 +2854,6 @@ msgstr ""
"Falls die Geometrie nicht leer ist, werden die Flurstücke aktuell berechnet. " "Falls die Geometrie nicht leer ist, werden die Flurstücke aktuell berechnet. "
"Bitte laden Sie diese Seite in ein paar Augenblicken erneut..." "Bitte laden Sie diese Seite in ein paar Augenblicken erneut..."
#: user/forms/modals/api_token.py:25
msgid "Generate API Token"
msgstr "API Token generieren"
#: user/forms/modals/api_token.py:29
msgid ""
"You are about to create a new API token. The existing one will not be usable "
"afterwards."
msgstr ""
"Wenn Sie fortfahren, generieren Sie einen neuen API Token. Ihren existierenden werden Sie dann nicht länger nutzen können."
#: user/forms/modals/api_token.py:31
msgid "A new token needs to be validated by an administrator!"
msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!"
#: user/forms/modals/team.py:20 user/forms/modals/team.py:24 #: user/forms/modals/team.py:20 user/forms/modals/team.py:24
#: user/forms/team.py:17 user/forms/team.py:22 #: user/forms/team.py:17 user/forms/team.py:22
msgid "Team name" msgid "Team name"
@ -2912,11 +2876,11 @@ msgstr ""
"Mehrfachauswahl möglich - Sie können nur Nutzer wählen, die noch nicht " "Mehrfachauswahl möglich - Sie können nur Nutzer wählen, die noch nicht "
"Mitglieder dieses Teams sind. Geben Sie den ganzen Nutzernamen an." "Mitglieder dieses Teams sind. Geben Sie den ganzen Nutzernamen an."
#: user/forms/modals/team.py:56 user/tests/unit/test_forms.py:29 #: user/forms/modals/team.py:56 user/tests/unit/test_forms.py:31
msgid "Create new team" msgid "Create new team"
msgstr "Neues Team anlegen" msgstr "Neues Team anlegen"
#: user/forms/modals/team.py:57 user/tests/unit/test_forms.py:30 #: user/forms/modals/team.py:57 user/tests/unit/test_forms.py:32
msgid "" msgid ""
"You will become the administrator for this group by default. You do not need " "You will become the administrator for this group by default. You do not need "
"to add yourself to the list of members." "to add yourself to the list of members."
@ -2945,11 +2909,11 @@ msgid "There must be at least one admin on this team."
msgstr "Es muss mindestens einen Administrator für das Team geben." msgstr "Es muss mindestens einen Administrator für das Team geben."
#: user/forms/modals/team.py:160 user/templates/user/team/index.html:60 #: user/forms/modals/team.py:160 user/templates/user/team/index.html:60
#: user/tests/unit/test_forms.py:86 #: user/tests/unit/test_forms.py:88
msgid "Edit team" msgid "Edit team"
msgstr "Team bearbeiten" msgstr "Team bearbeiten"
#: user/forms/modals/team.py:187 user/tests/unit/test_forms.py:163 #: user/forms/modals/team.py:187 user/tests/unit/test_forms.py:165
msgid "" msgid ""
"ATTENTION!\n" "ATTENTION!\n"
"\n" "\n"
@ -2966,7 +2930,7 @@ msgstr ""
"Sind Sie sicher, dass Sie dieses Team löschen möchten?" "Sind Sie sicher, dass Sie dieses Team löschen möchten?"
#: user/forms/modals/team.py:197 user/templates/user/team/index.html:56 #: user/forms/modals/team.py:197 user/templates/user/team/index.html:56
#: user/tests/unit/test_forms.py:196 #: user/tests/unit/test_forms.py:198
msgid "Leave team" msgid "Leave team"
msgstr "Team verlassen" msgstr "Team verlassen"
@ -2998,10 +2962,22 @@ msgstr "Benachrichtigungen"
msgid "Select the situations when you want to receive a notification" msgid "Select the situations when you want to receive a notification"
msgstr "Wann wollen Sie per E-Mail benachrichtigt werden?" msgstr "Wann wollen Sie per E-Mail benachrichtigt werden?"
#: user/forms/user.py:38 user/tests/unit/test_forms.py:232 #: user/forms/user.py:38 user/tests/unit/test_forms.py:234
msgid "Edit notifications" msgid "Edit notifications"
msgstr "Benachrichtigungen bearbeiten" msgstr "Benachrichtigungen bearbeiten"
#: user/forms/user.py:73
msgid "Token"
msgstr ""
#: user/forms/user.py:88 user/tests/unit/test_forms.py:260
msgid "Create new token"
msgstr "Neuen Token generieren"
#: user/forms/user.py:89 user/tests/unit/test_forms.py:261
msgid "A new token needs to be validated by an administrator!"
msgstr "Neue Tokens müssen durch Administratoren freigeschaltet werden!"
#: user/models/user_action.py:23 #: user/models/user_action.py:23
msgid "Unrecorded" msgid "Unrecorded"
msgstr "Entzeichnet" msgstr "Entzeichnet"
@ -3056,7 +3032,7 @@ msgid "Manage teams"
msgstr "" msgstr ""
#: user/templates/user/index.html:53 user/templates/user/team/index.html:19 #: user/templates/user/index.html:53 user/templates/user/team/index.html:19
#: user/views/views.py:135 #: user/views.py:171
msgid "Teams" msgid "Teams"
msgstr "" msgstr ""
@ -3092,58 +3068,270 @@ msgstr "API Einstellungen"
msgid "Current token" msgid "Current token"
msgstr "Aktueller Token" msgstr "Aktueller Token"
#: user/templates/user/token.html:15 #: user/templates/user/token.html:14
msgid "Create new token"
msgstr "Neuen Token generieren"
#: user/templates/user/token.html:23
msgid "Authenticated by admins" msgid "Authenticated by admins"
msgstr "Von Admin freigeschaltet" msgstr "Von Admin freigeschaltet"
#: user/templates/user/token.html:27 #: user/templates/user/token.html:18
msgid "Token has been verified and can be used" msgid "Token has been verified and can be used"
msgstr "Token wurde freigeschaltet und kann verwendet werden" msgstr "Token wurde freigeschaltet und kann verwendet werden"
#: user/templates/user/token.html:29 #: user/templates/user/token.html:20
msgid "Token waiting for verification" msgid "Token waiting for verification"
msgstr "Token noch nicht freigeschaltet" msgstr "Token noch nicht freigeschaltet"
#: user/templates/user/token.html:33 #: user/templates/user/token.html:24
msgid "Valid until" msgid "Valid until"
msgstr "Läuft ab am" msgstr "Läuft ab am"
#: user/views/api_token.py:33 #: user/views.py:35
msgid "User API token"
msgstr "API Nutzer Token"
#: user/views/views.py:33
msgid "User settings" msgid "User settings"
msgstr "Einstellungen" msgstr "Einstellungen"
#: user/views/views.py:59 #: user/views.py:61
msgid "Notifications edited" msgid "Notifications edited"
msgstr "Benachrichtigungen bearbeitet" msgstr "Benachrichtigungen bearbeitet"
#: user/views/views.py:71 #: user/views.py:73
msgid "User notifications" msgid "User notifications"
msgstr "Benachrichtigungen" msgstr "Benachrichtigungen"
#: user/views/views.py:147 #: user/views.py:96
msgid "New token generated. Administrators need to validate."
msgstr "Neuer Token generiert. Administratoren sind informiert."
#: user/views.py:107
msgid "User API token"
msgstr "API Nutzer Token"
#: user/views.py:183
msgid "New team added" msgid "New team added"
msgstr "Neues Team hinzugefügt" msgstr "Neues Team hinzugefügt"
#: user/views/views.py:162 #: user/views.py:198
msgid "Team edited" msgid "Team edited"
msgstr "Team bearbeitet" msgstr "Team bearbeitet"
#: user/views/views.py:177 #: user/views.py:213
msgid "Team removed" msgid "Team removed"
msgstr "Team gelöscht" msgstr "Team gelöscht"
#: user/views/views.py:192 #: user/views.py:228
msgid "You are not a member of this team" msgid "You are not a member of this team"
msgstr "Sie sind kein Mitglied dieses Teams" msgstr "Sie sind kein Mitglied dieses Teams"
#: user/views/views.py:199 #: user/views.py:235
msgid "Left Team" msgid "Left Team"
msgstr "Team verlassen" msgstr "Team verlassen"
#~ msgid "close"
#~ msgstr "Schließen"
#~ msgid "Options"
#~ msgstr "Optionen"
#~ msgid "Commands"
#~ msgstr "Befehle"
#~ msgid "Missing command."
#~ msgstr "Befehl fehlt"
#~ msgid "Missing argument"
#~ msgstr "Argument fehlt"
#~ msgid "Missing option"
#~ msgstr "Option fehlt"
#~ msgid "Missing parameter"
#~ msgstr "Parameter fehlt"
#~ msgid "Messages"
#~ msgstr "Nachrichten"
#~ msgid "This field is required."
#~ msgstr "Pflichtfeld"
#~ msgid "Monday"
#~ msgstr "Montag"
#~ msgid "Tuesday"
#~ msgstr "Dienstag"
#~ msgid "Wednesday"
#~ msgstr "Mittwoch"
#~ msgid "Thursday"
#~ msgstr "Donnerstag"
#~ msgid "Friday"
#~ msgstr "Freitag"
#~ msgid "Saturday"
#~ msgstr "Samstag"
#~ msgid "Sunday"
#~ msgstr "Sonntag"
#~ msgid "Mon"
#~ msgstr "Mo"
#~ msgid "Tue"
#~ msgstr "Di"
#~ msgid "Wed"
#~ msgstr "Mi"
#~ msgid "Thu"
#~ msgstr "Do"
#~ msgid "Fri"
#~ msgstr "Fr"
#~ msgid "Sat"
#~ msgstr "Sa"
#~ msgid "Sun"
#~ msgstr "So"
#~ msgid "January"
#~ msgstr "Januar"
#~ msgid "February"
#~ msgstr "Februar"
#~ msgid "March"
#~ msgstr "März"
#~ msgid "May"
#~ msgstr "Mai"
#~ msgid "June"
#~ msgstr "Juni"
#~ msgid "July"
#~ msgstr "Juli"
#~ msgid "October"
#~ msgstr "Oktober"
#~ msgid "December"
#~ msgstr "Dezember"
#~ msgid "mar"
#~ msgstr "mär"
#~ msgid "may"
#~ msgstr "mai"
#~ msgid "oct"
#~ msgstr "okt"
#~ msgid "dec"
#~ msgstr "dez"
#~ msgctxt "abbrev. month"
#~ msgid "March"
#~ msgstr "Mär"
#~ msgctxt "abbrev. month"
#~ msgid "May"
#~ msgstr "Mai"
#~ msgctxt "abbrev. month"
#~ msgid "June"
#~ msgstr "Juni"
#~ msgctxt "abbrev. month"
#~ msgid "July"
#~ msgstr "Juli"
#~ msgctxt "abbrev. month"
#~ msgid "Oct."
#~ msgstr "Okt."
#~ msgctxt "abbrev. month"
#~ msgid "Dec."
#~ msgstr "Dez."
#~ msgctxt "alt. month"
#~ msgid "January"
#~ msgstr "Januar"
#~ msgctxt "alt. month"
#~ msgid "February"
#~ msgstr "Februar"
#~ msgctxt "alt. month"
#~ msgid "March"
#~ msgstr "März"
#~ msgctxt "alt. month"
#~ msgid "May"
#~ msgstr "Mai"
#~ msgctxt "alt. month"
#~ msgid "June"
#~ msgstr "Juni"
#~ msgctxt "alt. month"
#~ msgid "July"
#~ msgstr "Juli"
#~ msgctxt "alt. month"
#~ msgid "October"
#~ msgstr "Oktober"
#~ msgctxt "alt. month"
#~ msgid "December"
#~ msgstr "Dezember"
#~ msgid "or"
#~ msgstr "oder"
#~ msgid ""
#~ "Deductable surface can not be larger than existing surfaces in after "
#~ "states"
#~ msgstr ""
#~ "Die abbuchbare Fläche darf die Gesamtfläche der Zielzustände nicht "
#~ "überschreiten"
#~ msgid ""
#~ "Deductable surface can not be smaller than the sum of already existing "
#~ "deductions. Please contact the responsible users for the deductions!"
#~ msgstr ""
#~ "Es wurde bereits mehr Fläche abgebucht, als Sie nun als abbuchbar "
#~ "einstellen wollen. Kontaktieren Sie die für die Abbuchungen "
#~ "verantwortlichen Nutzer!"
#~ msgid "Added deadline"
#~ msgstr "Frist/Termin hinzugefügt"
#~ msgid "Change default configuration for your KSP map"
#~ msgstr "Karteneinstellungen ändern"
#~ msgid "Map settings"
#~ msgstr "Karte"
#~ msgid "There are errors on this intervention:"
#~ msgstr "Es liegen Fehler in diesem Eingriff vor:"
#~ msgid "Before"
#~ msgstr "Vor"
#~ msgid "Groups"
#~ msgstr "Gruppen"
#~ msgid "Show more..."
#~ msgstr "Mehr anzeigen..."
#~ msgid "Kreis"
#~ msgstr "Kreis"
#~ msgid "Gemarkung"
#~ msgstr "Gemarkung"
#~ msgid "Loading..."
#~ msgstr "Lade..."
#~ msgid "Who handles the eco-account"
#~ msgstr "Wer für die Herrichtung des Ökokontos verantwortlich ist"

View File

@ -1,63 +1,58 @@
amqp==5.3.1 amqp==5.2.0
asgiref==3.8.1 asgiref==3.8.1
async-timeout==5.0.1 async-timeout==4.0.3
beautifulsoup4==4.13.0b2 beautifulsoup4==4.13.0b2
billiard==4.2.1 billiard==4.2.0
cached-property==2.0.1 cached-property==1.5.2
celery==5.4.0 celery==5.4.0rc2
certifi==2024.12.14 certifi==2024.2.2
cffi==1.17.1
chardet==5.2.0 chardet==5.2.0
charset-normalizer==3.4.0 charset-normalizer==3.3.2
click==8.1.8 click==8.1.7
click-didyoumean==0.3.1 click-didyoumean==0.3.1
click-plugins==1.1.1 click-plugins==1.1.1
click-repl==0.3.0 click-repl==0.3.0
coverage==7.6.9 coverage==7.4.4
cryptography==44.0.0 Deprecated==1.2.14
Deprecated==1.2.15 Django==5.0.4
Django==5.1.4
django-autocomplete-light==3.11.0 django-autocomplete-light==3.11.0
django-bootstrap-modal-forms==3.0.5 django-bootstrap-modal-forms==3.0.4
django-bootstrap4==24.4 django-bootstrap4==24.1
django-debug-toolbar==4.3.0
django-environ==0.11.2 django-environ==0.11.2
django-filter==24.3 django-filter==24.2
django-fontawesome-5==1.0.18 django-fontawesome-5==1.0.18
django-oauth-toolkit==3.0.1 django-simple-sso==1.2.0
django-tables2==2.7.1 django-tables2==2.7.0
et_xmlfile==2.0.0 et-xmlfile==1.1.0
gunicorn==23.0.0 idna==3.7
idna==3.10 importlib_metadata==7.1.0
importlib_metadata==8.5.0 itsdangerous==2.1.2
jwcrypto==1.5.6 kombu==5.3.7
kombu==5.4.0rc1
oauthlib==3.2.2
openpyxl==3.2.0b1 openpyxl==3.2.0b1
packaging==24.2 packaging==24.0
pika==1.3.2 pika==1.3.2
pillow==11.0.0 prompt-toolkit==3.0.43
prompt_toolkit==3.0.48 psycopg==3.1.18
psycopg==3.2.3 psycopg-binary==3.1.18
psycopg-binary==3.2.3 pyparsing==3.1.2
pycparser==2.22
pyparsing==3.2.0
pypng==0.20220715.0 pypng==0.20220715.0
pyproj==3.7.0 pyproj==3.6.1
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
pytz==2024.2 pytz==2024.1
PyYAML==6.0.2 PyYAML==6.0.1
qrcode==7.3.1 qrcode==7.3.1
redis==5.1.0b6 redis==5.1.0b4
requests==2.32.3 requests==2.31.0
six==1.16.0 six==1.16.0
soupsieve==2.5 soupsieve==2.5
sqlparse==0.5.1 sqlparse==0.4.4
typing_extensions==4.12.2 typing_extensions==4.11.0
tzdata==2024.2 tzdata==2024.1
urllib3==2.3.0 urllib3==2.2.1
vine==5.1.0 vine==5.1.0
wcwidth==0.2.13 wcwidth==0.2.13
webservices==0.7 webservices==0.7
wrapt==1.16.0 wrapt==1.16.0
xmltodict==0.14.2 xmltodict==0.13.0
zipp==3.21.0 zipp==3.18.1

View File

@ -112,7 +112,7 @@
}, },
"import": "import":
{ {
"geopackageLibURL": "/static/libs/geopackage/4.2.3/" "geopackageLibURL": "/libs/geopackage/4.2.3/"
}, },
"export": "export":
{ {

View File

@ -16,7 +16,7 @@ netgis.Client=function(a,b){this.build="20220826";this.debug=!1;netgis.util.isSt
this.hideLoader()};netgis.Client.Layers=Object.freeze({PARCEL_DISTRICTS:"parcel-districts",PARCEL_FIELDS:"parcel-fields",PARCEL_FEATURES:"parcel-features",EDIT_LAYER:"edit-layer",PREVIEW_LAYER:"preview-layer"});netgis.Client.prototype.applyConfig=function(a){this.createModules();this.load();this.invoke(netgis.Events.CONTEXT_UPDATE,a);this.initModules(a);this.initConfig(a);this.initOutput(a);this.initEvents();this.importLayers={};this.setMode(netgis.Modes.VIEW)}; this.hideLoader()};netgis.Client.Layers=Object.freeze({PARCEL_DISTRICTS:"parcel-districts",PARCEL_FIELDS:"parcel-fields",PARCEL_FEATURES:"parcel-features",EDIT_LAYER:"edit-layer",PREVIEW_LAYER:"preview-layer"});netgis.Client.prototype.applyConfig=function(a){this.createModules();this.load();this.invoke(netgis.Events.CONTEXT_UPDATE,a);this.initModules(a);this.initConfig(a);this.initOutput(a);this.initEvents();this.importLayers={};this.setMode(netgis.Modes.VIEW)};
netgis.Client.prototype.initElements=function(){}; netgis.Client.prototype.initElements=function(){};
netgis.Client.prototype.initModules=function(a){this.map=new netgis.MapOpenLayers(a);this.map.attach(this.root);this.controls=new netgis.Controls;this.controls.attach(this.root);this.layerPanel=new netgis.Panel("Inhalte");this.layerPanel.attach(this.root);this.layerTree=new netgis.Tree;this.layerTree.container.style.position="absolute";this.layerTree.container.style.width="100%";this.layerTree.container.style.top="12mm";this.layerTree.container.style.bottom="6mm";this.layerTree.container.style.overflow= netgis.Client.prototype.initModules=function(a){this.map=new netgis.MapOpenLayers(a);this.map.attach(this.root);this.controls=new netgis.Controls;this.controls.attach(this.root);this.layerPanel=new netgis.Panel("Inhalte");this.layerPanel.attach(this.root);this.layerTree=new netgis.Tree;this.layerTree.container.style.position="absolute";this.layerTree.container.style.width="100%";this.layerTree.container.style.top="12mm";this.layerTree.container.style.bottom="6mm";this.layerTree.container.style.overflow=
"auto";this.layerTree.attach(this.layerPanel.container);this.toolboxPanel=new netgis.Panel("Werkzeuge");this.toolboxPanel.attach(this.root);this.toolbox=new netgis.Toolbox(a);this.toolbox.attach(this.toolboxPanel.container);this.searchPlace=new netgis.SearchPlace(a);this.searchPlace.attach(this.root);this.parcelPanel=new netgis.Panel("Flurst\u00fccks-Suche");this.parcelPanel.container.style.minWidth="92mm";this.parcelPanel.attach(this.root);this.searchParcel=new netgis.SearchParcel(a,this);this.searchParcel.attach(this.parcelPanel.container); "auto";this.layerTree.attach(this.layerPanel.container);this.toolboxPanel=new netgis.Panel("Werkzeuge");this.toolboxPanel.attach(this.root);this.toolbox=new netgis.Toolbox(a);this.toolbox.attach(this.toolboxPanel.container);this.searchPlace=new netgis.SearchPlace(a);this.searchPlace.attach(this.root);this.parcelPanel=new netgis.Panel("Flurst\u00fccks-Suche");this.parcelPanel.container.style.minWidth="92mm";this.parcelPanel.attach(this.root);this.searchParcel=new netgis.SearchParcel(a);this.searchParcel.attach(this.parcelPanel.container);
this.importModal=new netgis.Modal("Ebene hinzuf\u00fcgen");this.importModal.container.classList.add("netgis-import");this.importModal.attach(this.root);a=document.createElement("div");a.className="netgis-content netgis-form netgis-noselect";this.importModal.content.appendChild(a);var b=document.createElement("label");b.innerHTML="Dateiformat:";a.appendChild(b);var c=document.createElement("select");c.innerHTML="<option value='geojson'>GeoJSON</option><option value='gml'>GML</option><option value='gpkg'>GeoPackage</option><option value='shp'>Shapefile (Zip)</option><option value='spatialite'>Spatialite</option>"; this.importModal=new netgis.Modal("Ebene hinzuf\u00fcgen");this.importModal.container.classList.add("netgis-import");this.importModal.attach(this.root);a=document.createElement("div");a.className="netgis-content netgis-form netgis-noselect";this.importModal.content.appendChild(a);var b=document.createElement("label");b.innerHTML="Dateiformat:";a.appendChild(b);var c=document.createElement("select");c.innerHTML="<option value='geojson'>GeoJSON</option><option value='gml'>GML</option><option value='gpkg'>GeoPackage</option><option value='shp'>Shapefile (Zip)</option><option value='spatialite'>Spatialite</option>";
b.addEventListener("change",this.onImportFileTypeChange.bind(this));b.appendChild(c);this.importFileSelect=c;b=document.createElement("div");b.innerHTML="<h3>Unterst\u00fctzte Koordinatensysteme:</h3><ul><li>World Geodetic System 1984 (EPSG:4326)</li><li>ETRS89 / UTM zone 32N (EPSG:25832)</li></ul>";a.appendChild(b);b=document.createElement("label");b.innerHTML="Datei ausw\u00e4hlen/ablegen:";a.appendChild(b);c=document.createElement("input");c.className="netgis-color-d";c.setAttribute("type","file"); b.addEventListener("change",this.onImportFileTypeChange.bind(this));b.appendChild(c);this.importFileSelect=c;b=document.createElement("div");b.innerHTML="<h3>Unterst\u00fctzte Koordinatensysteme:</h3><ul><li>World Geodetic System 1984 (EPSG:4326)</li><li>ETRS89 / UTM zone 32N (EPSG:25832)</li></ul>";a.appendChild(b);b=document.createElement("label");b.innerHTML="Datei ausw\u00e4hlen/ablegen:";a.appendChild(b);c=document.createElement("input");c.className="netgis-color-d";c.setAttribute("type","file");
c.setAttribute("accept",".geojson,.json");b.appendChild(c);this.importFileInput=c;b=document.createElement("button");b.setAttribute("type","button");b.className="netgis-button netgis-center netgis-color-a netgis-hover-c netgis-shadow";b.innerHTML="<i class='netgis-icon fas fa-check'></i><span>Hinzuf\u00fcgen</span>";b.addEventListener("click",this.onImportFileClick.bind(this));a.appendChild(b);this.importServiceModal=new netgis.Modal("Dienst hinzuf\u00fcgen");this.importServiceModal.container.classList.add("netgis-import"); c.setAttribute("accept",".geojson,.json");b.appendChild(c);this.importFileInput=c;b=document.createElement("button");b.setAttribute("type","button");b.className="netgis-button netgis-center netgis-color-a netgis-hover-c netgis-shadow";b.innerHTML="<i class='netgis-icon fas fa-check'></i><span>Hinzuf\u00fcgen</span>";b.addEventListener("click",this.onImportFileClick.bind(this));a.appendChild(b);this.importServiceModal=new netgis.Modal("Dienst hinzuf\u00fcgen");this.importServiceModal.container.classList.add("netgis-import");
@ -53,16 +53,15 @@ netgis.Client.prototype.onLayersToggle=function(a){this.layerPanel.toggle();this
netgis.Client.prototype.onLayerTreeItemChange=function(a){var b=a.detail.id;a=a.detail.checked;switch(b){default:for(var c=this.config.layers,d=!1,e=null,f=0;f<c.length;f++){var g=c[f],h=g.id;if(h===b){e=this.map.createLayer(g);d=!0;break}}if(!d)for(h in this.importLayers)if(h===b){e=this.importLayers[b];d=!0;break}d&&(a?(this.map.addLayer(b,e),this.attribution.onLayerShow({id:b})):(this.map.removeLayer(b),this.attribution.onLayerHide({id:b})));break;case "edit-layer":a?this.map.addLayer(netgis.Client.Layers.EDIT_LAYER, netgis.Client.prototype.onLayerTreeItemChange=function(a){var b=a.detail.id;a=a.detail.checked;switch(b){default:for(var c=this.config.layers,d=!1,e=null,f=0;f<c.length;f++){var g=c[f],h=g.id;if(h===b){e=this.map.createLayer(g);d=!0;break}}if(!d)for(h in this.importLayers)if(h===b){e=this.importLayers[b];d=!0;break}d&&(a?(this.map.addLayer(b,e),this.attribution.onLayerShow({id:b})):(this.map.removeLayer(b),this.attribution.onLayerHide({id:b})));break;case "edit-layer":a?this.map.addLayer(netgis.Client.Layers.EDIT_LAYER,
this.map.editLayer):this.map.removeLayer(netgis.Client.Layers.EDIT_LAYER)}};netgis.Client.prototype.onToolboxToggle=function(a){this.toolboxPanel.toggle();this.toolboxPanel.visible()&&(this.layerPanel.hide(),this.parcelPanel.hide())};netgis.Client.prototype.onSearchPlaceToggle=function(a){this.searchPlace.toggle()};netgis.Client.prototype.onSearchPlaceSelect=function(a){a=a.detail;this.map.zoomExtent(a.minx,a.miny,a.maxx,a.maxy)}; this.map.editLayer):this.map.removeLayer(netgis.Client.Layers.EDIT_LAYER)}};netgis.Client.prototype.onToolboxToggle=function(a){this.toolboxPanel.toggle();this.toolboxPanel.visible()&&(this.layerPanel.hide(),this.parcelPanel.hide())};netgis.Client.prototype.onSearchPlaceToggle=function(a){this.searchPlace.toggle()};netgis.Client.prototype.onSearchPlaceSelect=function(a){a=a.detail;this.map.zoomExtent(a.minx,a.miny,a.maxx,a.maxy)};
netgis.Client.prototype.onSearchParcelToggle=function(a){this.parcelPanel.toggle();this.parcelPanel.visible()&&(this.layerPanel.hide(),this.toolboxPanel.hide())};netgis.Client.prototype.onControlsZoom=function(a){this.map.zoom(a.detail)};netgis.Client.prototype.onControlsZoomAll=function(a){this.map.zoomAll()};netgis.Client.prototype.onParcelsPanelToggle=function(a){this.searchParcel.reset()}; netgis.Client.prototype.onSearchParcelToggle=function(a){this.parcelPanel.toggle();this.parcelPanel.visible()&&(this.layerPanel.hide(),this.toolboxPanel.hide())};netgis.Client.prototype.onControlsZoom=function(a){this.map.zoom(a.detail)};netgis.Client.prototype.onControlsZoomAll=function(a){this.map.zoomAll()};netgis.Client.prototype.onParcelsPanelToggle=function(a){this.searchParcel.reset()};
netgis.Client.prototype.onParcelsReset=function(a){this.map.removeLayer(netgis.Client.Layers.PARCEL_DISTRICTS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FIELDS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FEATURES);if(this.parcelPanel.visible()){this.parcelsAdded=[];var b=this.config.searchParcel.districts_service;a=this.map.createLayer(b);var c=this.config.styles.parcel;this.map.setLayerStyleSimple(a,c.fill,c.stroke,c.width);this.map.setLayerOrder(a,99999);this.map.addLayer(netgis.Client.Layers.PARCEL_DISTRICTS, netgis.Client.prototype.onParcelsReset=function(a){this.map.removeLayer(netgis.Client.Layers.PARCEL_DISTRICTS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FIELDS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FEATURES);if(this.parcelPanel.visible()){this.parcelsAdded=[];var b=this.config.searchParcel.districts_service;a=this.map.createLayer(b);var c=this.config.styles.parcel;this.map.setLayerStyleSimple(a,c.fill,c.stroke,c.width);this.map.addLayer(netgis.Client.Layers.PARCEL_DISTRICTS,a);
a);if(b.minZoom){var d=this;window.setTimeout(function(){d.map.zoomLevel(b.minZoom+1)},50)}this.setMode(netgis.Modes.PARCEL_SELECT)}else this.setMode(netgis.Modes.VIEW)}; if(b.minZoom){var d=this;window.setTimeout(function(){d.map.zoomLevel(b.minZoom+1)},50)}this.setMode(netgis.Modes.PARCEL_SELECT)}else this.setMode(netgis.Modes.VIEW)};
netgis.Client.prototype.onParcelsFieldsResponse=function(a){var b=a.detail;this.map.zoomGeoJSON(b.geojson);this.map.removeLayer(netgis.Client.Layers.PARCEL_DISTRICTS);a=this.config.styles.parcel;b=this.map.createLayerGeoJSON(b.geojson,!1);this.map.setLayerStyleSimple(b,a.fill,a.stroke,a.width);this.map.setLayerOrder(b,99999);this.map.addLayer(netgis.Client.Layers.PARCEL_FIELDS,b)}; netgis.Client.prototype.onParcelsFieldsResponse=function(a){var b=a.detail;this.map.zoomGeoJSON(b.geojson);this.map.removeLayer(netgis.Client.Layers.PARCEL_DISTRICTS);a=this.config.styles.parcel;b=this.map.createLayerGeoJSON(b.geojson,!1);this.map.setLayerStyleSimple(b,a.fill,a.stroke,a.width);this.map.setLayerOrder(b,4E4);this.map.addLayer(netgis.Client.Layers.PARCEL_FIELDS,b)};
netgis.Client.prototype.onParcelsResponse=function(a){a=this.map.createLayerWKT(a.detail.parcels);var b=this.config.styles.parcel;this.map.removeLayer(netgis.Client.Layers.PARCEL_DISTRICTS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FIELDS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FEATURES);this.map.setLayerStyleSimple(a,b.fill,b.stroke,b.width);this.map.setLayerOrder(a,99999);this.map.addLayer(netgis.Client.Layers.PARCEL_FEATURES,a);this.map.zoomLayer(netgis.Client.Layers.PARCEL_FEATURES)}; netgis.Client.prototype.onParcelsResponse=function(a){a=this.map.createLayerWKT(a.detail.parcels);var b=this.config.styles.parcel;this.map.removeLayer(netgis.Client.Layers.PARCEL_DISTRICTS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FIELDS);this.map.removeLayer(netgis.Client.Layers.PARCEL_FEATURES);this.map.setLayerStyleSimple(a,b.fill,b.stroke,b.width);this.map.addLayer(netgis.Client.Layers.PARCEL_FEATURES,a);this.map.zoomLayer(netgis.Client.Layers.PARCEL_FEATURES)};
netgis.Client.prototype.onMapFeatureEnter=function(a){a=a.detail;switch(a.layer){case netgis.Client.Layers.PARCEL_DISTRICTS:a=a.properties.gemarkung;this.map.container.setAttribute("title",a);break;case netgis.Client.Layers.PARCEL_FIELDS:a=a.properties.flurname;this.map.container.setAttribute("title",a);break;case netgis.Client.Layers.PARCEL_FEATURES:a="Flur: "+a.properties.fln+" / Z\u00e4hler: "+a.properties.fsn_zae+" / Nenner: "+a.properties.fsn_nen,this.editable&&(a+=" (Klicken zum \u00dcbernehmen der Geometrie)"), netgis.Client.prototype.onMapFeatureEnter=function(a){a=a.detail;switch(a.layer){case netgis.Client.Layers.PARCEL_DISTRICTS:a=a.properties.gemarkung;this.map.container.setAttribute("title",a);break;case netgis.Client.Layers.PARCEL_FIELDS:a=a.properties.flurname;this.map.container.setAttribute("title",a);break;case netgis.Client.Layers.PARCEL_FEATURES:a="Flur: "+a.properties.fln+" / Z\u00e4hler: "+a.properties.fsn_zae+" / Nenner: "+a.properties.fsn_nen,this.map.container.setAttribute("title",a+" (Klicken zum \u00dcbernehmen der Geometrie)")}};
this.map.container.setAttribute("title",a)}}; netgis.Client.prototype.onMapFeatureClick=function(a){a=a.detail;switch(a.layer){default:console.info("Feature Click:",a);break;case netgis.Client.Layers.PARCEL_DISTRICTS:this.searchParcel.setDistrict(a.properties.gemarkung+" ("+a.properties.ldkreis+")",a.properties.gmkgnr);break;case netgis.Client.Layers.PARCEL_FIELDS:this.searchParcel.setFieldNumber(a.properties.flur);break;case netgis.Client.Layers.PARCEL_FEATURES:var b=a.properties.fsk;-1===this.parcelsAdded.indexOf(b)&&(this.map.addEditFeature(this.map.createFeatureJSON(a.properties)),
netgis.Client.prototype.onMapFeatureClick=function(a){a=a.detail;switch(a.layer){default:console.info("Feature Click:",a);break;case netgis.Client.Layers.PARCEL_DISTRICTS:this.searchParcel.setDistrict(a.properties.gemarkung+" ("+a.properties.ldkreis+")",a.properties.gmkgnr);break;case netgis.Client.Layers.PARCEL_FIELDS:this.searchParcel.setFieldNumber(a.properties.flur);break;case netgis.Client.Layers.PARCEL_FEATURES:if(!0===this.editable){var b=a.properties.fsk;-1===this.parcelsAdded.indexOf(b)&& this.map.updateSnapFeatures(),this.parcelsAdded.push(b))}};netgis.Client.prototype.onMapFeatureLeave=function(a){this.map.container.setAttribute("title","")};netgis.Client.prototype.onParcelsItemEnter=function(a){this.map.setFeatureHover(this.map.findFeature(netgis.Client.Layers.PARCEL_FEATURES,"fsk",a.detail.id),!0)};netgis.Client.prototype.onParcelsItemLeave=function(a){this.map.setFeatureHover(this.map.findFeature(netgis.Client.Layers.PARCEL_FEATURES,"fsk",a.detail.id),!1)};
(this.map.addEditFeature(this.map.createFeatureJSON(a.properties)),this.map.updateSnapFeatures(),this.parcelsAdded.push(b))}}};netgis.Client.prototype.onMapFeatureLeave=function(a){this.map.container.setAttribute("title","")};netgis.Client.prototype.onParcelsItemEnter=function(a){this.map.setFeatureHover(this.map.findFeature(netgis.Client.Layers.PARCEL_FEATURES,"fsk",a.detail.id),!0)}; netgis.Client.prototype.onParcelsItemClick=function(a){this.map.zoomFeature(this.map.findFeature(netgis.Client.Layers.PARCEL_FEATURES,"fsk",a.detail.id))};netgis.Client.prototype.onParcelsImportClick=function(a){a=a.detail;this.map.addEditFeature(this.map.createFeatureWKT(a.geom,{id:a.id}));this.map.updateSnapFeatures()};netgis.Client.prototype.onSetMode=function(a){this.setMode(a.detail)};
netgis.Client.prototype.onParcelsItemLeave=function(a){this.map.setFeatureHover(this.map.findFeature(netgis.Client.Layers.PARCEL_FEATURES,"fsk",a.detail.id),!1)};netgis.Client.prototype.onParcelsItemClick=function(a){this.map.zoomFeature(this.map.findFeature(netgis.Client.Layers.PARCEL_FEATURES,"fsk",a.detail.id))};netgis.Client.prototype.onParcelsImportClick=function(a){a=a.detail;!1!==this.editable&&(this.map.addEditFeature(this.map.createFeatureWKT(a.geom,{id:a.id})),this.map.updateSnapFeatures())}; netgis.Client.prototype.onSnapToggle=function(a){(a=a.detail.target.checked)?(this.map.updateSnapFeatures(),this.map.setSnapOn()):this.map.setSnapOff();this.toolbox.setSnap(a)};netgis.Client.prototype.onTracingToggle=function(a){a.detail.target.checked?(this.map.updateSnapFeatures(),this.map.setSnapOn(),this.map.setTracingOn(),this.toolbox.setSnap(!0)):this.map.setTracingOff()};
netgis.Client.prototype.onSetMode=function(a){this.setMode(a.detail)};netgis.Client.prototype.onSnapToggle=function(a){(a=a.detail.target.checked)?(this.map.updateSnapFeatures(),this.map.setSnapOn()):this.map.setSnapOff();this.toolbox.setSnap(a)};netgis.Client.prototype.onTracingToggle=function(a){a.detail.target.checked?(this.map.updateSnapFeatures(),this.map.setSnapOn(),this.map.setTracingOn(),this.toolbox.setSnap(!0)):this.map.setTracingOff()};
netgis.Client.prototype.onBufferToggle=function(a){if(a=a.detail.target.checked)this.map.onDrawBufferOn();else this.map.onDrawBufferOff();this.toolbox.setBuffer(a)};netgis.Client.prototype.onBufferRadiusChange=function(a){a=Number.parseFloat(a.detail.target.value);this.map.onDrawBufferRadiusChange(a);this.toolbox.setBufferRadius(a)};netgis.Client.prototype.onBufferSegmentsChange=function(a){a=Number.parseInt(a.detail.target.value);this.map.onDrawBufferSegmentsChange(a);this.toolbox.setBufferSegments(a)}; netgis.Client.prototype.onBufferToggle=function(a){if(a=a.detail.target.checked)this.map.onDrawBufferOn();else this.map.onDrawBufferOff();this.toolbox.setBuffer(a)};netgis.Client.prototype.onBufferRadiusChange=function(a){a=Number.parseFloat(a.detail.target.value);this.map.onDrawBufferRadiusChange(a);this.toolbox.setBufferRadius(a)};netgis.Client.prototype.onBufferSegmentsChange=function(a){a=Number.parseInt(a.detail.target.value);this.map.onDrawBufferSegmentsChange(a);this.toolbox.setBufferSegments(a)};
netgis.Client.prototype.onBufferFeatureChange=function(a){this.map.onBufferChange(a.detail)};netgis.Client.prototype.onBufferFeatureAccept=function(a){this.map.onBufferAccept()};netgis.Client.prototype.onImportShapefileShow=function(a){this.modal.onImportShapefileShow(a)};netgis.Client.prototype.onImportGeoJSONShow=function(a){this.modal.onImportGeoJSONShow(a)};netgis.Client.prototype.onImportGMLShow=function(a){this.modal.onImportGMLShow(a)};netgis.Client.prototype.onImportSpatialiteShow=function(a){this.modal.onImportSpatialiteShow(a)}; netgis.Client.prototype.onBufferFeatureChange=function(a){this.map.onBufferChange(a.detail)};netgis.Client.prototype.onBufferFeatureAccept=function(a){this.map.onBufferAccept()};netgis.Client.prototype.onImportShapefileShow=function(a){this.modal.onImportShapefileShow(a)};netgis.Client.prototype.onImportGeoJSONShow=function(a){this.modal.onImportGeoJSONShow(a)};netgis.Client.prototype.onImportGMLShow=function(a){this.modal.onImportGMLShow(a)};netgis.Client.prototype.onImportSpatialiteShow=function(a){this.modal.onImportSpatialiteShow(a)};
netgis.Client.prototype.onImportGeopackageShow=function(a){this.modal.onImportGeopackageShow(a)};netgis.Client.prototype.onExportPDFShow=function(a){this.modal.onExportPDFShow(a)};netgis.Client.prototype.onExportPNGShow=function(a){this.modal.onExportPNGShow(a)};netgis.Client.prototype.onExportJPEGShow=function(a){this.modal.onExportJPEGShow(a)};netgis.Client.prototype.onExportGIFShow=function(a){this.modal.onExportGIFShow(a)};netgis.Client.prototype.onExportPDF=function(a){this.map.onExportPDF(a)}; netgis.Client.prototype.onImportGeopackageShow=function(a){this.modal.onImportGeopackageShow(a)};netgis.Client.prototype.onExportPDFShow=function(a){this.modal.onExportPDFShow(a)};netgis.Client.prototype.onExportPNGShow=function(a){this.modal.onExportPNGShow(a)};netgis.Client.prototype.onExportJPEGShow=function(a){this.modal.onExportJPEGShow(a)};netgis.Client.prototype.onExportGIFShow=function(a){this.modal.onExportGIFShow(a)};netgis.Client.prototype.onExportPDF=function(a){this.map.onExportPDF(a)};
@ -105,8 +104,8 @@ this.client.on(netgis.Events.PARCEL_SHOW_PREVIEW,this.onParcelShowPreview.bind(t
this.onDrawBufferRadiusChange.bind(this));this.client.on(netgis.Events.DRAW_BUFFER_SEGMENTS_CHANGE,this.onDrawBufferSegmentsChange.bind(this))}; this.onDrawBufferRadiusChange.bind(this));this.client.on(netgis.Events.DRAW_BUFFER_SEGMENTS_CHANGE,this.onDrawBufferSegmentsChange.bind(this))};
netgis.MapOpenLayers.prototype.initMap=function(a){"undefined"!==typeof proj4&&(proj4.defs(a.projections),proj4.defs("urn:ogc:def:crs:OGC:1.3:CRS84",proj4.defs("EPSG:4326")),ol.proj.proj4.register(proj4));this.view=new ol.View({projection:a.map.projection,center:a.map.center,minZoom:a.map.minZoom,maxZoom:a.map.maxZoom,zoom:a.map.zoom});this.setPadding(0,0,0,0);this.map=new ol.Map({target:this.container,view:this.view,pixelRatio:1,moveTolerance:5,controls:[]});a.map.scalebar&&(this.scalebar=new ol.control.ScaleLine({bar:!0}), netgis.MapOpenLayers.prototype.initMap=function(a){"undefined"!==typeof proj4&&(proj4.defs(a.projections),proj4.defs("urn:ogc:def:crs:OGC:1.3:CRS84",proj4.defs("EPSG:4326")),ol.proj.proj4.register(proj4));this.view=new ol.View({projection:a.map.projection,center:a.map.center,minZoom:a.map.minZoom,maxZoom:a.map.maxZoom,zoom:a.map.zoom});this.setPadding(0,0,0,0);this.map=new ol.Map({target:this.container,view:this.view,pixelRatio:1,moveTolerance:5,controls:[]});a.map.scalebar&&(this.scalebar=new ol.control.ScaleLine({bar:!0}),
this.map.addControl(this.scalebar));this.map.on("pointermove",this.onPointerMove.bind(this));this.map.on("click",this.onSingleClick.bind(this));this.map.on("movestart",this.onMoveStart.bind(this));this.map.on("moveend",this.onMoveEnd.bind(this));this.view.on("change:resolution",this.onChangeResolution.bind(this))};netgis.MapOpenLayers.prototype.initStyles=function(a){a=a.styles.select;this.hoverStyle=this.createStyle(a.fill,a.stroke,a.width);this.hoverStyle.setZIndex(1)}; this.map.addControl(this.scalebar));this.map.on("pointermove",this.onPointerMove.bind(this));this.map.on("click",this.onSingleClick.bind(this));this.map.on("movestart",this.onMoveStart.bind(this));this.map.on("moveend",this.onMoveEnd.bind(this));this.view.on("change:resolution",this.onChangeResolution.bind(this))};netgis.MapOpenLayers.prototype.initStyles=function(a){a=a.styles.select;this.hoverStyle=this.createStyle(a.fill,a.stroke,a.width);this.hoverStyle.setZIndex(1)};
netgis.MapOpenLayers.prototype.initDefaultLayers=function(){this.editLayer=new ol.layer.Vector({source:new ol.source.Vector({features:[]}),style:this.styleEdit.bind(this),zIndex:this.editLayerID});this.addLayer(netgis.Client.Layers.EDIT_LAYER,this.editLayer);this.previewLayer=new ol.layer.Vector({source:new ol.source.Vector({features:[]}),style:this.styleSketch.bind(this),zIndex:this.editLayerID+1E3});this.addLayer(netgis.Client.Layers.PREVIEW_LAYER,this.previewLayer);this.editEventsOn()}; netgis.MapOpenLayers.prototype.initDefaultLayers=function(){this.editLayer=new ol.layer.Vector({source:new ol.source.Vector({features:[]}),style:this.styleEdit.bind(this),zIndex:this.editLayerID});this.addLayer(netgis.Client.Layers.EDIT_LAYER,this.editLayer);this.previewLayer=new ol.layer.Vector({source:new ol.source.Vector({features:[]}),style:this.styleSketch.bind(this),zIndex:this.editLayerID+10});this.addLayer(netgis.Client.Layers.PREVIEW_LAYER,this.previewLayer);this.parcelLayer=new ol.layer.Vector({source:new ol.source.Vector({features:[]}),
netgis.MapOpenLayers.prototype.editEventsOn=function(){this.editLayer.getSource().on("addfeature",this.onEditLayerAdd.bind(this));this.editLayer.getSource().on("changefeature",this.onEditLayerChange.bind(this));this.editLayer.getSource().on("removefeature",this.onEditLayerRemove.bind(this))};netgis.MapOpenLayers.prototype.editEventsOff=function(){}; style:this.styleParcel.bind(this),zIndex:this.editLayerID+20});this.map.addLayer(this.parcelLayer);this.editEventsOn()};netgis.MapOpenLayers.prototype.editEventsOn=function(){this.editLayer.getSource().on("addfeature",this.onEditLayerAdd.bind(this));this.editLayer.getSource().on("changefeature",this.onEditLayerChange.bind(this));this.editLayer.getSource().on("removefeature",this.onEditLayerRemove.bind(this))};netgis.MapOpenLayers.prototype.editEventsOff=function(){};
netgis.MapOpenLayers.prototype.initInteractions=function(){this.interactions[netgis.Modes.VIEW]=[new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.PANNING]=this.interactions[netgis.Modes.VIEW];this.interactions[netgis.Modes.ZOOMING_IN]=this.interactions[netgis.Modes.VIEW];this.interactions[netgis.Modes.ZOOMING_OUT]=this.interactions[netgis.Modes.VIEW];this.interactions[netgis.Modes.DRAW_POINTS]=[new ol.interaction.Draw({type:"Point",source:this.editLayer.getSource(), netgis.MapOpenLayers.prototype.initInteractions=function(){this.interactions[netgis.Modes.VIEW]=[new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.PANNING]=this.interactions[netgis.Modes.VIEW];this.interactions[netgis.Modes.ZOOMING_IN]=this.interactions[netgis.Modes.VIEW];this.interactions[netgis.Modes.ZOOMING_OUT]=this.interactions[netgis.Modes.VIEW];this.interactions[netgis.Modes.DRAW_POINTS]=[new ol.interaction.Draw({type:"Point",source:this.editLayer.getSource(),
style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.DRAW_POINTS][0].on("drawend",this.onDrawPointsEnd.bind(this));this.interactions[netgis.Modes.DRAW_LINES]=[new ol.interaction.Draw({type:"LineString",source:this.editLayer.getSource(),style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.DRAW_LINES][0].on("drawend",this.onDrawLinesEnd.bind(this)); style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.DRAW_POINTS][0].on("drawend",this.onDrawPointsEnd.bind(this));this.interactions[netgis.Modes.DRAW_LINES]=[new ol.interaction.Draw({type:"LineString",source:this.editLayer.getSource(),style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.DRAW_LINES][0].on("drawend",this.onDrawLinesEnd.bind(this));
this.interactions[netgis.Modes.DRAW_POLYGONS]=[new ol.interaction.Draw({type:"Polygon",source:this.editLayer.getSource(),style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.CUT_FEATURE_BEGIN]=[new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.CUT_FEATURE_DRAW]=[new ol.interaction.Draw({type:"Polygon",style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom]; this.interactions[netgis.Modes.DRAW_POLYGONS]=[new ol.interaction.Draw({type:"Polygon",source:this.editLayer.getSource(),style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.CUT_FEATURE_BEGIN]=[new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];this.interactions[netgis.Modes.CUT_FEATURE_DRAW]=[new ol.interaction.Draw({type:"Polygon",style:this.styleSketch.bind(this)}),new ol.interaction.DragPan,new ol.interaction.MouseWheelZoom];
@ -135,7 +134,7 @@ netgis.MapOpenLayers.prototype.styleModify=function(a){var b=new ol.style.Style(
geometry:this.getGeometryPoints(a)});a=a.getGeometry();a instanceof ol.geom.Polygon&&(a=a.getArea(),b.setText(new ol.style.Text({text:[netgis.util.formatArea(a,!0),"4mm sans-serif"],font:this.labelFont,fill:new ol.style.Fill({color:this.config.styles.modify.stroke}),backgroundFill:new ol.style.Fill({color:"rgba( 255, 255, 255, 0.5 )"}),padding:[2,4,2,4]})));return[b,c]}; geometry:this.getGeometryPoints(a)});a=a.getGeometry();a instanceof ol.geom.Polygon&&(a=a.getArea(),b.setText(new ol.style.Text({text:[netgis.util.formatArea(a,!0),"4mm sans-serif"],font:this.labelFont,fill:new ol.style.Fill({color:this.config.styles.modify.stroke}),backgroundFill:new ol.style.Fill({color:"rgba( 255, 255, 255, 0.5 )"}),padding:[2,4,2,4]})));return[b,c]};
netgis.MapOpenLayers.prototype.styleSketch=function(a){var b=a.getGeometry(),c=new ol.style.Style({image:new ol.style.Circle({radius:this.config.styles.sketch.pointRadius,fill:new ol.style.Fill({color:this.config.styles.sketch.stroke})}),fill:new ol.style.Fill({color:this.config.styles.sketch.fill}),stroke:new ol.style.Stroke({color:this.config.styles.sketch.stroke,width:this.config.styles.sketch.strokeWidth})});b instanceof ol.geom.Polygon&&(b=b.getArea(),c.setText(new ol.style.Text({text:[netgis.util.formatArea(b, netgis.MapOpenLayers.prototype.styleSketch=function(a){var b=a.getGeometry(),c=new ol.style.Style({image:new ol.style.Circle({radius:this.config.styles.sketch.pointRadius,fill:new ol.style.Fill({color:this.config.styles.sketch.stroke})}),fill:new ol.style.Fill({color:this.config.styles.sketch.fill}),stroke:new ol.style.Stroke({color:this.config.styles.sketch.stroke,width:this.config.styles.sketch.strokeWidth})});b instanceof ol.geom.Polygon&&(b=b.getArea(),c.setText(new ol.style.Text({text:[netgis.util.formatArea(b,
!0),"4mm sans-serif"],font:this.labelFont,fill:new ol.style.Fill({color:this.config.styles.sketch.stroke}),backgroundFill:new ol.style.Fill({color:"rgba( 255, 255, 255, 0.5 )"}),padding:[2,4,2,4]})));a=new ol.style.Style({image:new ol.style.Circle({radius:this.config.styles.sketch.pointRadius,fill:new ol.style.Fill({color:this.config.styles.sketch.stroke})}),geometry:this.getGeometryPoints(a)});return[c,a]}; !0),"4mm sans-serif"],font:this.labelFont,fill:new ol.style.Fill({color:this.config.styles.sketch.stroke}),backgroundFill:new ol.style.Fill({color:"rgba( 255, 255, 255, 0.5 )"}),padding:[2,4,2,4]})));a=new ol.style.Style({image:new ol.style.Circle({radius:this.config.styles.sketch.pointRadius,fill:new ol.style.Fill({color:this.config.styles.sketch.stroke})}),geometry:this.getGeometryPoints(a)});return[c,a]};
netgis.MapOpenLayers.prototype.styleParcel=function(){return new ol.style.Style({fill:new ol.style.Fill({color:this.client.config.styles.parcel.fill}),stroke:new ol.style.Stroke({color:this.client.config.styles.parcel.stroke,width:this.client.config.styles.parcel.strokeWidth}),zIndex:99999})};netgis.MapOpenLayers.prototype.redrawVectorLayers=function(){this.map.getLayers().forEach(function(a,b,c){a instanceof ol.layer.Vector&&a.setStyle(a.getStyle())})}; netgis.MapOpenLayers.prototype.styleParcel=function(){return new ol.style.Style({fill:new ol.style.Fill({color:this.client.config.styles.parcel.fill}),stroke:new ol.style.Stroke({color:this.client.config.styles.parcel.stroke,width:this.client.config.styles.parcel.strokeWidth})})};netgis.MapOpenLayers.prototype.redrawVectorLayers=function(){this.map.getLayers().forEach(function(a,b,c){a instanceof ol.layer.Vector&&a.setStyle(a.getStyle())})};
netgis.MapOpenLayers.prototype.getGeometryPoints=function(a){var b=a.getGeometry();if(b instanceof ol.geom.LineString)return new ol.geom.MultiPoint(b.getCoordinates());if(b instanceof ol.geom.Polygon){a=[];b=b.getCoordinates();for(var c=0;c<b.length;c++)for(var d=b[c],e=0;e<d.length;e++)a.push(d[e]);return new ol.geom.MultiPoint(a)}if(b instanceof ol.geom.MultiPolygon){a=[];for(var f=b.getPolygons(),g=0;g<f.length;g++)for(b=f[g].getCoordinates(),c=0;c<b.length;c++)for(d=b[c],e=0;e<d.length;e++)a.push(d[e]); netgis.MapOpenLayers.prototype.getGeometryPoints=function(a){var b=a.getGeometry();if(b instanceof ol.geom.LineString)return new ol.geom.MultiPoint(b.getCoordinates());if(b instanceof ol.geom.Polygon){a=[];b=b.getCoordinates();for(var c=0;c<b.length;c++)for(var d=b[c],e=0;e<d.length;e++)a.push(d[e]);return new ol.geom.MultiPoint(a)}if(b instanceof ol.geom.MultiPolygon){a=[];for(var f=b.getPolygons(),g=0;g<f.length;g++)for(b=f[g].getCoordinates(),c=0;c<b.length;c++)for(d=b[c],e=0;e<d.length;e++)a.push(d[e]);
return new ol.geom.MultiPoint(a)}if(b instanceof ol.geom.MultiLineString){a=[];f=b.getPolygons();for(g=0;g<f.length;g++)for(b=f[g].getCoordinates(),c=0;c<b.length;c++)for(d=b[c],e=0;e<d.length;e++)a.push(d[e]);return new ol.geom.MultiPoint(a)}return b};netgis.MapOpenLayers.prototype.getActiveVectorLayers=function(){for(var a=[],b=this.map.getLayers().getArray(),c=this.layers,d=0;d<c.length;d++){var e=c[d];e instanceof ol.layer.Vector&&-1<b.indexOf(e)&&a.push(e)}return a}; return new ol.geom.MultiPoint(a)}if(b instanceof ol.geom.MultiLineString){a=[];f=b.getPolygons();for(g=0;g<f.length;g++)for(b=f[g].getCoordinates(),c=0;c<b.length;c++)for(d=b[c],e=0;e<d.length;e++)a.push(d[e]);return new ol.geom.MultiPoint(a)}return b};netgis.MapOpenLayers.prototype.getActiveVectorLayers=function(){for(var a=[],b=this.map.getLayers().getArray(),c=this.layers,d=0;d<c.length;d++){var e=c[d];e instanceof ol.layer.Vector&&-1<b.indexOf(e)&&a.push(e)}return a};
netgis.MapOpenLayers.prototype.setMode=function(a){switch(this.mode){case netgis.Modes.BUFFER_FEATURE_EDIT:this.onBufferCancel(null);break;case netgis.Modes.DRAW_POINTS:case netgis.Modes.DRAW_LINES:var b=this.drawBufferOn;this.onDrawBufferOff(null);this.drawBufferOn=b;break;case netgis.Modes.MODIFY_FEATURES:this.editLayer.setStyle(this.styleEdit.bind(this))}this.container.classList.remove("netgis-cursor-draw");this.container.classList.remove("netgis-clickable");switch(a){case netgis.Modes.DRAW_POINTS:this.container.classList.add("netgis-cursor-draw"); netgis.MapOpenLayers.prototype.setMode=function(a){switch(this.mode){case netgis.Modes.BUFFER_FEATURE_EDIT:this.onBufferCancel(null);break;case netgis.Modes.DRAW_POINTS:case netgis.Modes.DRAW_LINES:var b=this.drawBufferOn;this.onDrawBufferOff(null);this.drawBufferOn=b;break;case netgis.Modes.MODIFY_FEATURES:this.editLayer.setStyle(this.styleEdit.bind(this))}this.container.classList.remove("netgis-cursor-draw");this.container.classList.remove("netgis-clickable");switch(a){case netgis.Modes.DRAW_POINTS:this.container.classList.add("netgis-cursor-draw");
@ -154,10 +153,9 @@ netgis.MapOpenLayers.prototype.onAddServiceWMS=function(a){var b=this.createLaye
netgis.MapOpenLayers.prototype.onSetExtent=function(a){var b=ol.proj.fromLonLat([a.minx,a.miny],this.client.config.map.projection);a=ol.proj.fromLonLat([a.maxx,a.maxy],this.client.config.map.projection);this.view.fit([b[0],b[1],a[0],a[1]])};netgis.MapOpenLayers.prototype.onChangeZoom=function(a){this.view.animate({zoom:this.view.getZoom()+a,duration:200})};netgis.MapOpenLayers.prototype.onZoomWKT=function(a){a=(new ol.format.WKT).readGeometry(a);this.view.fit(a,{duration:300,padding:[40,40,40,40]})}; netgis.MapOpenLayers.prototype.onSetExtent=function(a){var b=ol.proj.fromLonLat([a.minx,a.miny],this.client.config.map.projection);a=ol.proj.fromLonLat([a.maxx,a.maxy],this.client.config.map.projection);this.view.fit([b[0],b[1],a[0],a[1]])};netgis.MapOpenLayers.prototype.onChangeZoom=function(a){this.view.animate({zoom:this.view.getZoom()+a,duration:200})};netgis.MapOpenLayers.prototype.onZoomWKT=function(a){a=(new ol.format.WKT).readGeometry(a);this.view.fit(a,{duration:300,padding:[40,40,40,40]})};
netgis.MapOpenLayers.prototype.onPointerMove=function(a){var b=a.pixel;a=a.coordinate;var c=null,d=null;this.map.forEachFeatureAtPixel(b,function(a,b){if(b){if(b.get("id")&&netgis.util.isString(b.get("id"))&&-1!==b.get("id").search("import-"))return!1;c=a;d=b;return!0}});switch(this.mode){default:return!0;case netgis.Modes.PARCEL_SELECT:case netgis.Modes.DELETE_FEATURES:case netgis.Modes.CUT_FEATURE_BEGIN:case netgis.Modes.BUFFER_FEATURE_BEGIN:break;case netgis.Modes.DRAW_POINTS:case netgis.Modes.DRAW_LINES:return this.updateDrawBufferPreview(), netgis.MapOpenLayers.prototype.onPointerMove=function(a){var b=a.pixel;a=a.coordinate;var c=null,d=null;this.map.forEachFeatureAtPixel(b,function(a,b){if(b){if(b.get("id")&&netgis.util.isString(b.get("id"))&&-1!==b.get("id").search("import-"))return!1;c=a;d=b;return!0}});switch(this.mode){default:return!0;case netgis.Modes.PARCEL_SELECT:case netgis.Modes.DELETE_FEATURES:case netgis.Modes.CUT_FEATURE_BEGIN:case netgis.Modes.BUFFER_FEATURE_BEGIN:break;case netgis.Modes.DRAW_POINTS:case netgis.Modes.DRAW_LINES:return this.updateDrawBufferPreview(),
!0}c!==this.hoverFeature&&(this.hoverFeature&&this.featureLeave(this.hoverFeature,this.hoverLayer,b,a),c&&this.featureEnter(c,d,b,a),this.redrawVectorLayers(),this.hoverFeature=c,this.hoverLayer=d);c&&this.featureHover(c,d,b,a)};netgis.MapOpenLayers.prototype.onPointerLeave=function(a){this.hoverFeature&&(this.featureLeave(this.hoverFeature,this.hoverLayer,[a.offsetX,a.offsetY],null),this.hoverLayer=this.hoverFeature=null)}; !0}c!==this.hoverFeature&&(this.hoverFeature&&this.featureLeave(this.hoverFeature,this.hoverLayer,b,a),c&&this.featureEnter(c,d,b,a),this.redrawVectorLayers(),this.hoverFeature=c,this.hoverLayer=d);c&&this.featureHover(c,d,b,a)};netgis.MapOpenLayers.prototype.onPointerLeave=function(a){this.hoverFeature&&(this.featureLeave(this.hoverFeature,this.hoverLayer,[a.offsetX,a.offsetY],null),this.hoverLayer=this.hoverFeature=null)};
netgis.MapOpenLayers.prototype.featureEnter=function(a,b,c,d){if(b){this.container.classList.add("netgis-clickable");var e=!1;b.get("id")===netgis.Client.Layers.EDIT_LAYER&&(e=!0);b.get("id")===netgis.Client.Layers.PARCEL_DISTRICTS&&(e=!0);b.get("id")===netgis.Client.Layers.PARCEL_FIELDS&&(e=!0);b.get("id")===netgis.Client.Layers.PARCEL_FEATURES&&(e=!0);e&&a.setStyle(this.hoverStyle);netgis.util.invoke(this.container,netgis.Events.MAP_FEATURE_ENTER,{pixel:c,coords:d,layer:b.get("id"),properties:a.getProperties()})}}; netgis.MapOpenLayers.prototype.featureEnter=function(a,b,c,d){b&&(this.container.classList.add("netgis-clickable"),b.get("id")!==netgis.Client.Layers.EDIT_LAYER&&-1===b.get("id").search("import-")&&a.setStyle(this.hoverStyle),netgis.util.invoke(this.container,netgis.Events.MAP_FEATURE_ENTER,{pixel:c,coords:d,layer:b.get("id"),properties:a.getProperties()}))};netgis.MapOpenLayers.prototype.featureHover=function(a,b,c,d){};
netgis.MapOpenLayers.prototype.featureHover=function(a,b,c,d){};netgis.MapOpenLayers.prototype.featureLeave=function(a,b,c,d){b&&(this.container.classList.remove("netgis-clickable"),b.get("id")!==netgis.Client.Layers.EDIT_LAYER&&a.setStyle(null),netgis.util.invoke(this.container,netgis.Events.MAP_FEATURE_LEAVE,{pixel:c,coords:d,layer:b.get("id"),properties:a.getProperties()}))}; netgis.MapOpenLayers.prototype.featureLeave=function(a,b,c,d){b&&(this.container.classList.remove("netgis-clickable"),b.get("id")!==netgis.Client.Layers.EDIT_LAYER&&a.setStyle(null),netgis.util.invoke(this.container,netgis.Events.MAP_FEATURE_LEAVE,{pixel:c,coords:d,layer:b.get("id"),properties:a.getProperties()}))};netgis.MapOpenLayers.prototype.findFeature=function(a,b,c){if(a=this.activeLayers[a]){a=a.getSource().getFeatures();for(var d=null,e=0;e<a.length;e++){var f=a[e];if(f.get(b)===c){d=f;break}}return d}};
netgis.MapOpenLayers.prototype.findFeature=function(a,b,c){if(a=this.activeLayers[a]){a=a.getSource().getFeatures();for(var d=null,e=0;e<a.length;e++){var f=a[e];if(f.get(b)===c){d=f;break}}return d}};netgis.MapOpenLayers.prototype.setFeatureHover=function(a,b){a.setStyle(b?this.hoverStyle:null)}; netgis.MapOpenLayers.prototype.setFeatureHover=function(a,b){a.setStyle(b?this.hoverStyle:null)};netgis.MapOpenLayers.prototype.onPointerMove_01=function(a){a=a.pixel;var b=this.hoverFeature,c=this;switch(this.mode){case netgis.Modes.DRAW_POINTS:case netgis.Modes.DRAW_LINES:this.updateDrawBufferPreview()}b=null;this.map.forEachFeatureAtPixel(a,function(a,e){e===c.editLayer&&(b=a);return!0});b!==this.hoverFeature&&this.redrawVectorLayers();this.hoverFeature=b};
netgis.MapOpenLayers.prototype.onPointerMove_01=function(a){a=a.pixel;var b=this.hoverFeature,c=this;switch(this.mode){case netgis.Modes.DRAW_POINTS:case netgis.Modes.DRAW_LINES:this.updateDrawBufferPreview()}b=null;this.map.forEachFeatureAtPixel(a,function(a,e){e===c.editLayer&&(b=a);return!0});b!==this.hoverFeature&&this.redrawVectorLayers();this.hoverFeature=b};
netgis.MapOpenLayers.prototype.onSingleClick=function(a){switch(this.mode){default:this.hoverFeature&&this.hoverLayer&&(a={pixel:a.pixel,coords:a.coordinate,layer:this.hoverLayer.get("id"),id:this.hoverFeature.getId(),properties:this.hoverFeature.getProperties()},netgis.util.invoke(this.container,netgis.Events.MAP_FEATURE_CLICK,a));break;case netgis.Modes.DELETE_FEATURES:this.hoverFeature&&(this.editLayer.getSource().removeFeature(this.hoverFeature),this.featureLeave(this.hoverFeature,this.editLayer), netgis.MapOpenLayers.prototype.onSingleClick=function(a){switch(this.mode){default:this.hoverFeature&&this.hoverLayer&&(a={pixel:a.pixel,coords:a.coordinate,layer:this.hoverLayer.get("id"),id:this.hoverFeature.getId(),properties:this.hoverFeature.getProperties()},netgis.util.invoke(this.container,netgis.Events.MAP_FEATURE_CLICK,a));break;case netgis.Modes.DELETE_FEATURES:this.hoverFeature&&(this.editLayer.getSource().removeFeature(this.hoverFeature),this.featureLeave(this.hoverFeature,this.editLayer),
this.hoverFeature=null,netgis.util.invoke(this.container,netgis.Events.SET_MODE,netgis.Modes.VIEW));break;case netgis.Modes.CUT_FEATURE_BEGIN:this.hoverFeature&&(this.selected=this.hoverFeature,netgis.util.invoke(this.container,netgis.Events.SET_MODE,netgis.Modes.CUT_FEATURE_DRAW));break;case netgis.Modes.BUFFER_FEATURE_BEGIN:this.hoverFeature&&(this.selected=this.hoverFeature,netgis.util.invoke(this.container,netgis.Events.SET_MODE,netgis.Modes.BUFFER_FEATURE_EDIT))}}; this.hoverFeature=null,netgis.util.invoke(this.container,netgis.Events.SET_MODE,netgis.Modes.VIEW));break;case netgis.Modes.CUT_FEATURE_BEGIN:this.hoverFeature&&(this.selected=this.hoverFeature,netgis.util.invoke(this.container,netgis.Events.SET_MODE,netgis.Modes.CUT_FEATURE_DRAW));break;case netgis.Modes.BUFFER_FEATURE_BEGIN:this.hoverFeature&&(this.selected=this.hoverFeature,netgis.util.invoke(this.container,netgis.Events.SET_MODE,netgis.Modes.BUFFER_FEATURE_EDIT))}};
netgis.MapOpenLayers.prototype.onMoveStart=function(a){};netgis.MapOpenLayers.prototype.onMoveEnd=function(a){};netgis.MapOpenLayers.prototype.onChangeResolution=function(a){}; netgis.MapOpenLayers.prototype.onMoveStart=function(a){};netgis.MapOpenLayers.prototype.onMoveEnd=function(a){};netgis.MapOpenLayers.prototype.onChangeResolution=function(a){};
@ -196,8 +194,8 @@ function(){var n=document.createElement("canvas");n.width=b;n.height=c;var p=n.g
t.setFillColor(255,255,255);t.rect(x,r+u-11,80,11,"F");t.setFontSize(8);t.text("Datum: "+netgis.util.getTimeStamp(),x+2,r+u-2-4);t.text("Quelle: "+window.location.href,x+2,r+u-2);n=t.output("bloburl",{filename:k.export.defaultFilename+".pdf"});window.open(n,"_blank");break;case "jpeg":window.navigator.msSaveBlob?window.navigator.msSaveBlob(n.msToBlob(),k.export.defaultFilename+".jpg"):(q.setAttribute("download",k.export.defaultFilename+".jpg"),q.setAttribute("href",n.toDataURL("image/jpeg",1)),q.click()); t.setFillColor(255,255,255);t.rect(x,r+u-11,80,11,"F");t.setFontSize(8);t.text("Datum: "+netgis.util.getTimeStamp(),x+2,r+u-2-4);t.text("Quelle: "+window.location.href,x+2,r+u-2);n=t.output("bloburl",{filename:k.export.defaultFilename+".pdf"});window.open(n,"_blank");break;case "jpeg":window.navigator.msSaveBlob?window.navigator.msSaveBlob(n.msToBlob(),k.export.defaultFilename+".jpg"):(q.setAttribute("download",k.export.defaultFilename+".jpg"),q.setAttribute("href",n.toDataURL("image/jpeg",1)),q.click());
break;case "png":window.navigator.msSaveBlob?window.navigator.msSaveBlob(n.msToBlob(),k.export.defaultFilename+".png"):(q.setAttribute("download",k.export.defaultFilename+".png"),q.setAttribute("href",n.toDataURL("image/png",1)),q.click());break;case "gif":q.setAttribute("download",k.export.defaultFilename+".gif"),v=new GIF({workerScript:k.export.gifWebWorker,quality:1}),v.addFrame(n),v.on("finished",function(a){q.setAttribute("href",window.URL.createObjectURL(a));q.click()}),v.render()}h.setTarget(g); break;case "png":window.navigator.msSaveBlob?window.navigator.msSaveBlob(n.msToBlob(),k.export.defaultFilename+".png"):(q.setAttribute("download",k.export.defaultFilename+".png"),q.setAttribute("href",n.toDataURL("image/png",1)),q.click());break;case "gif":q.setAttribute("download",k.export.defaultFilename+".gif"),v=new GIF({workerScript:k.export.gifWebWorker,quality:1}),v.addFrame(n),v.on("finished",function(a){q.setAttribute("href",window.URL.createObjectURL(a));q.click()}),v.render()}h.setTarget(g);
g.removeChild(m);netgis.util.invoke(f.container,netgis.Events.EXPORT_END,null)});h.renderSync()};l.src=k.export.logo};netgis.MapOpenLayers.prototype.splitMultiPolygons=function(a){a=a.getSource();for(var b=a.getFeatures(),c=[],d=[],e=0;e<b.length;e++){var f=b[e],g=f.getGeometry();if(g instanceof ol.geom.MultiPolygon){g=g.getPolygons();for(var h=0;h<g.length;h++){var k=new ol.Feature({geometry:g[h]});d.push(k)}c.push(f)}}for(e=0;e<c.length;e++)a.removeFeature(c[e]);a.addFeatures(d)};netgis=netgis||{};netgis.Menu=function(){this.root=this.client=null;this.sections=[]}; g.removeChild(m);netgis.util.invoke(f.container,netgis.Events.EXPORT_END,null)});h.renderSync()};l.src=k.export.logo};netgis.MapOpenLayers.prototype.splitMultiPolygons=function(a){a=a.getSource();for(var b=a.getFeatures(),c=[],d=[],e=0;e<b.length;e++){var f=b[e],g=f.getGeometry();if(g instanceof ol.geom.MultiPolygon){g=g.getPolygons();for(var h=0;h<g.length;h++){var k=new ol.Feature({geometry:g[h]});d.push(k)}c.push(f)}}for(e=0;e<c.length;e++)a.removeFeature(c[e]);a.addFeatures(d)};netgis=netgis||{};netgis.Menu=function(){this.root=this.client=null;this.sections=[]};
netgis.Menu.prototype.load=function(){this.root=document.createElement("header");this.root.className="netgis-menu netgis-color-a netgis-shadow";var a=document.createElement("div");this.root.appendChild(a);var b=this.createButton('<i class="fab fa-buffer"></i><span>Inhalte</span>',!0);b.addEventListener("click",this.onToggleClick.bind(this));a.appendChild(b);!0===this.client.editable&&(b=this.createButton('<i class="fas fa-tools"></i><span>Werkzeuge</span>',!0),b.addEventListener("click",this.onToolboxClick.bind(this)), netgis.Menu.prototype.load=function(){this.root=document.createElement("header");this.root.className="netgis-menu netgis-color-a netgis-shadow";var a=document.createElement("div");this.root.appendChild(a);var b=this.createButton('<i class="fab fa-buffer"></i><span>Inhalte</span>',!0);b.addEventListener("click",this.onToggleClick.bind(this));a.appendChild(b);b=this.createButton('<i class="fas fa-tools"></i><span>Werkzeuge</span>',!0);b.addEventListener("click",this.onToolboxClick.bind(this));a.appendChild(b);
a.appendChild(b));b=this.createButton('<i class="fas fa-vector-square"></i><span>Flurst\u00fccke</span>',!0);b.addEventListener("click",this.onSearchParcelClick.bind(this));a.appendChild(b);b=this.createButton('<i class="fas fa-search"></i><span>Suche</span>',!0);b.addEventListener("click",this.onSearchPlaceClick.bind(this));a.appendChild(b);this.client.editable&&(a=this.createMenu('<i class="fas fa-caret-down"></i>Zeichnen').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-map-marker-alt"></i>Punkte', b=this.createButton('<i class="fas fa-vector-square"></i><span>Flurst\u00fccke</span>',!0);b.addEventListener("click",this.onSearchParcelClick.bind(this));a.appendChild(b);b=this.createButton('<i class="fas fa-search"></i><span>Suche</span>',!0);b.addEventListener("click",this.onSearchPlaceClick.bind(this));a.appendChild(b);this.client.editable&&(a=this.createMenu('<i class="fas fa-caret-down"></i>Zeichnen').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-map-marker-alt"></i>Punkte',
this.onDrawPointClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-minus"></i>Linien',this.onDrawLineClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-vector-square"></i>Polygone',this.onDrawPolygonClick.bind(this))),a=this.createMenu('<i class="fas fa-caret-down"></i>Bearbeiten').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-cut"></i>Ausschneiden',this.onCutFeatureClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-arrows-alt"></i>Verschieben', this.onDrawPointClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-minus"></i>Linien',this.onDrawLineClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-vector-square"></i>Polygone',this.onDrawPolygonClick.bind(this))),a=this.createMenu('<i class="fas fa-caret-down"></i>Bearbeiten').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-cut"></i>Ausschneiden',this.onCutFeatureClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-arrows-alt"></i>Verschieben',
this.onModifyFeaturesClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-eraser"></i>L\u00f6schen',this.onDeleteFeaturesClick.bind(this))),a.appendChild(this.createMenuItem('<i class="far fa-dot-circle"></i>Puffern',this.onBufferFeatureClick.bind(this))),a=this.createMenu('<i class="fas fa-caret-down"></i>Import').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>GeoJSON',this.onImportGeoJSONClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>GML', this.onModifyFeaturesClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-eraser"></i>L\u00f6schen',this.onDeleteFeaturesClick.bind(this))),a.appendChild(this.createMenuItem('<i class="far fa-dot-circle"></i>Puffern',this.onBufferFeatureClick.bind(this))),a=this.createMenu('<i class="fas fa-caret-down"></i>Import').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>GeoJSON',this.onImportGeoJSONClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>GML',
this.onImportGMLClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>Shapefile',this.onImportShapefileClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>Spatialite',this.onImportSpatialiteClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>GeoPackage',this.onImportGeopackageClick.bind(this))),a=this.createMenu('<i class="fas fa-caret-down"></i>Export').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>PDF', this.onImportGMLClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>Shapefile',this.onImportShapefileClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>Spatialite',this.onImportSpatialiteClick.bind(this))),a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>GeoPackage',this.onImportGeopackageClick.bind(this))),a=this.createMenu('<i class="fas fa-caret-down"></i>Export').getElementsByTagName("ul")[0],a.appendChild(this.createMenuItem('<i class="fas fa-file"></i>PDF',
@ -220,7 +218,7 @@ l){k=e;m=!0;break}m||(e=c.folders.length,c.folders.push({title:h,parent:k,path:l
l[0];c.layers.push({folder:g,type:netgis.LayerTypes.XYZ,url:k.href,title:f.title,attribution:f.rights,active:f.active});break;case "http://www.opengis.net/spec/owc-geojson/1.0/req/osm":k=l[0],c.layers.push({folder:g,type:netgis.LayerTypes.XYZ,url:k.href,title:f.title,attribution:f.rights,active:f.active})}}b.invoke(netgis.Events.CONTEXT_UPDATE,c)}}}();netgis=netgis||{};netgis.Panel=function(a){this.initElements(a);this.initEvents()};netgis.Panel.prototype.initElements=function(a){var b=document.createElement("section");b.className="netgis-panel netgis-resize-right netgis-color-e netgis-shadow";this.addHeader(b,a,this.onHeaderClick.bind(this));this.container=b};netgis.Panel.prototype.initEvents=function(){this.resizeObserver=(new ResizeObserver(this.onResize.bind(this))).observe(this.container)};netgis.Panel.prototype.attach=function(a){a.appendChild(this.container)}; l[0];c.layers.push({folder:g,type:netgis.LayerTypes.XYZ,url:k.href,title:f.title,attribution:f.rights,active:f.active});break;case "http://www.opengis.net/spec/owc-geojson/1.0/req/osm":k=l[0],c.layers.push({folder:g,type:netgis.LayerTypes.XYZ,url:k.href,title:f.title,attribution:f.rights,active:f.active})}}b.invoke(netgis.Events.CONTEXT_UPDATE,c)}}}();netgis=netgis||{};netgis.Panel=function(a){this.initElements(a);this.initEvents()};netgis.Panel.prototype.initElements=function(a){var b=document.createElement("section");b.className="netgis-panel netgis-resize-right netgis-color-e netgis-shadow";this.addHeader(b,a,this.onHeaderClick.bind(this));this.container=b};netgis.Panel.prototype.initEvents=function(){this.resizeObserver=(new ResizeObserver(this.onResize.bind(this))).observe(this.container)};netgis.Panel.prototype.attach=function(a){a.appendChild(this.container)};
netgis.Panel.prototype.addHeader=function(a,b,c){var d=document.createElement("button");d.className="netgis-button netgis-clip-text netgis-color-c";d.innerHTML="<span>"+b+"</span><i class='netgis-icon fas fa-times'></i>";d.setAttribute("type","button");c&&(d.onclick=c);a&&a.appendChild(d);return d};netgis.Panel.prototype.show=function(){this.container.classList.add("netgis-show");netgis.util.invoke(this.container,netgis.Events.PANEL_TOGGLE,{panel:this,visible:!0})}; netgis.Panel.prototype.addHeader=function(a,b,c){var d=document.createElement("button");d.className="netgis-button netgis-clip-text netgis-color-c";d.innerHTML="<span>"+b+"</span><i class='netgis-icon fas fa-times'></i>";d.setAttribute("type","button");c&&(d.onclick=c);a&&a.appendChild(d);return d};netgis.Panel.prototype.show=function(){this.container.classList.add("netgis-show");netgis.util.invoke(this.container,netgis.Events.PANEL_TOGGLE,{panel:this,visible:!0})};
netgis.Panel.prototype.hide=function(){this.container.classList.remove("netgis-show");netgis.util.invoke(this.container,netgis.Events.PANEL_TOGGLE,{panel:this,visible:!1})};netgis.Panel.prototype.toggle=function(){this.container.classList.toggle("netgis-show");netgis.util.invoke(this.container,netgis.Events.PANEL_TOGGLE,{panel:this,visible:this.container.classList.contains("netgis-show")})};netgis.Panel.prototype.visible=function(){return this.container.classList.contains("netgis-show")}; netgis.Panel.prototype.hide=function(){this.container.classList.remove("netgis-show");netgis.util.invoke(this.container,netgis.Events.PANEL_TOGGLE,{panel:this,visible:!1})};netgis.Panel.prototype.toggle=function(){this.container.classList.toggle("netgis-show");netgis.util.invoke(this.container,netgis.Events.PANEL_TOGGLE,{panel:this,visible:this.container.classList.contains("netgis-show")})};netgis.Panel.prototype.visible=function(){return this.container.classList.contains("netgis-show")};
netgis.Panel.prototype.width=function(){return this.container.getBoundingClientRect().width};netgis.Panel.prototype.onHeaderClick=function(a){this.hide()};netgis.Panel.prototype.onResize=function(a){this.container.classList.contains("netgis-show")&&(a=this.container.getBoundingClientRect(),netgis.util.invoke(this.container,netgis.Events.PANEL_RESIZE,{width:a.width}))};netgis=netgis||{};netgis.SearchParcel=function(a,b){this.client=b;this.config=a;this.initElements();this.initEvents()};netgis.SearchParcel.Events=Object.freeze({TOGGLE:"search-parcel-toggle"}); netgis.Panel.prototype.width=function(){return this.container.getBoundingClientRect().width};netgis.Panel.prototype.onHeaderClick=function(a){this.hide()};netgis.Panel.prototype.onResize=function(a){this.container.classList.contains("netgis-show")&&(a=this.container.getBoundingClientRect(),netgis.util.invoke(this.container,netgis.Events.PANEL_RESIZE,{width:a.width}))};netgis=netgis||{};netgis.SearchParcel=function(a){this.config=a;this.initElements();this.initEvents()};netgis.SearchParcel.Events=Object.freeze({TOGGLE:"search-parcel-toggle"});
netgis.SearchParcel.prototype.initElements=function(){this.container=document.createElement("section");this.container.className="netgis-search-parcel";var a=document.createElement("section");this.top=a;this.container.appendChild(a);var b=this.createInput("Gemarkungsname:");b.style.position="relative";a.appendChild(b);this.nameInput=b.children[0];this.nameInput.setAttribute("title","ENTER: Ausw\u00e4hlen, ESCAPE: Zur\u00fccksetzen");this.nameInput.addEventListener("keyup",this.onInputNameKey.bind(this)); netgis.SearchParcel.prototype.initElements=function(){this.container=document.createElement("section");this.container.className="netgis-search-parcel";var a=document.createElement("section");this.top=a;this.container.appendChild(a);var b=this.createInput("Gemarkungsname:");b.style.position="relative";a.appendChild(b);this.nameInput=b.children[0];this.nameInput.setAttribute("title","ENTER: Ausw\u00e4hlen, ESCAPE: Zur\u00fccksetzen");this.nameInput.addEventListener("keyup",this.onInputNameKey.bind(this));
this.nameLoader=document.createElement("div");this.nameLoader.className="netgis-loader netgis-text-primary netgis-hide";this.nameLoader.innerHTML="<i class='fas fa-spinner'></i>";b.appendChild(this.nameLoader);this.nameList=document.createElement("ul");a.appendChild(this.nameList);b=this.createInput("Gemarkungsnummer:");this.districtInput=b.children[0];a.appendChild(b);b=this.createInput("Flurnummer:");this.fieldInput=b.children[0];this.fieldInput.addEventListener("keyup",this.onInputFieldKey.bind(this)); this.nameLoader=document.createElement("div");this.nameLoader.className="netgis-loader netgis-text-primary netgis-hide";this.nameLoader.innerHTML="<i class='fas fa-spinner'></i>";b.appendChild(this.nameLoader);this.nameList=document.createElement("ul");a.appendChild(this.nameList);b=this.createInput("Gemarkungsnummer:");this.districtInput=b.children[0];a.appendChild(b);b=this.createInput("Flurnummer:");this.fieldInput=b.children[0];this.fieldInput.addEventListener("keyup",this.onInputFieldKey.bind(this));
a.appendChild(b);b=this.createInput("<span>Flurst\u00fccksnummer (Z\u00e4hler/Nenner):</span>");this.parcelInputA=b.children[1];this.parcelInputA.style.width="48%";this.parcelInputB=this.parcelInputA.cloneNode(!0);this.parcelInputB.style.marginLeft="4%";b.appendChild(this.parcelInputB);a.appendChild(b);b=document.createElement("button");b.setAttribute("type","button");b.addEventListener("click",this.onParcelSearchClick.bind(this));b.className="netgis-color-a netgis-hover-c";b.innerHTML="Flurst\u00fccke suchen"; a.appendChild(b);b=this.createInput("<span>Flurst\u00fccksnummer (Z\u00e4hler/Nenner):</span>");this.parcelInputA=b.children[1];this.parcelInputA.style.width="48%";this.parcelInputB=this.parcelInputA.cloneNode(!0);this.parcelInputB.style.marginLeft="4%";b.appendChild(this.parcelInputB);a.appendChild(b);b=document.createElement("button");b.setAttribute("type","button");b.addEventListener("click",this.onParcelSearchClick.bind(this));b.className="netgis-color-a netgis-hover-c";b.innerHTML="Flurst\u00fccke suchen";
@ -231,8 +229,8 @@ netgis.SearchParcel.prototype.attach=function(a){a.appendChild(this.container)};
netgis.SearchParcel.prototype.createNameItem=function(a){var b=document.createElement("li"),c=document.createElement("button");c.setAttribute("type","button");c.addEventListener("click",this.onNameItemClick.bind(this));c.className="netgis-color-e netgis-hover-a netgis-text-a netgis-hover-text-e";c.innerHTML=a;b.appendChild(c);return b}; netgis.SearchParcel.prototype.createNameItem=function(a){var b=document.createElement("li"),c=document.createElement("button");c.setAttribute("type","button");c.addEventListener("click",this.onNameItemClick.bind(this));c.className="netgis-color-e netgis-hover-a netgis-text-a netgis-hover-text-e";c.innerHTML=a;b.appendChild(c);return b};
netgis.SearchParcel.prototype.createTable=function(a){var b=document.createElement("div");b.className="netgis-table-wrapper";var c=document.createElement("table");b.appendChild(c);var d=document.createElement("thead");c.appendChild(d);var e=document.createElement("tr");e.className="netgis-color-d netgis-shadow";d.appendChild(e);for(d=0;d<a.length;d++){var f=document.createElement("th");f.innerHTML=a[d];e.appendChild(f)}a=document.createElement("tbody");c.appendChild(a);return b}; netgis.SearchParcel.prototype.createTable=function(a){var b=document.createElement("div");b.className="netgis-table-wrapper";var c=document.createElement("table");b.appendChild(c);var d=document.createElement("thead");c.appendChild(d);var e=document.createElement("tr");e.className="netgis-color-d netgis-shadow";d.appendChild(e);for(d=0;d<a.length;d++){var f=document.createElement("th");f.innerHTML=a[d];e.appendChild(f)}a=document.createElement("tbody");c.appendChild(a);return b};
netgis.SearchParcel.prototype.createParcelItem=function(a,b,c,d,e,f,g){var h=document.createElement("tr");h.className="netgis-hover-light netgis-hover-text-primary";h.setAttribute("title","Klicken zum zoomen");h.setAttribute("data-id",d);h.setAttribute("data-field",a);h.setAttribute("data-parcel-a",b);h.setAttribute("data-parcel-b",c);h.setAttribute("data-bbox",f);h.setAttribute("data-geom",g);h.addEventListener("pointerenter",this.onParcelEnter.bind(this));h.addEventListener("pointerleave",this.onParcelLeave.bind(this)); netgis.SearchParcel.prototype.createParcelItem=function(a,b,c,d,e,f,g){var h=document.createElement("tr");h.className="netgis-hover-light netgis-hover-text-primary";h.setAttribute("title","Klicken zum zoomen");h.setAttribute("data-id",d);h.setAttribute("data-field",a);h.setAttribute("data-parcel-a",b);h.setAttribute("data-parcel-b",c);h.setAttribute("data-bbox",f);h.setAttribute("data-geom",g);h.addEventListener("pointerenter",this.onParcelEnter.bind(this));h.addEventListener("pointerleave",this.onParcelLeave.bind(this));
h.addEventListener("click",this.onParcelClick.bind(this));f=document.createElement("td");h.appendChild(f);this.client.editable&&(g=document.createElement("button"),g.setAttribute("type","button"),g.setAttribute("title","Geometrie \u00fcbernehmen"),g.addEventListener("click",this.onParcelImportClick.bind(this)),g.className="netgis-text-primary netgis-hover-primary",g.innerHTML="<i class='fas fa-paste'></i>",f.appendChild(g));f=document.createElement("td");f.innerHTML=a;h.appendChild(f);a=document.createElement("td"); h.addEventListener("click",this.onParcelClick.bind(this));f=document.createElement("td");h.appendChild(f);g=document.createElement("button");g.setAttribute("type","button");g.setAttribute("title","Geometrie \u00fcbernehmen");g.addEventListener("click",this.onParcelImportClick.bind(this));g.className="netgis-text-primary netgis-hover-primary";g.innerHTML="<i class='fas fa-paste'></i>";f.appendChild(g);f=document.createElement("td");f.innerHTML=a;h.appendChild(f);a=document.createElement("td");a.innerHTML=
a.innerHTML=b;h.appendChild(a);b=document.createElement("td");b.innerHTML=c;h.appendChild(b);c=document.createElement("td");c.innerHTML=d;h.appendChild(c);d=document.createElement("td");d.innerHTML=e;h.appendChild(d);return h}; b;h.appendChild(a);b=document.createElement("td");b.innerHTML=c;h.appendChild(b);c=document.createElement("td");c.innerHTML=d;h.appendChild(c);d=document.createElement("td");d.innerHTML=e;h.appendChild(d);return h};
netgis.SearchParcel.prototype.reset=function(){this.hideBottom();this.nameLoader.classList.add("netgis-hide");this.nameInput.value="";this.districtInput.value="";this.fieldInput.value="";this.parcelInputA.value="";this.parcelInputB.value="";this.nameList.innerHTML="";this.parcelInfo.innerHTML="";this.parcelList.innerHTML="";this.parcelTable.classList.add("netgis-hide");this.parcelReset.classList.add("netgis-hide");this.parcelCount.innerHTML="";var a=this;window.setTimeout(function(){a.top.scrollTop= netgis.SearchParcel.prototype.reset=function(){this.hideBottom();this.nameLoader.classList.add("netgis-hide");this.nameInput.value="";this.districtInput.value="";this.fieldInput.value="";this.parcelInputA.value="";this.parcelInputB.value="";this.nameList.innerHTML="";this.parcelInfo.innerHTML="";this.parcelList.innerHTML="";this.parcelTable.classList.add("netgis-hide");this.parcelReset.classList.add("netgis-hide");this.parcelCount.innerHTML="";var a=this;window.setTimeout(function(){a.top.scrollTop=
0;a.parcelTable.scrollTop=0},10);netgis.util.invoke(this.container,netgis.Events.PARCELS_RESET,null)};netgis.SearchParcel.prototype.onInputNameKey=function(a){switch(a.keyCode){case 13:this.selectFirstName();break;case 27:this.reset();break;default:this.requestName(this.nameInput.value.trim())}}; 0;a.parcelTable.scrollTop=0},10);netgis.util.invoke(this.container,netgis.Events.PARCELS_RESET,null)};netgis.SearchParcel.prototype.onInputNameKey=function(a){switch(a.keyCode){case 13:this.selectFirstName();break;case 27:this.reset();break;default:this.requestName(this.nameInput.value.trim())}};
netgis.SearchParcel.prototype.requestName=function(a){this.nameDebounce&&window.clearTimeout(this.nameDebounce);if(0!==a.length){var b=this.config.searchParcel.nameURL;b=netgis.util.replace(b,"{q}",window.encodeURIComponent(a));this.nameDebounce=window.setTimeout(this.onInputNameDebounce.bind(this,b),200);this.nameLoader.classList.remove("netgis-hide")}};netgis.SearchParcel.prototype.onInputNameDebounce=function(a){netgis.util.request(a,this.onInputNameResponse.bind(this))}; netgis.SearchParcel.prototype.requestName=function(a){this.nameDebounce&&window.clearTimeout(this.nameDebounce);if(0!==a.length){var b=this.config.searchParcel.nameURL;b=netgis.util.replace(b,"{q}",window.encodeURIComponent(a));this.nameDebounce=window.setTimeout(this.onInputNameDebounce.bind(this,b),200);this.nameLoader.classList.remove("netgis-hide")}};netgis.SearchParcel.prototype.onInputNameDebounce=function(a){netgis.util.request(a,this.onInputNameResponse.bind(this))};

View File

@ -29,7 +29,6 @@ class UserAdmin(admin.ModelAdmin):
"is_staff", "is_staff",
"is_superuser", "is_superuser",
"api_token", "api_token",
"oauth_token",
"groups", "groups",
"notifications", "notifications",
"date_joined", "date_joined",

View File

@ -17,11 +17,10 @@ class ShareUserAutocomplete(Select2QuerySetView):
""" """
def get_queryset(self): def get_queryset(self):
qs = User.objects.none()
if self.request.user.is_anonymous: if self.request.user.is_anonymous:
return qs return User.objects.none()
qs = User.objects.all()
if self.q: if self.q:
qs = User.objects.all()
# Due to privacy concerns only a full username match will return the proper user entry # Due to privacy concerns only a full username match will return the proper user entry
qs = qs.filter( qs = qs.filter(
Q(username=self.q) | Q(username=self.q) |
@ -42,13 +41,13 @@ class ShareTeamAutocomplete(Select2QuerySetView):
""" """
def get_queryset(self): def get_queryset(self):
qs = Team.objects.none()
if self.request.user.is_anonymous: if self.request.user.is_anonymous:
return qs return Team.objects.none()
qs = Team.objects.filter(
deleted__isnull=True
)
if self.q: if self.q:
qs = Team.objects.filter( # Due to privacy concerns only a full username match will return the proper user entry
deleted__isnull=True
)
q_parts = self.q.split(" ") q_parts = self.q.split(" ")
q = Q() q = Q()
for part in q_parts: for part in q_parts:

View File

@ -1,49 +0,0 @@
"""
Author: Michel Peltriaux
Created on: 08.01.25
"""
from django import forms
from django.utils.translation import gettext_lazy as _
from api.models import APIUserToken
from konova.forms.modals import BaseModalForm
from konova.utils.mailer import Mailer
class NewAPITokenModalForm(BaseModalForm):
confirm = forms.BooleanField(
label=_("Confirm"),
label_suffix=_(""),
widget=forms.CheckboxInput(),
required=True,
)
def __init__(self, *args, **kwargs):
self.template = "modal/modal_form.html"
super().__init__(*args, **kwargs)
self.form_title = _("Generate API Token")
self.form_caption = ""
if self.__user_has_api_token():
self.form_caption = _("You are about to create a new API token. The existing one will not be usable afterwards.")
self.form_caption += "\n"
self.form_caption += _("A new token needs to be validated by an administrator!")
# Disable automatic w-100 setting for this type of modal form. Looks kinda strange
self.fields["confirm"].widget.attrs["class"] = ""
def __user_has_api_token(self):
return self.instance.api_token is not None
def save(self):
user = self.instance
if user.api_token is not None:
user.api_token.delete()
user.api_token = APIUserToken.objects.create()
user.save()
mailer = Mailer()
mailer.send_mail_verify_api_token(user)
return user.api_token

View File

@ -66,3 +66,48 @@ class UserNotificationForm(BaseForm):
id__in=selected_notification_ids, id__in=selected_notification_ids,
) )
self.user.notifications.set(notifications) self.user.notifications.set(notifications)
class UserAPITokenForm(BaseForm):
token = forms.CharField(
label=_("Token"),
label_suffix="",
max_length=255,
required=True,
help_text=_("Generated automatically - not editable"),
widget=GenerateInput(
attrs={
"class": "form-control",
"url": reverse_lazy("api:generate-new-token"),
}
)
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_title = _("Create new token")
self.form_caption = _("A new token needs to be validated by an administrator!")
self.action_url = reverse("user:api-token")
self.cancel_redirect = reverse("user:index")
# Make direct token editing by user impossible. Instead set the proper url for generating a new token
self.initialize_form_field("token", None)
self.fields["token"].widget.attrs["readonly"] = True
def save(self):
""" Saves the form data
Returns:
api_token (APIUserToken)
"""
user = self.instance
new_token = self.cleaned_data["token"]
if user.api_token is not None:
user.api_token.delete()
new_token = APIUserToken.objects.create(
token=new_token
)
user.api_token = new_token
user.save()
return new_token

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.4 on 2024-04-30 07:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0003_oauthtoken'),
('user', '0008_alter_user_id'),
]
operations = [
migrations.AddField(
model_name='user',
name='oauth_token',
field=models.ForeignKey(blank=True, db_comment='OAuth token for the user', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='api.oauthtoken'),
),
]

View File

@ -9,7 +9,7 @@ from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from api.models import APIUserToken, OAuthToken from api.models import APIUserToken
from konova.settings import ZB_GROUP, DEFAULT_GROUP, ETS_GROUP from konova.settings import ZB_GROUP, DEFAULT_GROUP, ETS_GROUP
from konova.utils.mailer import Mailer from konova.utils.mailer import Mailer
from user.enums import UserNotificationEnum from user.enums import UserNotificationEnum
@ -24,14 +24,6 @@ class User(AbstractUser):
help_text="The user's API token", help_text="The user's API token",
on_delete=models.SET_NULL on_delete=models.SET_NULL
) )
oauth_token = models.ForeignKey(
"api.OAuthToken",
blank=True,
null=True,
on_delete=models.SET_NULL,
db_comment="OAuth token for the user",
related_name="+"
)
def is_notification_setting_set(self, notification_enum: UserNotificationEnum): def is_notification_setting_set(self, notification_enum: UserNotificationEnum):
return self.notifications.filter( return self.notifications.filter(
@ -222,46 +214,4 @@ class User(AbstractUser):
shared_teams = self.teams.filter( shared_teams = self.teams.filter(
deleted__isnull=True deleted__isnull=True
) )
return shared_teams return shared_teams
@staticmethod
def oauth_update_user(user_data: dict):
"""
Get or create a user depending on given user_data.
If the user record already exists, it's data will be updated using user_data.
Args:
user_data (dict): User data from OAuth SSO component
Returns:
user (User): The resolved user
"""
username = user_data.get("username")
user, is_created = User.objects.get_or_create(
username=username
)
if is_created:
user.set_unusable_password()
user.first_name = user_data.get("first_name")
user.last_name = user_data.get("last_name")
user.email = user_data.get("email")
return user
def oauth_replace_token(self, token: OAuthToken):
"""
Drops old token (if existing) and stores given token.
Args:
token (OAuthToken): New token
Returns:
user (User)
"""
if self.oauth_token:
self.oauth_token.delete()
self.oauth_token = token
self.save()
return self

View File

@ -8,16 +8,7 @@
<table class="table table-hover"> <table class="table table-hover">
<tr> <tr>
<th scope="row">{% trans 'Current token' %}</th> <th scope="row">{% trans 'Current token' %}</th>
<td> <td>{{ user.api_token.token }}</td>
<div class="row">
<div class="col-10">{{ user.api_token.token }}</div>
<div class="col-2">
<button class="btn btn-default btn-modal" data-form-url="{% url 'user:api-token-new' %}" title="{% trans 'Create new token' %}">
{% fa5_icon 'dice' %}
</button>
</div>
</div>
</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Authenticated by admins' %}</th> <th scope="row">{% trans 'Authenticated by admins' %}</th>
@ -36,9 +27,7 @@
</table> </table>
</div> </div>
</div> </div>
<hr>
{% with 'btn-modal' as btn_class %} {% include 'form/table/generic_table_form.html' %}
{% include 'modal/modal_form_script.html' %}
{% endwith %}
{% endblock %} {% endblock %}

Some files were not shown because too many files have changed in this diff Show More