Compare commits
No commits in common. "master" and "1.9.2" have entirely different histories.
@ -24,7 +24,6 @@ DEFAULT_FROM_EMAIL=service@ksp.de
|
|||||||
|
|
||||||
# Proxy
|
# Proxy
|
||||||
PROXY=CHANGE_ME
|
PROXY=CHANGE_ME
|
||||||
MAP_PROXY_HOST_WHITELIST=CHANGE_ME_1,CHANGE_ME_2
|
|
||||||
GEOPORTAL_RLP_USER=CHANGE_ME
|
GEOPORTAL_RLP_USER=CHANGE_ME
|
||||||
GEOPORTAL_RLP_PASSWORD=CHANGE_ME
|
GEOPORTAL_RLP_PASSWORD=CHANGE_ME
|
||||||
|
|
||||||
@ -38,7 +37,6 @@ SSO_SERVER_BASE_URL=https://login.naturschutz.rlp.de
|
|||||||
OAUTH_CODE_VERIFIER=CHANGE_ME
|
OAUTH_CODE_VERIFIER=CHANGE_ME
|
||||||
OAUTH_CLIENT_ID=CHANGE_ME
|
OAUTH_CLIENT_ID=CHANGE_ME
|
||||||
OAUTH_CLIENT_SECRET=CHANGE_ME
|
OAUTH_CLIENT_SECRET=CHANGE_ME
|
||||||
PROPAGATION_SECRET=CHANGE_ME
|
|
||||||
|
|
||||||
# RabbitMQ
|
# RabbitMQ
|
||||||
## For connections to EGON
|
## For connections to EGON
|
||||||
|
@ -51,7 +51,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@ -155,25 +155,3 @@ class OAuthToken(UuidModel):
|
|||||||
|
|
||||||
return user
|
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
|
|
||||||
|
|
||||||
|
@ -50,18 +50,13 @@ 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}")
|
||||||
|
else:
|
||||||
self.user = token_user
|
self.user = token_user
|
||||||
|
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
|
@ -82,8 +82,8 @@ class Command(BaseKonovaCommand):
|
|||||||
atom_id = element.find("atomid").text
|
atom_id = element.find("atomid").text
|
||||||
selectable = element.find("selectable").text.lower()
|
selectable = element.find("selectable").text.lower()
|
||||||
selectable = bool_map.get(selectable, False)
|
selectable = bool_map.get(selectable, False)
|
||||||
short_name = element.find("shortname").text or ""
|
short_name = element.find("shortname").text
|
||||||
long_name = element.find("longname").text or ""
|
long_name = element.find("longname").text
|
||||||
is_archived = bool_map.get((element.find("archive").text.lower()), False)
|
is_archived = bool_map.get((element.find("archive").text.lower()), False)
|
||||||
|
|
||||||
code = KonovaCode.objects.get_or_create(
|
code = KonovaCode.objects.get_or_create(
|
||||||
|
@ -1,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,
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.0.8 on 2024-08-26 16:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('codelist', '0003_alter_konovacode_long_name_and_more'),
|
|
||||||
('compensation', '0015_alter_compensation_after_states_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='compensationstate',
|
|
||||||
name='biotope_type_details',
|
|
||||||
field=models.ManyToManyField(blank=True, limit_choices_to={'code_lists__in': [288], 'is_archived': False, 'is_selectable': True}, related_name='+', to='codelist.konovacode'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -6,11 +6,11 @@ Created on: 26.10.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import zipfile
|
import zipfile
|
||||||
from datetime import datetime
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.datetime_safe import datetime
|
||||||
|
|
||||||
from analysis.utils.excel.excel import TempExcelFile
|
from analysis.utils.excel.excel import TempExcelFile
|
||||||
from analysis.utils.report import TimespanReport
|
from analysis.utils.report import TimespanReport
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
||||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
||||||
Created on: 04.01.22
|
|
||||||
|
|
||||||
"""
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.contrib.gis.db.models.functions import Area
|
|
||||||
|
|
||||||
from konova.management.commands.setup import BaseKonovaCommand
|
|
||||||
from konova.models import Geometry, ParcelIntersection
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseKonovaCommand):
|
|
||||||
help = "Recalculates parcels for entries with geometry but missing parcel information"
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument(
|
|
||||||
"--force-all",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="If Attribute set, all entries parcels will be recalculated"
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
try:
|
|
||||||
self.recalculate_parcels(options)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
self._break_line()
|
|
||||||
exit(-1)
|
|
||||||
|
|
||||||
def recalculate_parcels(self, options: dict):
|
|
||||||
force_all = options.get("force_all", False)
|
|
||||||
|
|
||||||
geometry_objects = Geometry.objects.filter(
|
|
||||||
geom__isempty=False,
|
|
||||||
).exclude(
|
|
||||||
geom=None
|
|
||||||
)
|
|
||||||
|
|
||||||
if not force_all:
|
|
||||||
# Fetch all intersections
|
|
||||||
intersection_objs = ParcelIntersection.objects.filter(
|
|
||||||
geometry__in=geometry_objects
|
|
||||||
)
|
|
||||||
# Just take the geometry ids, which seem to have intersections
|
|
||||||
geom_with_intersection_ids = intersection_objs.values_list(
|
|
||||||
"geometry__id",
|
|
||||||
flat=True
|
|
||||||
)
|
|
||||||
# ... and resolve into Geometry objects again ...
|
|
||||||
intersected_geom_objs = Geometry.objects.filter(
|
|
||||||
id__in=geom_with_intersection_ids
|
|
||||||
)
|
|
||||||
# ... to be able to use the way more efficient difference() function ...
|
|
||||||
geometry_objects_ids = geometry_objects.difference(intersected_geom_objs).values_list("id", flat=True)
|
|
||||||
# ... so we can resolve these into proper Geometry objects again for further annotation usage
|
|
||||||
geometry_objects = Geometry.objects.filter(id__in=geometry_objects_ids)
|
|
||||||
|
|
||||||
self._write_warning("=== Update parcels and districts ===")
|
|
||||||
# Order geometries by size to process smaller once at first
|
|
||||||
geometries = geometry_objects.annotate(
|
|
||||||
area=Area("geom")
|
|
||||||
).order_by(
|
|
||||||
'area'
|
|
||||||
)
|
|
||||||
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
|
|
||||||
i = 0
|
|
||||||
num_geoms = geometries.count()
|
|
||||||
geoms_with_errors = {}
|
|
||||||
for geometry in geometries:
|
|
||||||
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
|
|
||||||
try:
|
|
||||||
geometry.update_parcels()
|
|
||||||
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
|
|
||||||
except Exception as e:
|
|
||||||
geoms_with_errors[geometry.id] = str(e)
|
|
||||||
i += 1
|
|
||||||
self._write_warning(f"--- {i}/{num_geoms} processed")
|
|
||||||
|
|
||||||
self._write_success("Updating parcels done!")
|
|
||||||
|
|
||||||
for key, val in geoms_with_errors.items():
|
|
||||||
self._write_error(f" Error on {key}: {val}")
|
|
||||||
self._write_success(f"{num_geoms - len(geoms_with_errors)} geometries successfuly recalculated!")
|
|
||||||
self._break_line()
|
|
51
konova/management/commands/test_identifier_generating.py
Normal file
51
konova/management/commands/test_identifier_generating.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 19.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
|
from intervention.models import Intervention
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Performs test on collisions using the identifier generation"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
identifiers = {}
|
||||||
|
max_iterations = 100000
|
||||||
|
try:
|
||||||
|
collisions = 0
|
||||||
|
len_ids = len(identifiers)
|
||||||
|
while len_ids < max_iterations:
|
||||||
|
tmp_intervention = Intervention()
|
||||||
|
_id = tmp_intervention.generate_new_identifier()
|
||||||
|
len_ids = len(identifiers)
|
||||||
|
if _id not in identifiers:
|
||||||
|
if len_ids % (max_iterations/5) == 0:
|
||||||
|
print(len_ids)
|
||||||
|
identifiers[_id] = None
|
||||||
|
else:
|
||||||
|
collisions += 1
|
||||||
|
print("+++ Collision after {} identifiers +++".format(len_ids))
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._break_line()
|
||||||
|
exit(-1)
|
||||||
|
print(
|
||||||
|
"\n{} collisions in {} identifiers; Collision rate {}%".format(
|
||||||
|
collisions,
|
||||||
|
len_ids,
|
||||||
|
(collisions / len_ids)*100,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _break_line(self):
|
||||||
|
""" Simply prints a line break
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write("\n")
|
54
konova/management/commands/update_all_parcels.py
Normal file
54
konova/management/commands/update_all_parcels.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 04.01.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.contrib.gis.db.models.functions import Area
|
||||||
|
|
||||||
|
from konova.management.commands.setup import BaseKonovaCommand
|
||||||
|
from konova.models import Geometry, Parcel, District
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseKonovaCommand):
|
||||||
|
help = "Checks the database' sanity and removes unused entries"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
try:
|
||||||
|
self.update_all_parcels()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._break_line()
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
def update_all_parcels(self):
|
||||||
|
num_parcels_before = Parcel.objects.count()
|
||||||
|
num_districts_before = District.objects.count()
|
||||||
|
self._write_warning("=== Update parcels and districts ===")
|
||||||
|
# Order geometries by size to process smaller once at first
|
||||||
|
geometries = Geometry.objects.all().exclude(
|
||||||
|
geom=None
|
||||||
|
).annotate(area=Area("geom")).order_by(
|
||||||
|
'area'
|
||||||
|
)
|
||||||
|
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
|
||||||
|
i = 0
|
||||||
|
num_geoms = geometries.count()
|
||||||
|
for geometry in geometries:
|
||||||
|
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
|
||||||
|
geometry.update_parcels()
|
||||||
|
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
|
||||||
|
i += 1
|
||||||
|
self._write_warning(f"--- {i}/{num_geoms} processed")
|
||||||
|
|
||||||
|
num_parcels_after = Parcel.objects.count()
|
||||||
|
num_districts_after = District.objects.count()
|
||||||
|
if num_parcels_after != num_parcels_before:
|
||||||
|
self._write_error(f"Parcels have changed: {num_parcels_before} to {num_parcels_after} entries. You should run the sanitize command.")
|
||||||
|
if num_districts_after != num_districts_before:
|
||||||
|
self._write_error(f"Districts have changed: {num_districts_before} to {num_districts_after} entries. You should run the sanitize command.")
|
||||||
|
|
||||||
|
self._write_success("Updating parcels done!")
|
||||||
|
self._break_line()
|
@ -8,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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -66,6 +66,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',
|
||||||
|
@ -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
|
|
@ -16,5 +16,3 @@ OAUTH_CODE_VERIFIER = env("OAUTH_CODE_VERIFIER")
|
|||||||
|
|
||||||
OAUTH_CLIENT_ID = env("OAUTH_CLIENT_ID")
|
OAUTH_CLIENT_ID = env("OAUTH_CLIENT_ID")
|
||||||
OAUTH_CLIENT_SECRET = env("OAUTH_CLIENT_SECRET")
|
OAUTH_CLIENT_SECRET = env("OAUTH_CLIENT_SECRET")
|
||||||
|
|
||||||
PROPAGATION_SECRET = env("PROPAGATION_SECRET")
|
|
||||||
|
@ -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
78
konova/utils/messenger.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 17.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from collections import Iterable
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from user.models import User
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.settings import SSO_SERVER_BASE, SSO_PUBLIC_KEY, PROXIES
|
||||||
|
from konova.sub_settings.context_settings import BASE_TITLE_SHORT
|
||||||
|
|
||||||
|
|
||||||
|
class Messenger:
|
||||||
|
""" Used to send messages to the SSO server.
|
||||||
|
|
||||||
|
Messages can be seen by the user the next time they login on their SSO dashboard.
|
||||||
|
Documentation for SSO Server-Client communication can be found here:
|
||||||
|
https://git.naturschutz.rlp.de/SGD-Nord/arnova/wiki/Messages
|
||||||
|
|
||||||
|
"""
|
||||||
|
server_url = "{}communication/message/".format(SSO_SERVER_BASE)
|
||||||
|
|
||||||
|
def __init__(self, users: Iterable, subject: str = None, body: str = None, type: str = None):
|
||||||
|
self.users = users
|
||||||
|
self.msg_subject = subject
|
||||||
|
self.msg_body = body
|
||||||
|
self.msg_type = type
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
""" Sends a message
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.msg_body is None or len(self.msg_body) == 0:
|
||||||
|
raise AttributeError("No message body set")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"x-services-public-key": SSO_PUBLIC_KEY
|
||||||
|
}
|
||||||
|
for user in self.users:
|
||||||
|
data = {
|
||||||
|
"type": self.msg_type,
|
||||||
|
"sender": BASE_TITLE_SHORT,
|
||||||
|
"receiver": user.username,
|
||||||
|
"subject": self.msg_subject,
|
||||||
|
"body": self.msg_body,
|
||||||
|
}
|
||||||
|
requests.post(
|
||||||
|
self.server_url,
|
||||||
|
data=data,
|
||||||
|
headers=headers,
|
||||||
|
proxies=PROXIES
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_object_checked(self, obj_identifier: str, performing_user: User, detail_view_url: str = ""):
|
||||||
|
""" Wraps sending of a message related to the checking of an object, like an intervention
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj_identifier (str): The object's identifier (e.g. 'EIV-123'
|
||||||
|
performing_user (User): The user who performed the checking
|
||||||
|
detail_view_url (str): If a direct link to the object shall be added to the message, it can be provided here
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.msg_subject = _("{} checked").format(obj_identifier)
|
||||||
|
if len(detail_view_url) > 0:
|
||||||
|
detail_view_url = _('<a href="{}">Check it out</a>').format(detail_view_url)
|
||||||
|
self.msg_body = _("{} has been checked successfully by user {}! {}").format(
|
||||||
|
obj_identifier,
|
||||||
|
performing_user.username,
|
||||||
|
detail_view_url
|
||||||
|
)
|
||||||
|
self.send()
|
@ -55,11 +55,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
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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,7 +18,6 @@ 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, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD
|
from konova.sub_settings.proxy_settings import PROXIES, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD
|
||||||
|
|
||||||
|
|
||||||
@ -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)
|
||||||
|
@ -115,10 +115,10 @@ class OAuthCallbackView(View):
|
|||||||
if status_code_invalid:
|
if status_code_invalid:
|
||||||
raise RuntimeError(f"OAuth access token could not be fetched: {access_code_response.text}")
|
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_access_token = OAuthToken.from_access_token_response(access_code_response_body, received_on)
|
||||||
oauth_token.save()
|
oauth_access_token.save()
|
||||||
user = oauth_token.update_and_get_user()
|
user = oauth_access_token.update_and_get_user()
|
||||||
user.oauth_replace_token(oauth_token)
|
user.oauth_replace_token(oauth_access_token)
|
||||||
|
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect("home")
|
return redirect("home")
|
||||||
|
Binary file not shown.
@ -38,14 +38,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-08-19 10:32+0200\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"
|
||||||
@ -374,6 +373,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"
|
||||||
|
|
||||||
@ -1795,7 +1795,8 @@ msgstr "Bearbeitender Nutzer"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"Search for entries where this person has been participated according to log "
|
"Search for entries where this person has been participated according to log "
|
||||||
"history"
|
"history"
|
||||||
msgstr "Sucht nach Einträgen, an denen diese Person gearbeitet hat"
|
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"
|
||||||
@ -1857,7 +1858,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"
|
||||||
|
|
||||||
@ -2283,10 +2283,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 +2321,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..."
|
||||||
@ -2835,8 +2831,10 @@ msgid "Reports"
|
|||||||
msgstr "Berichte"
|
msgstr "Berichte"
|
||||||
|
|
||||||
#: templates/navbars/navbar.html:57
|
#: templates/navbars/navbar.html:57
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Admins"
|
||||||
msgid "Admin"
|
msgid "Admin"
|
||||||
msgstr ""
|
msgstr "Administratoren"
|
||||||
|
|
||||||
#: templates/navbars/navbar.html:59 user/templates/user/index.html:31
|
#: templates/navbars/navbar.html:59 user/templates/user/index.html:31
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
@ -2875,21 +2873,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 +2895,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 +2928,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 +2949,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 +2981,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 +3051,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/views.py:171
|
||||||
msgid "Teams"
|
msgid "Teams"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3092,58 +3087,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/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/views.py:61
|
||||||
msgid "Notifications edited"
|
msgid "Notifications edited"
|
||||||
msgstr "Benachrichtigungen bearbeitet"
|
msgstr "Benachrichtigungen bearbeitet"
|
||||||
|
|
||||||
#: user/views/views.py:71
|
#: user/views/views.py:73
|
||||||
msgid "User notifications"
|
msgid "User notifications"
|
||||||
msgstr "Benachrichtigungen"
|
msgstr "Benachrichtigungen"
|
||||||
|
|
||||||
#: user/views/views.py:147
|
#: user/views/views.py:96
|
||||||
|
msgid "New token generated. Administrators need to validate."
|
||||||
|
msgstr "Neuer Token generiert. Administratoren sind informiert."
|
||||||
|
|
||||||
|
#: user/views/views.py:107
|
||||||
|
msgid "User API token"
|
||||||
|
msgstr "API Nutzer Token"
|
||||||
|
|
||||||
|
#: user/views/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/views.py:198
|
||||||
msgid "Team edited"
|
msgid "Team edited"
|
||||||
msgstr "Team bearbeitet"
|
msgstr "Team bearbeitet"
|
||||||
|
|
||||||
#: user/views/views.py:177
|
#: user/views/views.py:213
|
||||||
msgid "Team removed"
|
msgid "Team removed"
|
||||||
msgstr "Team gelöscht"
|
msgstr "Team gelöscht"
|
||||||
|
|
||||||
#: user/views/views.py:192
|
#: user/views/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/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"
|
||||||
|
@ -1,63 +1,65 @@
|
|||||||
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.0
|
||||||
certifi==2024.12.14
|
certifi==2024.7.4
|
||||||
cffi==1.17.1
|
cffi==1.17.0
|
||||||
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.5.4
|
||||||
cryptography==44.0.0
|
cryptography==43.0.0
|
||||||
Deprecated==1.2.15
|
Deprecated==1.2.14
|
||||||
Django==5.1.4
|
Django==5.0.8
|
||||||
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.3
|
||||||
django-environ==0.11.2
|
django-environ==0.11.2
|
||||||
django-filter==24.3
|
django-filter==24.3
|
||||||
django-fontawesome-5==1.0.18
|
django-fontawesome-5==1.0.18
|
||||||
django-oauth-toolkit==3.0.1
|
django-oauth-toolkit==2.4.0
|
||||||
django-tables2==2.7.1
|
django-simple-sso==1.2.0
|
||||||
et_xmlfile==2.0.0
|
django-tables2==2.7.0
|
||||||
gunicorn==23.0.0
|
et-xmlfile==1.1.0
|
||||||
idna==3.10
|
gunicorn==22.0.0
|
||||||
importlib_metadata==8.5.0
|
idna==3.7
|
||||||
|
importlib_metadata==8.2.0
|
||||||
|
itsdangerous==0.24
|
||||||
jwcrypto==1.5.6
|
jwcrypto==1.5.6
|
||||||
kombu==5.4.0rc1
|
kombu==5.4.0rc1
|
||||||
oauthlib==3.2.2
|
oauthlib==3.2.2
|
||||||
openpyxl==3.2.0b1
|
openpyxl==3.2.0b1
|
||||||
packaging==24.2
|
packaging==24.1
|
||||||
pika==1.3.2
|
pika==1.3.2
|
||||||
pillow==11.0.0
|
pillow==10.4.0
|
||||||
prompt_toolkit==3.0.48
|
prompt_toolkit==3.0.47
|
||||||
psycopg==3.2.3
|
psycopg==3.2.1
|
||||||
psycopg-binary==3.2.3
|
psycopg-binary==3.2.1
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
pyparsing==3.2.0
|
pyparsing==3.1.2
|
||||||
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.2
|
||||||
qrcode==7.3.1
|
qrcode==7.3.1
|
||||||
redis==5.1.0b6
|
redis==5.1.0b6
|
||||||
requests==2.32.3
|
requests<2.32.0 # kombu 5.4.0rc1 depends on requests<2.32.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
soupsieve==2.5
|
soupsieve==2.5
|
||||||
sqlparse==0.5.1
|
sqlparse==0.5.1
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.12.2
|
||||||
tzdata==2024.2
|
tzdata==2024.1
|
||||||
urllib3==2.3.0
|
urllib3==2.2.2
|
||||||
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.19.2
|
||||||
|
@ -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()
|
||||||
if self.q:
|
|
||||||
qs = User.objects.all()
|
qs = User.objects.all()
|
||||||
|
if self.q:
|
||||||
# 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()
|
||||||
if self.q:
|
|
||||||
qs = Team.objects.filter(
|
qs = Team.objects.filter(
|
||||||
deleted__isnull=True
|
deleted__isnull=True
|
||||||
)
|
)
|
||||||
|
if self.q:
|
||||||
|
# Due to privacy concerns only a full username match will return the proper user entry
|
||||||
q_parts = self.q.split(" ")
|
q_parts = self.q.split(" ")
|
||||||
q = Q()
|
q = Q()
|
||||||
for part in q_parts:
|
for part in q_parts:
|
||||||
|
@ -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
|
|
||||||
|
|
@ -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
|
@ -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 %}
|
@ -5,14 +5,15 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 12.09.23
|
Created on: 12.09.23
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.test import RequestFactory
|
from django.test import RequestFactory
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from api.models import APIUserToken
|
||||||
from konova.tests.test_views import BaseTestCase
|
from konova.tests.test_views import BaseTestCase
|
||||||
from user.forms.modals.api_token import NewAPITokenModalForm
|
|
||||||
from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm
|
from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm
|
||||||
from user.forms.user import UserNotificationForm
|
from user.forms.user import UserNotificationForm, UserAPITokenForm
|
||||||
from user.models import Team, UserAction, UserNotification
|
from user.models import Team, UserAction, UserNotification
|
||||||
|
|
||||||
|
|
||||||
@ -251,28 +252,35 @@ class UserNotificationFormTestCase(BaseTestCase):
|
|||||||
self.assertIn(selected_notification, self.user.notifications.all())
|
self.assertIn(selected_notification, self.user.notifications.all())
|
||||||
|
|
||||||
|
|
||||||
class ApiTokenFormTestCase(BaseTestCase):
|
class UserAPITokenFormTestCase(BaseTestCase):
|
||||||
def test_new_token_and_recreating_token(self):
|
def test_init(self):
|
||||||
request = RequestFactory().request()
|
form = UserAPITokenForm(
|
||||||
request.user = self.user
|
instance=self.user
|
||||||
request.POST = {
|
)
|
||||||
"confirm": True
|
self.assertEqual(form.form_title, str(_("Create new token")))
|
||||||
|
self.assertEqual(form.form_caption, str(_("A new token needs to be validated by an administrator!")))
|
||||||
|
self.assertEqual(form.action_url, reverse("user:api-token"))
|
||||||
|
self.assertEqual(form.cancel_redirect, reverse("user:index"))
|
||||||
|
|
||||||
|
self.assertIsNone(form.fields["token"].initial)
|
||||||
|
self.assertTrue(form.fields["token"].widget.attrs["readonly"])
|
||||||
|
|
||||||
|
def test_save(self):
|
||||||
|
data = {
|
||||||
|
"token": APIUserToken().token
|
||||||
}
|
}
|
||||||
|
form = UserAPITokenForm(
|
||||||
|
data,
|
||||||
|
instance=self.user
|
||||||
|
)
|
||||||
|
self.assertTrue(form.is_valid(), msg=form.errors)
|
||||||
self.assertIsNone(self.user.api_token)
|
self.assertIsNone(self.user.api_token)
|
||||||
form = NewAPITokenModalForm(request.POST, instance=self.user)
|
token = form.save()
|
||||||
form.save()
|
self.assertEqual(self.user.api_token, token)
|
||||||
self.user.refresh_from_db()
|
new_token = form.save()
|
||||||
token = self.user.api_token
|
self.assertEqual(self.user.api_token, new_token)
|
||||||
self.assertFalse(token.is_active)
|
try:
|
||||||
self.assertIsNone(token.valid_until)
|
token.refresh_from_db()
|
||||||
self.assertIsNotNone(token.token)
|
self.fail("Token should be deleted and not be fetchable anymore")
|
||||||
|
except ObjectDoesNotExist:
|
||||||
old_token = token.token
|
pass
|
||||||
form.save()
|
|
||||||
self.user.refresh_from_db()
|
|
||||||
new_token = self.user.api_token
|
|
||||||
self.assertNotEqual(new_token.token, old_token)
|
|
||||||
self.assertFalse(new_token.is_active)
|
|
||||||
self.assertIsNone(new_token.valid_until)
|
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ from django.urls import path
|
|||||||
|
|
||||||
from user.autocomplete.share import ShareUserAutocomplete, ShareTeamAutocomplete
|
from user.autocomplete.share import ShareUserAutocomplete, ShareTeamAutocomplete
|
||||||
from user.autocomplete.team import TeamAdminAutocomplete
|
from user.autocomplete.team import TeamAdminAutocomplete
|
||||||
from user.views.api_token import APITokenView, new_api_token_view
|
|
||||||
from user.views.propagate import PropagateUserView
|
from user.views.propagate import PropagateUserView
|
||||||
from user.views.views import *
|
from user.views.views import *
|
||||||
|
|
||||||
@ -18,8 +17,7 @@ urlpatterns = [
|
|||||||
path("", index_view, name="index"),
|
path("", index_view, name="index"),
|
||||||
path("propagate/", PropagateUserView.as_view(), name="propagate"),
|
path("propagate/", PropagateUserView.as_view(), name="propagate"),
|
||||||
path("notifications/", notifications_view, name="notifications"),
|
path("notifications/", notifications_view, name="notifications"),
|
||||||
path("token/api", APITokenView.as_view(), name="api-token"),
|
path("token/api", api_token_view, name="api-token"),
|
||||||
path("token/api/new", new_api_token_view, name="api-token-new"),
|
|
||||||
path("contact/<id>", contact_view, name="contact"),
|
path("contact/<id>", contact_view, name="contact"),
|
||||||
path("team/", index_team_view, name="team-index"),
|
path("team/", index_team_view, name="team-index"),
|
||||||
path("team/new", new_team_view, name="team-new"),
|
path("team/new", new_team_view, name="team-new"),
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 08.01.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.http import HttpRequest
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from konova.contexts import BaseContext
|
|
||||||
from konova.decorators import default_group_required
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.utils.message_templates import NEW_API_TOKEN_GENERATED
|
|
||||||
from user.forms.modals.api_token import NewAPITokenModalForm
|
|
||||||
|
|
||||||
|
|
||||||
class APITokenView(View):
|
|
||||||
|
|
||||||
@method_decorator(login_required)
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest):
|
|
||||||
template = "user/token.html"
|
|
||||||
user = request.user
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"user": user,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("User API token"),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
|
|
||||||
def new_api_token_view(request: HttpRequest):
|
|
||||||
""" Function based view for processing ModalForm
|
|
||||||
(Currently ModalForms only work properly with function based views)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
user = request.user
|
|
||||||
|
|
||||||
form = NewAPITokenModalForm(request.POST or None, instance=user, request=request)
|
|
||||||
return form.process_request(
|
|
||||||
request=request,
|
|
||||||
msg_success=NEW_API_TOKEN_GENERATED,
|
|
||||||
redirect_url=reverse("user:api-token"),
|
|
||||||
)
|
|
@ -16,7 +16,7 @@ from django.utils.decorators import method_decorator
|
|||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from konova.sub_settings.sso_settings import PROPAGATION_SECRET
|
from konova.sub_settings.sso_settings import OAUTH_CLIENT_ID
|
||||||
from user.models import User
|
from user.models import User
|
||||||
|
|
||||||
|
|
||||||
@ -35,9 +35,9 @@ class PropagateUserView(View):
|
|||||||
def post(self, request: HttpRequest, *args, **kwargs):
|
def post(self, request: HttpRequest, *args, **kwargs):
|
||||||
# Decrypt
|
# Decrypt
|
||||||
encrypted_body = request.body
|
encrypted_body = request.body
|
||||||
_hash = hashlib.md5()
|
hash = hashlib.md5()
|
||||||
_hash.update(PROPAGATION_SECRET.encode("utf-8"))
|
hash.update(OAUTH_CLIENT_ID.encode("utf-8"))
|
||||||
key = base64.urlsafe_b64encode(_hash.hexdigest().encode("utf-8"))
|
key = base64.urlsafe_b64encode(hash.hexdigest().encode("utf-8"))
|
||||||
fernet = Fernet(key)
|
fernet = Fernet(key)
|
||||||
body = fernet.decrypt(encrypted_body).decode("utf-8")
|
body = fernet.decrypt(encrypted_body).decode("utf-8")
|
||||||
body = json.loads(body)
|
body = json.loads(body)
|
||||||
|
@ -3,17 +3,19 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
|
from konova.utils.mailer import Mailer
|
||||||
|
from konova.utils.message_templates import FORM_INVALID
|
||||||
from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm
|
from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm
|
||||||
from user.forms.modals.user import UserContactForm
|
from user.forms.modals.user import UserContactForm
|
||||||
from user.forms.team import TeamDataForm
|
from user.forms.team import TeamDataForm
|
||||||
from user.forms.user import UserNotificationForm
|
from user.forms.user import UserNotificationForm, UserAPITokenForm
|
||||||
from user.models import User, Team
|
from user.models import User, Team
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import HttpRequest, Http404
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import any_group_check, login_required_modal
|
from konova.decorators import any_group_check, default_group_required, login_required_modal
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -74,6 +76,40 @@ def notifications_view(request: HttpRequest):
|
|||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
def api_token_view(request: HttpRequest):
|
||||||
|
""" Handles the request for user api frontend settings
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "user/token.html"
|
||||||
|
user = request.user
|
||||||
|
form = UserAPITokenForm(request.POST or None, instance=user)
|
||||||
|
if request.method == "POST":
|
||||||
|
if form.is_valid():
|
||||||
|
token = form.save()
|
||||||
|
messages.info(request, _("New token generated. Administrators need to validate."))
|
||||||
|
mailer = Mailer()
|
||||||
|
mailer.send_mail_verify_api_token(user)
|
||||||
|
return redirect("user:api-token")
|
||||||
|
else:
|
||||||
|
messages.error(request, FORM_INVALID, extra_tags="danger")
|
||||||
|
elif request.method != "GET":
|
||||||
|
raise NotImplementedError
|
||||||
|
context = {
|
||||||
|
"user": user,
|
||||||
|
"form": form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("User API token"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
@login_required_modal
|
@login_required_modal
|
||||||
@login_required
|
@login_required
|
||||||
def contact_view(request: HttpRequest, id: str):
|
def contact_view(request: HttpRequest, id: str):
|
||||||
|
Loading…
Reference in New Issue
Block a user