Merge branch 'refs/heads/master' into netgis_map_client_update
# Conflicts: # templates/map/client/config.json
This commit is contained in:
		
						commit
						c2b12649b0
					
				@ -24,6 +24,7 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -37,6 +38,7 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ class APIUserToken(models.Model):
 | 
			
		||||
            if token_obj.valid_until is not None and token_obj.valid_until < _today:
 | 
			
		||||
                raise PermissionError("Token validity expired")
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            raise PermissionError("Credentials invalid")
 | 
			
		||||
            raise PermissionError("Token unknown")
 | 
			
		||||
        return token_obj.user
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -155,3 +155,25 @@ class OAuthToken(UuidModel):
 | 
			
		||||
 | 
			
		||||
        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,14 +50,19 @@ class AbstractAPIView(View):
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        try:
 | 
			
		||||
            # Fetch the proper user from the given request header token
 | 
			
		||||
            ksp_token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
 | 
			
		||||
            token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
 | 
			
		||||
            ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
 | 
			
		||||
            token_user = APIUserToken.get_user_from_token(ksp_token)
 | 
			
		||||
 | 
			
		||||
            if ksp_user != token_user.username:
 | 
			
		||||
            if not token and not ksp_user:
 | 
			
		||||
                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}")
 | 
			
		||||
            else:
 | 
			
		||||
                self.user = token_user
 | 
			
		||||
            self.user = token_user
 | 
			
		||||
 | 
			
		||||
            request.user = self.user
 | 
			
		||||
            if not self.user.is_default_user():
 | 
			
		||||
 | 
			
		||||
@ -82,8 +82,8 @@ class Command(BaseKonovaCommand):
 | 
			
		||||
                atom_id = element.find("atomid").text
 | 
			
		||||
                selectable = element.find("selectable").text.lower()
 | 
			
		||||
                selectable = bool_map.get(selectable, False)
 | 
			
		||||
                short_name = element.find("shortname").text
 | 
			
		||||
                long_name = element.find("longname").text
 | 
			
		||||
                short_name = element.find("shortname").text or ""
 | 
			
		||||
                long_name = element.find("longname").text or ""
 | 
			
		||||
                is_archived = bool_map.get((element.find("archive").text.lower()), False)
 | 
			
		||||
 | 
			
		||||
                code = KonovaCode.objects.get_or_create(
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,9 @@ class Command(BaseKonovaCommand):
 | 
			
		||||
    def recalculate_parcels(self, options: dict):
 | 
			
		||||
        force_all = options.get("force_all", False)
 | 
			
		||||
 | 
			
		||||
        geometry_objects = Geometry.objects.all().exclude(
 | 
			
		||||
        geometry_objects = Geometry.objects.filter(
 | 
			
		||||
            geom__isempty=False,
 | 
			
		||||
        ).exclude(
 | 
			
		||||
            geom=None
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@ -48,11 +50,14 @@ class Command(BaseKonovaCommand):
 | 
			
		||||
                "geometry__id",
 | 
			
		||||
                flat=True
 | 
			
		||||
            )
 | 
			
		||||
            # ... and resolve into Geometry objects again ...
 | 
			
		||||
            intersected_geom_objs = Geometry.objects.filter(
 | 
			
		||||
                id__in=geom_with_intersection_ids
 | 
			
		||||
            )
 | 
			
		||||
            # Filter those geometries out (they have intersections and do not need to be processed)
 | 
			
		||||
            geometry_objects = geometry_objects.difference(intersected_geom_objs)
 | 
			
		||||
            # ... 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
 | 
			
		||||
 | 
			
		||||
@ -1,51 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
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")
 | 
			
		||||
@ -66,7 +66,6 @@ INSTALLED_APPS = [
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    'django.contrib.gis',
 | 
			
		||||
    'django.contrib.humanize',
 | 
			
		||||
    'simple_sso.sso_server',
 | 
			
		||||
    'django_tables2',
 | 
			
		||||
    'bootstrap_modal_forms',
 | 
			
		||||
    'fontawesome_5',
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 31.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from konova.sub_settings.django_settings import env
 | 
			
		||||
 | 
			
		||||
# MAPS
 | 
			
		||||
DEFAULT_LAT = 50.00
 | 
			
		||||
@ -28,3 +29,6 @@ LANIS_ZOOM_LUT = {
 | 
			
		||||
    1000: 30,
 | 
			
		||||
    500: 31,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MAP_PROXY_HOST_WHITELIST = env.list("MAP_PROXY_HOST_WHITELIST")
 | 
			
		||||
i = 0
 | 
			
		||||
@ -16,3 +16,5 @@ 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")
 | 
			
		||||
 | 
			
		||||
@ -91,3 +91,6 @@ INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations"
 | 
			
		||||
DATA_CHECKED_ON_TEMPLATE = _("Checked on {} by {}")
 | 
			
		||||
DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by {}")
 | 
			
		||||
DATA_IS_UNCHECKED = _("Current data not checked yet")
 | 
			
		||||
 | 
			
		||||
# API TOKEN SETTINGS
 | 
			
		||||
NEW_API_TOKEN_GENERATED = _("New token generated. Administrators need to validate.")
 | 
			
		||||
@ -1,78 +0,0 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 17.08.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from collections import Iterable
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
from user.models import User
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from konova.settings import SSO_SERVER_BASE, SSO_PUBLIC_KEY, PROXIES
 | 
			
		||||
from konova.sub_settings.context_settings import BASE_TITLE_SHORT
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Messenger:
 | 
			
		||||
    """ Used to send messages to the SSO server.
 | 
			
		||||
 | 
			
		||||
    Messages can be seen by the user the next time they login on their SSO dashboard.
 | 
			
		||||
    Documentation for SSO Server-Client communication can be found here:
 | 
			
		||||
    https://git.naturschutz.rlp.de/SGD-Nord/arnova/wiki/Messages
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    server_url = "{}communication/message/".format(SSO_SERVER_BASE)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, users: Iterable, subject: str = None, body: str = None, type: str = None):
 | 
			
		||||
        self.users = users
 | 
			
		||||
        self.msg_subject = subject
 | 
			
		||||
        self.msg_body = body
 | 
			
		||||
        self.msg_type = type
 | 
			
		||||
 | 
			
		||||
    def send(self):
 | 
			
		||||
        """ Sends a message
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if self.msg_body is None or len(self.msg_body) == 0:
 | 
			
		||||
            raise AttributeError("No message body set")
 | 
			
		||||
 | 
			
		||||
        headers = {
 | 
			
		||||
            "x-services-public-key": SSO_PUBLIC_KEY
 | 
			
		||||
        }
 | 
			
		||||
        for user in self.users:
 | 
			
		||||
            data = {
 | 
			
		||||
                "type": self.msg_type,
 | 
			
		||||
                "sender": BASE_TITLE_SHORT,
 | 
			
		||||
                "receiver": user.username,
 | 
			
		||||
                "subject": self.msg_subject,
 | 
			
		||||
                "body": self.msg_body,
 | 
			
		||||
            }
 | 
			
		||||
            requests.post(
 | 
			
		||||
                self.server_url,
 | 
			
		||||
                data=data,
 | 
			
		||||
                headers=headers,
 | 
			
		||||
                proxies=PROXIES
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
    def send_object_checked(self, obj_identifier: str, performing_user: User, detail_view_url: str = ""):
 | 
			
		||||
        """ Wraps sending of a message related to the checking of an object, like an intervention
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj_identifier (str): The object's identifier (e.g. 'EIV-123'
 | 
			
		||||
            performing_user (User): The user who performed the checking
 | 
			
		||||
            detail_view_url (str): If a direct link to the object shall be added to the message, it can be provided here
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.msg_subject = _("{} checked").format(obj_identifier)
 | 
			
		||||
        if len(detail_view_url) > 0:
 | 
			
		||||
            detail_view_url = _('<a href="{}">Check it out</a>').format(detail_view_url)
 | 
			
		||||
        self.msg_body = _("{} has been checked successfully by user {}! {}").format(
 | 
			
		||||
            obj_identifier,
 | 
			
		||||
            performing_user.username,
 | 
			
		||||
            detail_view_url
 | 
			
		||||
        )
 | 
			
		||||
        self.send()
 | 
			
		||||
@ -11,6 +11,7 @@ from json import JSONDecodeError
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from konova.sub_settings import schneider_settings
 | 
			
		||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID
 | 
			
		||||
from konova.sub_settings.proxy_settings import PROXIES
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -34,6 +35,7 @@ class ParcelFetcher:
 | 
			
		||||
        if geom.area < buffer_threshold:
 | 
			
		||||
            # Fallback for malicious geometries which are way too small and would disappear on negative buffering
 | 
			
		||||
            geom = geometry.geom
 | 
			
		||||
        geom.transform(DEFAULT_SRID)
 | 
			
		||||
        self.geojson = geom.ewkt
 | 
			
		||||
        self.results = []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,6 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
 | 
			
		||||
Created on: 19.08.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon
 | 
			
		||||
from django.http import HttpResponse, HttpRequest
 | 
			
		||||
from django.shortcuts import get_object_or_404
 | 
			
		||||
 | 
			
		||||
@ -24,5 +24,13 @@ class LogoutView(View):
 | 
			
		||||
        Returns:
 | 
			
		||||
            A redirect
 | 
			
		||||
        """
 | 
			
		||||
        user = request.user
 | 
			
		||||
        try:
 | 
			
		||||
            oauth_token = user.oauth_token
 | 
			
		||||
            if oauth_token:
 | 
			
		||||
                oauth_token.revoke()
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        logout(request)
 | 
			
		||||
        return redirect(SSO_SERVER_BASE)
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import json
 | 
			
		||||
from json import JSONDecodeError
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
import urllib3.util
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.http import JsonResponse, HttpRequest, HttpResponse
 | 
			
		||||
from django.utils.decorators import method_decorator
 | 
			
		||||
@ -17,6 +18,7 @@ from django.views import View
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -31,6 +33,13 @@ class BaseClientProxyView(View):
 | 
			
		||||
    def dispatch(self, 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):
 | 
			
		||||
        """ Generic proxied call
 | 
			
		||||
 | 
			
		||||
@ -61,6 +70,11 @@ class ClientProxyParcelSearch(BaseClientProxyView):
 | 
			
		||||
 | 
			
		||||
    def get(self, request: HttpRequest):
 | 
			
		||||
        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)
 | 
			
		||||
        return HttpResponse(content)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -115,10 +115,10 @@ class OAuthCallbackView(View):
 | 
			
		||||
        if status_code_invalid:
 | 
			
		||||
            raise RuntimeError(f"OAuth access token could not be fetched: {access_code_response.text}")
 | 
			
		||||
 | 
			
		||||
        oauth_access_token = OAuthToken.from_access_token_response(access_code_response_body, received_on)
 | 
			
		||||
        oauth_access_token.save()
 | 
			
		||||
        user = oauth_access_token.update_and_get_user()
 | 
			
		||||
        user.oauth_replace_token(oauth_access_token)
 | 
			
		||||
        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.
										
									
								
							@ -38,13 +38,14 @@
 | 
			
		||||
#: konova/forms/modals/remove_form.py:23
 | 
			
		||||
#: konova/forms/modals/resubmission_form.py:22
 | 
			
		||||
#: konova/forms/modals/resubmission_form.py:38 konova/forms/remove_form.py:25
 | 
			
		||||
#: konova/tests/unit/test_forms.py:59 user/forms/user.py:39
 | 
			
		||||
#: konova/tests/unit/test_forms.py:59 user/forms/modals/api_token.py:17
 | 
			
		||||
#: user/forms/user.py:39
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2024-08-19 10:32+0200\n"
 | 
			
		||||
"POT-Creation-Date: 2025-01-08 15:26+0100\n"
 | 
			
		||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
@ -373,7 +374,6 @@ msgid "Identifier"
 | 
			
		||||
msgstr "Kennung"
 | 
			
		||||
 | 
			
		||||
#: compensation/forms/compensation.py:33 intervention/forms/intervention.py:33
 | 
			
		||||
#: user/forms/user.py:77
 | 
			
		||||
msgid "Generated automatically - not editable"
 | 
			
		||||
msgstr "Automatisch generiert - nicht bearbeitbar"
 | 
			
		||||
 | 
			
		||||
@ -1795,8 +1795,7 @@ msgstr "Bearbeitender Nutzer"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "Sucht nach Einträgen, an denen diese Person gearbeitet hat"
 | 
			
		||||
 | 
			
		||||
#: konova/forms/base_form.py:23 templates/form/collapsable/form.html:62
 | 
			
		||||
msgid "Save"
 | 
			
		||||
@ -1858,6 +1857,7 @@ msgstr ""
 | 
			
		||||
"Ich, {} {}, bestätige, dass diese Daten wieder entzeichnet werden müssen."
 | 
			
		||||
 | 
			
		||||
#: konova/forms/modals/remove_form.py:22 konova/forms/remove_form.py:24
 | 
			
		||||
#: user/forms/modals/api_token.py:16
 | 
			
		||||
msgid "Confirm"
 | 
			
		||||
msgstr "Bestätige"
 | 
			
		||||
 | 
			
		||||
@ -2283,6 +2283,10 @@ msgstr ""
 | 
			
		||||
msgid "Current data not checked yet"
 | 
			
		||||
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
 | 
			
		||||
msgid "{} checked"
 | 
			
		||||
msgstr "{} geprüft"
 | 
			
		||||
@ -2321,7 +2325,7 @@ msgstr "Home"
 | 
			
		||||
msgid "Log"
 | 
			
		||||
msgstr "Log"
 | 
			
		||||
 | 
			
		||||
#: konova/views/map_proxy.py:70
 | 
			
		||||
#: konova/views/map_proxy.py:84
 | 
			
		||||
msgid ""
 | 
			
		||||
"The external service is currently unavailable.<br>Please try again in a few "
 | 
			
		||||
"moments..."
 | 
			
		||||
@ -2831,10 +2835,8 @@ msgid "Reports"
 | 
			
		||||
msgstr "Berichte"
 | 
			
		||||
 | 
			
		||||
#: templates/navbars/navbar.html:57
 | 
			
		||||
#, fuzzy
 | 
			
		||||
#| msgid "Admins"
 | 
			
		||||
msgid "Admin"
 | 
			
		||||
msgstr "Administratoren"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: templates/navbars/navbar.html:59 user/templates/user/index.html:31
 | 
			
		||||
msgid "Settings"
 | 
			
		||||
@ -2873,6 +2875,21 @@ msgstr ""
 | 
			
		||||
"Falls die Geometrie nicht leer ist, werden die Flurstücke aktuell berechnet. "
 | 
			
		||||
"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/team.py:17 user/forms/team.py:22
 | 
			
		||||
msgid "Team name"
 | 
			
		||||
@ -2895,11 +2912,11 @@ msgstr ""
 | 
			
		||||
"Mehrfachauswahl möglich - Sie können nur Nutzer wählen, die noch nicht "
 | 
			
		||||
"Mitglieder dieses Teams sind. Geben Sie den ganzen Nutzernamen an."
 | 
			
		||||
 | 
			
		||||
#: user/forms/modals/team.py:56 user/tests/unit/test_forms.py:31
 | 
			
		||||
#: user/forms/modals/team.py:56 user/tests/unit/test_forms.py:29
 | 
			
		||||
msgid "Create new team"
 | 
			
		||||
msgstr "Neues Team anlegen"
 | 
			
		||||
 | 
			
		||||
#: user/forms/modals/team.py:57 user/tests/unit/test_forms.py:32
 | 
			
		||||
#: user/forms/modals/team.py:57 user/tests/unit/test_forms.py:30
 | 
			
		||||
msgid ""
 | 
			
		||||
"You will become the administrator for this group by default. You do not need "
 | 
			
		||||
"to add yourself to the list of members."
 | 
			
		||||
@ -2928,11 +2945,11 @@ msgid "There must be at least one admin on this team."
 | 
			
		||||
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/tests/unit/test_forms.py:88
 | 
			
		||||
#: user/tests/unit/test_forms.py:86
 | 
			
		||||
msgid "Edit team"
 | 
			
		||||
msgstr "Team bearbeiten"
 | 
			
		||||
 | 
			
		||||
#: user/forms/modals/team.py:187 user/tests/unit/test_forms.py:165
 | 
			
		||||
#: user/forms/modals/team.py:187 user/tests/unit/test_forms.py:163
 | 
			
		||||
msgid ""
 | 
			
		||||
"ATTENTION!\n"
 | 
			
		||||
"\n"
 | 
			
		||||
@ -2949,7 +2966,7 @@ msgstr ""
 | 
			
		||||
"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/tests/unit/test_forms.py:198
 | 
			
		||||
#: user/tests/unit/test_forms.py:196
 | 
			
		||||
msgid "Leave team"
 | 
			
		||||
msgstr "Team verlassen"
 | 
			
		||||
 | 
			
		||||
@ -2981,22 +2998,10 @@ msgstr "Benachrichtigungen"
 | 
			
		||||
msgid "Select the situations when you want to receive a notification"
 | 
			
		||||
msgstr "Wann wollen Sie per E-Mail benachrichtigt werden?"
 | 
			
		||||
 | 
			
		||||
#: user/forms/user.py:38 user/tests/unit/test_forms.py:234
 | 
			
		||||
#: user/forms/user.py:38 user/tests/unit/test_forms.py:232
 | 
			
		||||
msgid "Edit notifications"
 | 
			
		||||
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
 | 
			
		||||
msgid "Unrecorded"
 | 
			
		||||
msgstr "Entzeichnet"
 | 
			
		||||
@ -3051,7 +3056,7 @@ msgid "Manage teams"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/index.html:53 user/templates/user/team/index.html:19
 | 
			
		||||
#: user/views/views.py:171
 | 
			
		||||
#: user/views/views.py:135
 | 
			
		||||
msgid "Teams"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@ -3087,270 +3092,58 @@ msgstr "API Einstellungen"
 | 
			
		||||
msgid "Current token"
 | 
			
		||||
msgstr "Aktueller Token"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:14
 | 
			
		||||
#: user/templates/user/token.html:15
 | 
			
		||||
msgid "Create new token"
 | 
			
		||||
msgstr "Neuen Token generieren"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:23
 | 
			
		||||
msgid "Authenticated by admins"
 | 
			
		||||
msgstr "Von Admin freigeschaltet"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:18
 | 
			
		||||
#: user/templates/user/token.html:27
 | 
			
		||||
msgid "Token has been verified and can be used"
 | 
			
		||||
msgstr "Token wurde freigeschaltet und kann verwendet werden"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:20
 | 
			
		||||
#: user/templates/user/token.html:29
 | 
			
		||||
msgid "Token waiting for verification"
 | 
			
		||||
msgstr "Token noch nicht freigeschaltet"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:24
 | 
			
		||||
#: user/templates/user/token.html:33
 | 
			
		||||
msgid "Valid until"
 | 
			
		||||
msgstr "Läuft ab am"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:35
 | 
			
		||||
msgid "User settings"
 | 
			
		||||
msgstr "Einstellungen"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:61
 | 
			
		||||
msgid "Notifications edited"
 | 
			
		||||
msgstr "Benachrichtigungen bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:73
 | 
			
		||||
msgid "User notifications"
 | 
			
		||||
msgstr "Benachrichtigungen"
 | 
			
		||||
 | 
			
		||||
#: 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
 | 
			
		||||
#: user/views/api_token.py:33
 | 
			
		||||
msgid "User API token"
 | 
			
		||||
msgstr "API Nutzer Token"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:183
 | 
			
		||||
#: user/views/views.py:33
 | 
			
		||||
msgid "User settings"
 | 
			
		||||
msgstr "Einstellungen"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:59
 | 
			
		||||
msgid "Notifications edited"
 | 
			
		||||
msgstr "Benachrichtigungen bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:71
 | 
			
		||||
msgid "User notifications"
 | 
			
		||||
msgstr "Benachrichtigungen"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:147
 | 
			
		||||
msgid "New team added"
 | 
			
		||||
msgstr "Neues Team hinzugefügt"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:198
 | 
			
		||||
#: user/views/views.py:162
 | 
			
		||||
msgid "Team edited"
 | 
			
		||||
msgstr "Team bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:213
 | 
			
		||||
#: user/views/views.py:177
 | 
			
		||||
msgid "Team removed"
 | 
			
		||||
msgstr "Team gelöscht"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:228
 | 
			
		||||
#: user/views/views.py:192
 | 
			
		||||
msgid "You are not a member of this team"
 | 
			
		||||
msgstr "Sie sind kein Mitglied dieses Teams"
 | 
			
		||||
 | 
			
		||||
#: user/views/views.py:235
 | 
			
		||||
#: user/views/views.py:199
 | 
			
		||||
msgid "Left Team"
 | 
			
		||||
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,65 +1,63 @@
 | 
			
		||||
amqp==5.2.0
 | 
			
		||||
amqp==5.3.1
 | 
			
		||||
asgiref==3.8.1
 | 
			
		||||
async-timeout==4.0.3
 | 
			
		||||
async-timeout==5.0.1
 | 
			
		||||
beautifulsoup4==4.13.0b2
 | 
			
		||||
billiard==4.2.0
 | 
			
		||||
cached-property==1.5.2
 | 
			
		||||
billiard==4.2.1
 | 
			
		||||
cached-property==2.0.1
 | 
			
		||||
celery==5.4.0
 | 
			
		||||
certifi==2024.7.4
 | 
			
		||||
cffi==1.17.0
 | 
			
		||||
certifi==2024.12.14
 | 
			
		||||
cffi==1.17.1
 | 
			
		||||
chardet==5.2.0
 | 
			
		||||
charset-normalizer==3.3.2
 | 
			
		||||
click==8.1.7
 | 
			
		||||
charset-normalizer==3.4.0
 | 
			
		||||
click==8.1.8
 | 
			
		||||
click-didyoumean==0.3.1
 | 
			
		||||
click-plugins==1.1.1
 | 
			
		||||
click-repl==0.3.0
 | 
			
		||||
coverage==7.5.4
 | 
			
		||||
cryptography==43.0.0
 | 
			
		||||
Deprecated==1.2.14
 | 
			
		||||
Django==5.0.8
 | 
			
		||||
coverage==7.6.9
 | 
			
		||||
cryptography==44.0.0
 | 
			
		||||
Deprecated==1.2.15
 | 
			
		||||
Django==5.1.4
 | 
			
		||||
django-autocomplete-light==3.11.0
 | 
			
		||||
django-bootstrap-modal-forms==3.0.4
 | 
			
		||||
django-bootstrap4==24.3
 | 
			
		||||
django-bootstrap-modal-forms==3.0.5
 | 
			
		||||
django-bootstrap4==24.4
 | 
			
		||||
django-environ==0.11.2
 | 
			
		||||
django-filter==24.3
 | 
			
		||||
django-fontawesome-5==1.0.18
 | 
			
		||||
django-oauth-toolkit==2.4.0
 | 
			
		||||
django-simple-sso==1.2.0
 | 
			
		||||
django-tables2==2.7.0
 | 
			
		||||
et-xmlfile==1.1.0
 | 
			
		||||
gunicorn==22.0.0
 | 
			
		||||
idna==3.7
 | 
			
		||||
importlib_metadata==8.2.0
 | 
			
		||||
itsdangerous==0.24
 | 
			
		||||
django-oauth-toolkit==3.0.1
 | 
			
		||||
django-tables2==2.7.1
 | 
			
		||||
et_xmlfile==2.0.0
 | 
			
		||||
gunicorn==23.0.0
 | 
			
		||||
idna==3.10
 | 
			
		||||
importlib_metadata==8.5.0
 | 
			
		||||
jwcrypto==1.5.6
 | 
			
		||||
kombu==5.4.0rc1
 | 
			
		||||
oauthlib==3.2.2
 | 
			
		||||
openpyxl==3.2.0b1
 | 
			
		||||
packaging==24.1
 | 
			
		||||
packaging==24.2
 | 
			
		||||
pika==1.3.2
 | 
			
		||||
pillow==10.4.0
 | 
			
		||||
prompt_toolkit==3.0.47
 | 
			
		||||
psycopg==3.2.1
 | 
			
		||||
psycopg-binary==3.2.1
 | 
			
		||||
pillow==11.0.0
 | 
			
		||||
prompt_toolkit==3.0.48
 | 
			
		||||
psycopg==3.2.3
 | 
			
		||||
psycopg-binary==3.2.3
 | 
			
		||||
pycparser==2.22
 | 
			
		||||
pyparsing==3.1.2
 | 
			
		||||
pyparsing==3.2.0
 | 
			
		||||
pypng==0.20220715.0
 | 
			
		||||
pyproj==3.6.1
 | 
			
		||||
pyproj==3.7.0
 | 
			
		||||
python-dateutil==2.9.0.post0
 | 
			
		||||
pytz==2024.1
 | 
			
		||||
pytz==2024.2
 | 
			
		||||
PyYAML==6.0.2
 | 
			
		||||
qrcode==7.3.1
 | 
			
		||||
redis==5.1.0b6
 | 
			
		||||
requests<2.32.0  # kombu 5.4.0rc1 depends on requests<2.32.0
 | 
			
		||||
requests==2.32.3
 | 
			
		||||
six==1.16.0
 | 
			
		||||
soupsieve==2.5
 | 
			
		||||
sqlparse==0.5.1
 | 
			
		||||
typing_extensions==4.12.2
 | 
			
		||||
tzdata==2024.1
 | 
			
		||||
urllib3==2.2.2
 | 
			
		||||
tzdata==2024.2
 | 
			
		||||
urllib3==2.3.0
 | 
			
		||||
vine==5.1.0
 | 
			
		||||
wcwidth==0.2.13
 | 
			
		||||
webservices==0.7
 | 
			
		||||
wrapt==1.16.0
 | 
			
		||||
xmltodict==0.13.0
 | 
			
		||||
zipp==3.19.2
 | 
			
		||||
xmltodict==0.14.2
 | 
			
		||||
zipp==3.21.0
 | 
			
		||||
 | 
			
		||||
@ -17,10 +17,11 @@ class ShareUserAutocomplete(Select2QuerySetView):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        qs = User.objects.none()
 | 
			
		||||
        if self.request.user.is_anonymous:
 | 
			
		||||
            return User.objects.none()
 | 
			
		||||
        qs = User.objects.all()
 | 
			
		||||
            return qs
 | 
			
		||||
        if self.q:
 | 
			
		||||
            qs = User.objects.all()
 | 
			
		||||
            # Due to privacy concerns only a full username match will return the proper user entry
 | 
			
		||||
            qs = qs.filter(
 | 
			
		||||
                Q(username=self.q) |
 | 
			
		||||
@ -41,13 +42,13 @@ class ShareTeamAutocomplete(Select2QuerySetView):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        qs = Team.objects.none()
 | 
			
		||||
        if self.request.user.is_anonymous:
 | 
			
		||||
            return Team.objects.none()
 | 
			
		||||
        qs = Team.objects.filter(
 | 
			
		||||
            deleted__isnull=True
 | 
			
		||||
        )
 | 
			
		||||
            return qs
 | 
			
		||||
        if self.q:
 | 
			
		||||
            # Due to privacy concerns only a full username match will return the proper user entry
 | 
			
		||||
            qs = Team.objects.filter(
 | 
			
		||||
                deleted__isnull=True
 | 
			
		||||
            )
 | 
			
		||||
            q_parts = self.q.split(" ")
 | 
			
		||||
            q = Q()
 | 
			
		||||
            for part in q_parts:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										49
									
								
								user/forms/modals/api_token.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								user/forms/modals/api_token.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
"""
 | 
			
		||||
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,48 +66,3 @@ class UserNotificationForm(BaseForm):
 | 
			
		||||
            id__in=selected_notification_ids,
 | 
			
		||||
        )
 | 
			
		||||
        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,7 +8,16 @@
 | 
			
		||||
            <table class="table table-hover">
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th scope="row">{% trans 'Current token' %}</th>
 | 
			
		||||
                    <td>{{ user.api_token.token }}</td>
 | 
			
		||||
                    <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>
 | 
			
		||||
                    <th scope="row">{% trans 'Authenticated by admins' %}</th>
 | 
			
		||||
@ -27,7 +36,9 @@
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    {% include 'form/table/generic_table_form.html' %}
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
{% with 'btn-modal' as btn_class %}
 | 
			
		||||
    {% include 'modal/modal_form_script.html' %}
 | 
			
		||||
{% endwith %}
 | 
			
		||||
    
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -5,15 +5,14 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
 | 
			
		||||
Created on: 12.09.23
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.test import RequestFactory
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from api.models import APIUserToken
 | 
			
		||||
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.user import UserNotificationForm, UserAPITokenForm
 | 
			
		||||
from user.forms.user import UserNotificationForm
 | 
			
		||||
from user.models import Team, UserAction, UserNotification
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -252,35 +251,28 @@ class UserNotificationFormTestCase(BaseTestCase):
 | 
			
		||||
        self.assertIn(selected_notification, self.user.notifications.all())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserAPITokenFormTestCase(BaseTestCase):
 | 
			
		||||
    def test_init(self):
 | 
			
		||||
        form = UserAPITokenForm(
 | 
			
		||||
            instance=self.user
 | 
			
		||||
        )
 | 
			
		||||
        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
 | 
			
		||||
class ApiTokenFormTestCase(BaseTestCase):
 | 
			
		||||
    def test_new_token_and_recreating_token(self):
 | 
			
		||||
        request = RequestFactory().request()
 | 
			
		||||
        request.user = self.user
 | 
			
		||||
        request.POST = {
 | 
			
		||||
            "confirm": True
 | 
			
		||||
        }
 | 
			
		||||
        form = UserAPITokenForm(
 | 
			
		||||
            data,
 | 
			
		||||
            instance=self.user
 | 
			
		||||
        )
 | 
			
		||||
        self.assertTrue(form.is_valid(), msg=form.errors)
 | 
			
		||||
 | 
			
		||||
        self.assertIsNone(self.user.api_token)
 | 
			
		||||
        token = form.save()
 | 
			
		||||
        self.assertEqual(self.user.api_token, token)
 | 
			
		||||
        new_token = form.save()
 | 
			
		||||
        self.assertEqual(self.user.api_token, new_token)
 | 
			
		||||
        try:
 | 
			
		||||
            token.refresh_from_db()
 | 
			
		||||
            self.fail("Token should be deleted and not be fetchable anymore")
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            pass
 | 
			
		||||
        form = NewAPITokenModalForm(request.POST, instance=self.user)
 | 
			
		||||
        form.save()
 | 
			
		||||
        self.user.refresh_from_db()
 | 
			
		||||
        token = self.user.api_token
 | 
			
		||||
        self.assertFalse(token.is_active)
 | 
			
		||||
        self.assertIsNone(token.valid_until)
 | 
			
		||||
        self.assertIsNotNone(token.token)
 | 
			
		||||
 | 
			
		||||
        old_token = token.token
 | 
			
		||||
        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,6 +9,7 @@ from django.urls import path
 | 
			
		||||
 | 
			
		||||
from user.autocomplete.share import ShareUserAutocomplete, ShareTeamAutocomplete
 | 
			
		||||
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.views import *
 | 
			
		||||
 | 
			
		||||
@ -17,7 +18,8 @@ urlpatterns = [
 | 
			
		||||
    path("", index_view, name="index"),
 | 
			
		||||
    path("propagate/", PropagateUserView.as_view(), name="propagate"),
 | 
			
		||||
    path("notifications/", notifications_view, name="notifications"),
 | 
			
		||||
    path("token/api", api_token_view, name="api-token"),
 | 
			
		||||
    path("token/api", APITokenView.as_view(), name="api-token"),
 | 
			
		||||
    path("token/api/new", new_api_token_view, name="api-token-new"),
 | 
			
		||||
    path("contact/<id>", contact_view, name="contact"),
 | 
			
		||||
    path("team/", index_team_view, name="team-index"),
 | 
			
		||||
    path("team/new", new_team_view, name="team-new"),
 | 
			
		||||
@ -30,4 +32,4 @@ urlpatterns = [
 | 
			
		||||
    path("atcmplt/share/u", ShareUserAutocomplete.as_view(), name="share-user-autocomplete"),
 | 
			
		||||
    path("atcmplt/share/t", ShareTeamAutocomplete.as_view(), name="share-team-autocomplete"),
 | 
			
		||||
    path("atcmplt/team/admin", TeamAdminAutocomplete.as_view(), name="team-admin-autocomplete"),
 | 
			
		||||
]
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										57
									
								
								user/views/api_token.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								user/views/api_token.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
			
		||||
"""
 | 
			
		||||
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.decorators.csrf import csrf_exempt
 | 
			
		||||
 | 
			
		||||
from konova.sub_settings.sso_settings import OAUTH_CLIENT_ID
 | 
			
		||||
from konova.sub_settings.sso_settings import PROPAGATION_SECRET
 | 
			
		||||
from user.models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@ class PropagateUserView(View):
 | 
			
		||||
        # Decrypt
 | 
			
		||||
        encrypted_body = request.body
 | 
			
		||||
        _hash = hashlib.md5()
 | 
			
		||||
        _hash.update(OAUTH_CLIENT_ID.encode("utf-8"))
 | 
			
		||||
        _hash.update(PROPAGATION_SECRET.encode("utf-8"))
 | 
			
		||||
        key = base64.urlsafe_b64encode(_hash.hexdigest().encode("utf-8"))
 | 
			
		||||
        fernet = Fernet(key)
 | 
			
		||||
        body = fernet.decrypt(encrypted_body).decode("utf-8")
 | 
			
		||||
 | 
			
		||||
@ -3,19 +3,17 @@ from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
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.user import UserContactForm
 | 
			
		||||
from user.forms.team import TeamDataForm
 | 
			
		||||
from user.forms.user import UserNotificationForm, UserAPITokenForm
 | 
			
		||||
from user.forms.user import UserNotificationForm
 | 
			
		||||
from user.models import User, Team
 | 
			
		||||
from django.http import HttpRequest, Http404
 | 
			
		||||
from django.shortcuts import render, redirect, get_object_or_404
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from konova.contexts import BaseContext
 | 
			
		||||
from konova.decorators import any_group_check, default_group_required, login_required_modal
 | 
			
		||||
from konova.decorators import any_group_check, login_required_modal
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@ -76,40 +74,6 @@ def notifications_view(request: HttpRequest):
 | 
			
		||||
    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
 | 
			
		||||
def contact_view(request: HttpRequest, id: str):
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user