Merge branch 'master' into Docker
# Conflicts: # konova/sub_settings/django_settings.py
This commit is contained in:
		
						commit
						81f2724f3f
					
				
							
								
								
									
										0
									
								
								api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										16
									
								
								api/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								api/admin.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
 | 
			
		||||
from api.models.token import APIUserToken
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APITokenAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = [
 | 
			
		||||
        "token",
 | 
			
		||||
        "valid_until",
 | 
			
		||||
        "is_active",
 | 
			
		||||
    ]
 | 
			
		||||
    readonly_fields = [
 | 
			
		||||
        "token"
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
admin.site.register(APIUserToken, APITokenAdmin)
 | 
			
		||||
							
								
								
									
										5
									
								
								api/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								api/apps.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
from django.apps import AppConfig
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ApiConfig(AppConfig):
 | 
			
		||||
    name = 'api'
 | 
			
		||||
							
								
								
									
										8
									
								
								api/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								api/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from .token import *
 | 
			
		||||
							
								
								
									
										50
									
								
								api/models/token.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								api/models/token.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from konova.utils.generators import generate_token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIUserToken(models.Model):
 | 
			
		||||
    token = models.CharField(
 | 
			
		||||
        primary_key=True,
 | 
			
		||||
        max_length=1000,
 | 
			
		||||
        default=generate_token,
 | 
			
		||||
    )
 | 
			
		||||
    valid_until = models.DateField(
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True,
 | 
			
		||||
        help_text="Token is only valid until this date",
 | 
			
		||||
    )
 | 
			
		||||
    is_active = models.BooleanField(
 | 
			
		||||
        default=False,
 | 
			
		||||
        help_text="Must be activated by an admin"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.token
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_user_from_token(token: str, username: str):
 | 
			
		||||
        """ Getter for the related user object
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            token (str): The used token
 | 
			
		||||
            username (str): The username
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            user (User): Otherwise None
 | 
			
		||||
        """
 | 
			
		||||
        _today = timezone.now().date()
 | 
			
		||||
        try:
 | 
			
		||||
            token_obj = APIUserToken.objects.get(
 | 
			
		||||
                token=token,
 | 
			
		||||
                user__username=username
 | 
			
		||||
            )
 | 
			
		||||
            if not token_obj.is_active:
 | 
			
		||||
                raise PermissionError("Token unverified")
 | 
			
		||||
            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")
 | 
			
		||||
        return token_obj.user
 | 
			
		||||
							
								
								
									
										9
									
								
								api/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								api/settings.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
KSP_TOKEN_HEADER_IDENTIFIER = "Ksptoken"
 | 
			
		||||
KSP_USER_HEADER_IDENTIFIER = "Kspuser"
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tests/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tests/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 27.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tests/v1/create/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tests/v1/create/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 27.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										19
									
								
								api/tests/v1/create/compensation_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								api/tests/v1/create/compensation_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
    ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "Test_compensation",
 | 
			
		||||
        "is_cef": false,
 | 
			
		||||
        "is_coherence_keeping": false,
 | 
			
		||||
        "intervention": "MUST_BE_SET_IN_TEST",
 | 
			
		||||
        "before_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "after_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "actions": [
 | 
			
		||||
        ],
 | 
			
		||||
        "deadlines": [
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								api/tests/v1/create/deduction_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								api/tests/v1/create/deduction_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
    "eco_account": "CHANGE_BEFORE_RUN!!!",
 | 
			
		||||
    "surface": 500.0,
 | 
			
		||||
    "intervention": "CHANGE_BEFORE_RUN!!!"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								api/tests/v1/create/ecoaccount_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								api/tests/v1/create/ecoaccount_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
    ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "Test_ecoaccount",
 | 
			
		||||
        "deductable_surface": 10000.0,
 | 
			
		||||
        "responsible": {
 | 
			
		||||
            "conservation_office": null,
 | 
			
		||||
            "conservation_file_number": null,
 | 
			
		||||
            "handler": null
 | 
			
		||||
        },
 | 
			
		||||
        "legal": {
 | 
			
		||||
            "agreement_date": null
 | 
			
		||||
        },
 | 
			
		||||
        "before_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "after_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "actions": [
 | 
			
		||||
        ],
 | 
			
		||||
        "deadlines": [
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								api/tests/v1/create/ema_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								api/tests/v1/create/ema_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
    ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "Test_ema",
 | 
			
		||||
        "responsible": {
 | 
			
		||||
            "conservation_office": null,
 | 
			
		||||
            "conservation_file_number": null,
 | 
			
		||||
            "handler": null
 | 
			
		||||
        },
 | 
			
		||||
        "before_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "after_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "actions": [
 | 
			
		||||
        ],
 | 
			
		||||
        "deadlines": [
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								api/tests/v1/create/intervention_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								api/tests/v1/create/intervention_create_post_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
    ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "Test_intervention",
 | 
			
		||||
        "responsible": {
 | 
			
		||||
            "registration_office": null,
 | 
			
		||||
            "registration_file_number": null,
 | 
			
		||||
            "conservation_office": null,
 | 
			
		||||
            "conservation_file_number": null,
 | 
			
		||||
            "handler": null
 | 
			
		||||
        },
 | 
			
		||||
        "legal": {
 | 
			
		||||
            "registration_date": null,
 | 
			
		||||
            "binding_date": null,
 | 
			
		||||
            "process_type": null,
 | 
			
		||||
            "laws": []
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								api/tests/v1/create/test_api_create.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								api/tests/v1/create/test_api_create.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 27.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIV1CreateTestCase(BaseAPIV1TestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def _run_create_request(self, url, data):
 | 
			
		||||
        data = json.dumps(data)
 | 
			
		||||
        response = self.client.post(
 | 
			
		||||
            url,
 | 
			
		||||
            data=data,
 | 
			
		||||
            content_type="application/json",
 | 
			
		||||
            **self.header_data
 | 
			
		||||
        )
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    def _test_create_object(self, url, post_body):
 | 
			
		||||
        """ Tests the API creation of a new data object.
 | 
			
		||||
 | 
			
		||||
        Post body data stored in a local json file
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            url (str): The api creation url
 | 
			
		||||
            post_body (dict): The post body content as dict
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        response = self._run_create_request(url, post_body)
 | 
			
		||||
        self.assertEqual(response.status_code, 200, msg=response.content)
 | 
			
		||||
        content = json.loads(response.content)
 | 
			
		||||
        self.assertIsNotNone(content.get("id", None), msg=response.content)
 | 
			
		||||
 | 
			
		||||
    def test_create_intervention(self):
 | 
			
		||||
        """ Tests api creation
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        url = reverse("api:v1:intervention")
 | 
			
		||||
        json_file_path = "api/tests/v1/create/intervention_create_post_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            post_body = json.load(fp=json_file)
 | 
			
		||||
        self._test_create_object(url, post_body)
 | 
			
		||||
 | 
			
		||||
    def test_create_compensation(self):
 | 
			
		||||
        """ Tests api creation
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        url = reverse("api:v1:compensation")
 | 
			
		||||
        json_file_path = "api/tests/v1/create/compensation_create_post_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            post_body = json.load(fp=json_file)
 | 
			
		||||
            post_body["properties"]["intervention"] = str(self.intervention.id)
 | 
			
		||||
 | 
			
		||||
        # Expect this first request to fail, since user has no shared access on the intervention, we want to create
 | 
			
		||||
        # a compensation for
 | 
			
		||||
        response = self._run_create_request(url, post_body)
 | 
			
		||||
        self.assertEqual(response.status_code, 500, msg=response.content)
 | 
			
		||||
        content = json.loads(response.content)
 | 
			
		||||
        self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)
 | 
			
		||||
 | 
			
		||||
        # Add the user to the shared users of the intervention and try again! Now everything should work as expected.
 | 
			
		||||
        self.intervention.users.add(self.superuser)
 | 
			
		||||
        self._test_create_object(url, post_body)
 | 
			
		||||
 | 
			
		||||
    def test_create_eco_account(self):
 | 
			
		||||
        """ Tests api creation
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        url = reverse("api:v1:ecoaccount")
 | 
			
		||||
        json_file_path = "api/tests/v1/create/ecoaccount_create_post_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            post_body = json.load(fp=json_file)
 | 
			
		||||
        self._test_create_object(url, post_body)
 | 
			
		||||
 | 
			
		||||
    def test_create_ema(self):
 | 
			
		||||
        """ Tests api creation
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        url = reverse("api:v1:ema")
 | 
			
		||||
        json_file_path = "api/tests/v1/create/ema_create_post_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            post_body = json.load(fp=json_file)
 | 
			
		||||
        self._test_create_object(url, post_body)
 | 
			
		||||
 | 
			
		||||
    def test_create_deduction(self):
 | 
			
		||||
        """ Tests api creation
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
        self.eco_account.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        url = reverse("api:v1:deduction")
 | 
			
		||||
        json_file_path = "api/tests/v1/create/deduction_create_post_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            post_body = json.load(fp=json_file)
 | 
			
		||||
            post_body["intervention"] = str(self.intervention.id)
 | 
			
		||||
            post_body["eco_account"] = str(self.eco_account.id)
 | 
			
		||||
        self._test_create_object(url, post_body)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tests/v1/delete/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tests/v1/delete/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										118
									
								
								api/tests/v1/delete/test_api_delete.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								api/tests/v1/delete/test_api_delete.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIV1DeleteTestCase(BaseAPIV1TestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def _run_delete_request(self, url):
 | 
			
		||||
        response = self.client.delete(
 | 
			
		||||
            url,
 | 
			
		||||
            **self.header_data
 | 
			
		||||
        )
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    def _test_delete_object(self, obj, url):
 | 
			
		||||
        """ Tests the API DELETE of a data object.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            url (str): The api delete url
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        obj.refresh_from_db()
 | 
			
		||||
        _id = obj.id
 | 
			
		||||
        self.assertIsNotNone(_id)
 | 
			
		||||
        self.assertIsNone(obj.deleted)
 | 
			
		||||
 | 
			
		||||
        response = self._run_delete_request(url)
 | 
			
		||||
        content = json.loads(response.content)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(response.status_code, 200, msg=response.content)
 | 
			
		||||
        self.assertTrue(content.get("success", False), msg=response.content)
 | 
			
		||||
 | 
			
		||||
        obj.refresh_from_db()
 | 
			
		||||
        self.assertIsNotNone(obj.deleted)
 | 
			
		||||
        self.assertEqual(obj.deleted.user, self.superuser)
 | 
			
		||||
 | 
			
		||||
    def test_delete_intervention(self):
 | 
			
		||||
        """ Tests api creation of bare minimum interventions
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        test_intervention = self.create_dummy_intervention()
 | 
			
		||||
        test_intervention.share_with(self.superuser)
 | 
			
		||||
        url = reverse("api:v1:intervention", args=(str(test_intervention.id),))
 | 
			
		||||
        self._test_delete_object(test_intervention, url)
 | 
			
		||||
 | 
			
		||||
    def test_delete_compensation(self):
 | 
			
		||||
        """ Tests api creation of bare minimum interventions
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        test_comp = self.create_dummy_compensation()
 | 
			
		||||
        test_comp.share_with(self.superuser)
 | 
			
		||||
        url = reverse("api:v1:compensation", args=(str(test_comp.id),))
 | 
			
		||||
        self._test_delete_object(test_comp, url)
 | 
			
		||||
 | 
			
		||||
    def test_delete_eco_account(self):
 | 
			
		||||
        """ Tests api creation of bare minimum interventions
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        test_acc = self.create_dummy_eco_account()
 | 
			
		||||
        test_acc.share_with(self.superuser)
 | 
			
		||||
        url = reverse("api:v1:ecoaccount", args=(str(test_acc.id),))
 | 
			
		||||
        self._test_delete_object(test_acc, url)
 | 
			
		||||
 | 
			
		||||
    def test_delete_ema(self):
 | 
			
		||||
        """ Tests api creation of bare minimum interventions
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        test_ema = self.create_dummy_ema()
 | 
			
		||||
        test_ema.share_with(self.superuser)
 | 
			
		||||
        url = reverse("api:v1:ema", args=(str(test_ema.id),))
 | 
			
		||||
        self._test_delete_object(test_ema, url)
 | 
			
		||||
 | 
			
		||||
    def test_delete_deduction(self):
 | 
			
		||||
        """ Tests api creation of bare minimum interventions
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        test_deduction = self.create_dummy_deduction()
 | 
			
		||||
        test_deduction.intervention.share_with(self.superuser)
 | 
			
		||||
        url = reverse("api:v1:deduction", args=(str(test_deduction.id),))
 | 
			
		||||
 | 
			
		||||
        response = self._run_delete_request(url)
 | 
			
		||||
        content = json.loads(response.content)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(response.status_code, 200, msg=response.content)
 | 
			
		||||
        self.assertTrue(content.get("success", False), msg=response.content)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            test_deduction.refresh_from_db()
 | 
			
		||||
            self.fail("Deduction is not deleted from db!")
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tests/v1/get/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tests/v1/get/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										187
									
								
								api/tests/v1/get/test_api_get.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								api/tests/v1/get/test_api_get.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,187 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIV1GetTestCase(BaseAPIV1TestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def _run_get_request(self, url):
 | 
			
		||||
        response = self.client.get(
 | 
			
		||||
            url,
 | 
			
		||||
            **self.header_data
 | 
			
		||||
        )
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    def _test_get_object(self, obj, url):
 | 
			
		||||
        """ Tests the API GET of a data object.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            url (str): The api get url
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        response = self._run_get_request(url)
 | 
			
		||||
        content = json.loads(response.content)
 | 
			
		||||
        geojson = content[str(obj.id)]
 | 
			
		||||
        self.assertEqual(response.status_code, 200, msg=response.content)
 | 
			
		||||
        return geojson
 | 
			
		||||
 | 
			
		||||
    def _assert_geojson_format(self, geojson):
 | 
			
		||||
        try:
 | 
			
		||||
            geojson["type"]
 | 
			
		||||
            geojson["coordinates"]
 | 
			
		||||
            props = geojson["properties"]
 | 
			
		||||
            props["id"]
 | 
			
		||||
            props["identifier"]
 | 
			
		||||
            props["title"]
 | 
			
		||||
            props["created_on"]
 | 
			
		||||
            props["modified_on"]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            self.fail(e)
 | 
			
		||||
 | 
			
		||||
    def test_get_intervention(self):
 | 
			
		||||
        """ Tests api GET
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
        url = reverse("api:v1:intervention", args=(str(self.intervention.id),))
 | 
			
		||||
        geojson = self._test_get_object(self.intervention, url)
 | 
			
		||||
        self._assert_geojson_format(geojson)
 | 
			
		||||
        try:
 | 
			
		||||
            props = geojson["properties"]
 | 
			
		||||
            props["responsible"]
 | 
			
		||||
            props["responsible"]["registration_office"]
 | 
			
		||||
            props["responsible"]["registration_file_number"]
 | 
			
		||||
            props["responsible"]["conservation_office"]
 | 
			
		||||
            props["responsible"]["conservation_file_number"]
 | 
			
		||||
            props["legal"]["registration_date"]
 | 
			
		||||
            props["legal"]["binding_date"]
 | 
			
		||||
            props["legal"]["process_type"]
 | 
			
		||||
            props["legal"]["laws"]
 | 
			
		||||
            props["compensations"]
 | 
			
		||||
            props["payments"]
 | 
			
		||||
            props["deductions"]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            self.fail(e)
 | 
			
		||||
 | 
			
		||||
    def test_get_compensation(self):
 | 
			
		||||
        """ Tests api GET
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
        self.compensation.intervention = self.intervention
 | 
			
		||||
        self.compensation.save()
 | 
			
		||||
 | 
			
		||||
        url = reverse("api:v1:compensation", args=(str(self.compensation.id),))
 | 
			
		||||
        geojson = self._test_get_object(self.compensation, url)
 | 
			
		||||
        self._assert_geojson_format(geojson)
 | 
			
		||||
        try:
 | 
			
		||||
            props = geojson["properties"]
 | 
			
		||||
            props["is_cef"]
 | 
			
		||||
            props["is_coherence_keeping"]
 | 
			
		||||
            props["intervention"]
 | 
			
		||||
            props["intervention"]["id"]
 | 
			
		||||
            props["intervention"]["identifier"]
 | 
			
		||||
            props["intervention"]["title"]
 | 
			
		||||
            props["before_states"]
 | 
			
		||||
            props["after_states"]
 | 
			
		||||
            props["actions"]
 | 
			
		||||
            props["deadlines"]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            self.fail(e)
 | 
			
		||||
 | 
			
		||||
    def test_get_eco_account(self):
 | 
			
		||||
        """ Tests api GET
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.eco_account.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        url = reverse("api:v1:ecoaccount", args=(str(self.eco_account.id),))
 | 
			
		||||
        geojson = self._test_get_object(self.eco_account, url)
 | 
			
		||||
        self._assert_geojson_format(geojson)
 | 
			
		||||
        try:
 | 
			
		||||
            props = geojson["properties"]
 | 
			
		||||
            props["deductable_surface"]
 | 
			
		||||
            props["deductable_surface_available"]
 | 
			
		||||
            props["responsible"]
 | 
			
		||||
            props["responsible"]["conservation_office"]
 | 
			
		||||
            props["responsible"]["conservation_file_number"]
 | 
			
		||||
            props["responsible"]["handler"]
 | 
			
		||||
            props["legal"]
 | 
			
		||||
            props["legal"]["agreement_date"]
 | 
			
		||||
            props["before_states"]
 | 
			
		||||
            props["after_states"]
 | 
			
		||||
            props["actions"]
 | 
			
		||||
            props["deadlines"]
 | 
			
		||||
            props["deductions"]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            self.fail(e)
 | 
			
		||||
 | 
			
		||||
    def test_get_ema(self):
 | 
			
		||||
        """ Tests api GET
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.ema.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        url = reverse("api:v1:ema", args=(str(self.ema.id),))
 | 
			
		||||
        geojson = self._test_get_object(self.ema, url)
 | 
			
		||||
        self._assert_geojson_format(geojson)
 | 
			
		||||
        try:
 | 
			
		||||
            props = geojson["properties"]
 | 
			
		||||
            props["responsible"]
 | 
			
		||||
            props["responsible"]["conservation_office"]
 | 
			
		||||
            props["responsible"]["conservation_file_number"]
 | 
			
		||||
            props["responsible"]["handler"]
 | 
			
		||||
            props["before_states"]
 | 
			
		||||
            props["after_states"]
 | 
			
		||||
            props["actions"]
 | 
			
		||||
            props["deadlines"]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            self.fail(e)
 | 
			
		||||
 | 
			
		||||
    def test_get_deduction(self):
 | 
			
		||||
        """ Tests api GET
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.deduction.intervention.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        url = reverse("api:v1:deduction", args=(str(self.deduction.id),))
 | 
			
		||||
        _json = self._test_get_object(self.deduction, url)
 | 
			
		||||
        try:
 | 
			
		||||
            _json["id"]
 | 
			
		||||
            _json["eco_account"]
 | 
			
		||||
            _json["eco_account"]["id"]
 | 
			
		||||
            _json["eco_account"]["identifier"]
 | 
			
		||||
            _json["eco_account"]["title"]
 | 
			
		||||
            _json["surface"]
 | 
			
		||||
            _json["intervention"]
 | 
			
		||||
            _json["intervention"]["id"]
 | 
			
		||||
            _json["intervention"]["identifier"]
 | 
			
		||||
            _json["intervention"]["title"]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            self.fail(e)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tests/v1/share/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tests/v1/share/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										153
									
								
								api/tests/v1/share/test_api_sharing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								api/tests/v1/share/test_api_sharing.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from konova.settings import DEFAULT_GROUP
 | 
			
		||||
from konova.tests.test_views import BaseTestCase
 | 
			
		||||
from konova.utils.user_checks import is_default_group_only
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseAPIV1TestCase(BaseTestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
        cls.superuser.get_API_token()
 | 
			
		||||
        cls.superuser.api_token.is_active = True
 | 
			
		||||
        cls.superuser.api_token.save()
 | 
			
		||||
        default_group = cls.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        cls.superuser.groups.add(default_group)
 | 
			
		||||
 | 
			
		||||
        cls.header_data = {
 | 
			
		||||
            "HTTP_ksptoken": cls.superuser.api_token.token,
 | 
			
		||||
            "HTTP_kspuser": cls.superuser.username,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIV1SharingTestCase(BaseAPIV1TestCase):
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def _run_share_request(self, url, user_list: list):
 | 
			
		||||
        data = {
 | 
			
		||||
            "users": user_list
 | 
			
		||||
        }
 | 
			
		||||
        data = json.dumps(data)
 | 
			
		||||
        response = self.client.put(
 | 
			
		||||
            url,
 | 
			
		||||
            data,
 | 
			
		||||
            **self.header_data
 | 
			
		||||
        )
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    def _test_api_sharing(self, obj, url):
 | 
			
		||||
        """ Generic test for testing sharing of a ShareableObjectMixin object
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (ShareableObjectMixin): The object
 | 
			
		||||
            url (str): The url to be used for a request
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.assertEqual(obj.users.count(), 0)
 | 
			
		||||
        user_list = [
 | 
			
		||||
            self.superuser.username,
 | 
			
		||||
            self.user.username,
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        response = self._run_share_request(url, user_list)
 | 
			
		||||
 | 
			
		||||
        # Must fail, since performing user has no access on requested object
 | 
			
		||||
        self.assertEqual(response.status_code, 500)
 | 
			
		||||
        self.assertTrue(len(json.loads(response.content.decode("utf-8")).get("errors", [])) > 0)
 | 
			
		||||
 | 
			
		||||
        # Add performing user to shared access users and rerun the request
 | 
			
		||||
        obj.users.add(self.superuser)
 | 
			
		||||
        response = self._run_share_request(url, user_list)
 | 
			
		||||
 | 
			
		||||
        shared_users = obj.shared_users
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertEqual(shared_users.count(), 2)
 | 
			
		||||
        self.assertIn(self.superuser, shared_users)
 | 
			
		||||
        self.assertIn(self.user, shared_users)
 | 
			
		||||
 | 
			
		||||
    def test_api_token_invalid(self):
 | 
			
		||||
        """ Tests that a request with an invalid token won't be successfull
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        share_url = reverse("api:v1:intervention-share", args=(self.intervention.id,))
 | 
			
		||||
        # Expect the first request to work properly
 | 
			
		||||
        self.intervention.users.add(self.superuser)
 | 
			
		||||
        response = self._run_share_request(share_url, [self.superuser.username])
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        # Change the token
 | 
			
		||||
        self.header_data["HTTP_ksptoken"] = f"{self.superuser.api_token.token}__X"
 | 
			
		||||
 | 
			
		||||
        # Expect the request to fail now
 | 
			
		||||
        response = self._run_share_request(share_url, [self.superuser.username])
 | 
			
		||||
        self.assertEqual(response.status_code, 403)
 | 
			
		||||
 | 
			
		||||
    def test_api_intervention_sharing(self):
 | 
			
		||||
        """ Tests proper sharing of intervention
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        share_url = reverse("api:v1:intervention-share", args=(self.intervention.id,))
 | 
			
		||||
        self._test_api_sharing(self.intervention, share_url)
 | 
			
		||||
 | 
			
		||||
    def test_api_eco_account_sharing(self):
 | 
			
		||||
        """ Tests proper sharing of eco account
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        share_url = reverse("api:v1:ecoaccount-share", args=(self.eco_account.id,))
 | 
			
		||||
        self._test_api_sharing(self.eco_account, share_url)
 | 
			
		||||
 | 
			
		||||
    def test_api_ema_sharing(self):
 | 
			
		||||
        """ Tests proper sharing of ema
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        share_url = reverse("api:v1:ema-share", args=(self.ema.id,))
 | 
			
		||||
        self._test_api_sharing(self.ema, share_url)
 | 
			
		||||
 | 
			
		||||
    def test_api_sharing_as_default_group_only(self):
 | 
			
		||||
        """ Tests that sharing using the API as an only default group user works as expected.
 | 
			
		||||
 | 
			
		||||
        Expected:
 | 
			
		||||
            Default only user can only add new users, having shared access. Removing them from the list of users
 | 
			
		||||
            having shared access is only possible if the user has further rights, e.g. being part of a registration
 | 
			
		||||
            or conservation office group.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        share_url = reverse("api:v1:intervention-share", args=(self.intervention.id,))
 | 
			
		||||
 | 
			
		||||
        # Give the user only default group rights
 | 
			
		||||
        default_group = self.groups.get(name=DEFAULT_GROUP)
 | 
			
		||||
        self.superuser.groups.set([default_group])
 | 
			
		||||
        self.assertTrue(is_default_group_only(self.superuser))
 | 
			
		||||
 | 
			
		||||
        # Add only him as shared_users an object
 | 
			
		||||
        self.intervention.users.set([self.superuser])
 | 
			
		||||
        self.assertEqual(self.intervention.users.count(), 1)
 | 
			
		||||
 | 
			
		||||
        # Try to add another user via API -> must work!
 | 
			
		||||
        response = self._run_share_request(share_url, [self.superuser.username, self.user.username])
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertEqual(self.intervention.users.count(), 2)
 | 
			
		||||
 | 
			
		||||
        # Now try to remove the user again -> expect no changes at all to the shared user list
 | 
			
		||||
        response = self._run_share_request(share_url, [self.superuser.username])
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertEqual(self.intervention.users.count(), 2)
 | 
			
		||||
							
								
								
									
										7
									
								
								api/tests/v1/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/tests/v1/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										61
									
								
								api/tests/v1/update/compensation_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								api/tests/v1/update/compensation_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
            [
 | 
			
		||||
                [
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.837371826171871,
 | 
			
		||||
                        50.80155187891526
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.835698127746578,
 | 
			
		||||
                        50.805267562209806
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.841062545776364,
 | 
			
		||||
                        50.806623577403386
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.848916053771969,
 | 
			
		||||
                        50.808359219420474
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.855696678161618,
 | 
			
		||||
                        50.807057493952975
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.854666709899899,
 | 
			
		||||
                        50.80423696434001
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.850461006164548,
 | 
			
		||||
                        50.80217570040005
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ]
 | 
			
		||||
                ]
 | 
			
		||||
            ]
 | 
			
		||||
        ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "TEST_compensation_CHANGED",
 | 
			
		||||
        "is_cef": true,
 | 
			
		||||
        "is_coherence_keeping": true,
 | 
			
		||||
        "intervention": "CHANGE_BEFORE_RUN!!!",
 | 
			
		||||
        "before_states": [],
 | 
			
		||||
        "after_states": [],
 | 
			
		||||
        "actions": [],
 | 
			
		||||
        "deadlines": [
 | 
			
		||||
            {
 | 
			
		||||
                "type": "finished",
 | 
			
		||||
                "date": "2022-01-31",
 | 
			
		||||
                "comment": "TEST_CHANGED"
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								api/tests/v1/update/deduction_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								api/tests/v1/update/deduction_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
    "eco_account": "CHANGE_BEFORE_RUN!!!",
 | 
			
		||||
    "surface": 523400.0,
 | 
			
		||||
    "intervention": "CHANGE_BEFORE_RUN!!!"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								api/tests/v1/update/ecoaccount_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								api/tests/v1/update/ecoaccount_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
            [
 | 
			
		||||
                [
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.837371826171871,
 | 
			
		||||
                        50.80155187891526
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.835698127746578,
 | 
			
		||||
                        50.805267562209806
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.841062545776364,
 | 
			
		||||
                        50.806623577403386
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.848916053771969,
 | 
			
		||||
                        50.808359219420474
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.855696678161618,
 | 
			
		||||
                        50.807057493952975
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.854666709899899,
 | 
			
		||||
                        50.80423696434001
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.850461006164548,
 | 
			
		||||
                        50.80217570040005
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ]
 | 
			
		||||
                ]
 | 
			
		||||
            ]
 | 
			
		||||
        ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "TEST_account_CHANGED",
 | 
			
		||||
        "deductable_surface": "100000.0",
 | 
			
		||||
        "responsible": {
 | 
			
		||||
            "conservation_office": null,
 | 
			
		||||
            "conservation_file_number": "123-TEST",
 | 
			
		||||
            "handler": "TEST_HANDLER_CHANGED"
 | 
			
		||||
        },
 | 
			
		||||
        "legal": {
 | 
			
		||||
            "agreement_date": "2022-01-11"
 | 
			
		||||
        },
 | 
			
		||||
        "before_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "after_states": [
 | 
			
		||||
        ],
 | 
			
		||||
        "actions": [
 | 
			
		||||
        ],
 | 
			
		||||
        "deadlines": [
 | 
			
		||||
            {
 | 
			
		||||
                "type": "finished",
 | 
			
		||||
                "date": "2022-01-31",
 | 
			
		||||
                "comment": "TEST_CHANGED"
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								api/tests/v1/update/ema_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								api/tests/v1/update/ema_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
            [
 | 
			
		||||
                [
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.837371826171871,
 | 
			
		||||
                        50.80155187891526
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.835698127746578,
 | 
			
		||||
                        50.805267562209806
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.841062545776364,
 | 
			
		||||
                        50.806623577403386
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.848916053771969,
 | 
			
		||||
                        50.808359219420474
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.855696678161618,
 | 
			
		||||
                        50.807057493952975
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.854666709899899,
 | 
			
		||||
                        50.80423696434001
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.850461006164548,
 | 
			
		||||
                        50.80217570040005
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ]
 | 
			
		||||
                ]
 | 
			
		||||
            ]
 | 
			
		||||
        ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "TEST_EMA_CHANGED",
 | 
			
		||||
        "responsible": {
 | 
			
		||||
            "conservation_office": null,
 | 
			
		||||
            "conservation_file_number": "TEST_CHANGED",
 | 
			
		||||
            "handler": "TEST_HANDLER_CHANGED"
 | 
			
		||||
        },
 | 
			
		||||
        "before_states": [],
 | 
			
		||||
        "after_states": [],
 | 
			
		||||
        "actions": [],
 | 
			
		||||
        "deadlines": [
 | 
			
		||||
            {
 | 
			
		||||
                "type": "finished",
 | 
			
		||||
                "date": "2022-01-31",
 | 
			
		||||
                "comment": "TEST_CHANGED"
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								api/tests/v1/update/intervention_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								api/tests/v1/update/intervention_update_put_body.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
{
 | 
			
		||||
    "type": "MultiPolygon",
 | 
			
		||||
    "coordinates": [
 | 
			
		||||
            [
 | 
			
		||||
                [
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.837371826171871,
 | 
			
		||||
                        50.80155187891526
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.835698127746578,
 | 
			
		||||
                        50.805267562209806
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.841062545776364,
 | 
			
		||||
                        50.806623577403386
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.848916053771969,
 | 
			
		||||
                        50.808359219420474
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.855696678161618,
 | 
			
		||||
                        50.807057493952975
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.854666709899899,
 | 
			
		||||
                        50.80423696434001
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.850461006164548,
 | 
			
		||||
                        50.80217570040005
 | 
			
		||||
                    ],
 | 
			
		||||
                    [
 | 
			
		||||
                        7.845568656921382,
 | 
			
		||||
                        50.79829702304368
 | 
			
		||||
                    ]
 | 
			
		||||
                ]
 | 
			
		||||
            ]
 | 
			
		||||
        ],
 | 
			
		||||
    "properties": {
 | 
			
		||||
        "title": "Test_intervention_CHANGED",
 | 
			
		||||
        "responsible": {
 | 
			
		||||
            "registration_office": null,
 | 
			
		||||
            "registration_file_number": "CHANGED",
 | 
			
		||||
            "conservation_office": null,
 | 
			
		||||
            "conservation_file_number": "CHANGED",
 | 
			
		||||
            "handler": null
 | 
			
		||||
        },
 | 
			
		||||
        "legal": {
 | 
			
		||||
            "registration_date": "2022-02-01",
 | 
			
		||||
            "binding_date": "2022-02-01",
 | 
			
		||||
            "process_type": null,
 | 
			
		||||
            "laws": []
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										186
									
								
								api/tests/v1/update/test_api_update.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								api/tests/v1/update/test_api_update.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.contrib.gis import geos
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class APIV1UpdateTestCase(BaseAPIV1TestCase):
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpTestData(cls):
 | 
			
		||||
        super().setUpTestData()
 | 
			
		||||
 | 
			
		||||
    def _run_update_request(self, url, data):
 | 
			
		||||
        data = json.dumps(data)
 | 
			
		||||
        response = self.client.put(
 | 
			
		||||
            url,
 | 
			
		||||
            data=data,
 | 
			
		||||
            content_type="application/json",
 | 
			
		||||
            **self.header_data
 | 
			
		||||
        )
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
    def _test_update_object(self, url, put_body):
 | 
			
		||||
        """ Tests the API update of a data object.
 | 
			
		||||
 | 
			
		||||
        Put body data stored in a local json file
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            url (str): The api creation url
 | 
			
		||||
            put_body (dict): The put body content as dict
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        response = self._run_update_request(url, put_body)
 | 
			
		||||
        self.assertEqual(response.status_code, 200, msg=response.content)
 | 
			
		||||
        content = json.loads(response.content)
 | 
			
		||||
        self.assertIsNotNone(content.get("id", None), msg=response.content)
 | 
			
		||||
 | 
			
		||||
    def test_update_intervention(self):
 | 
			
		||||
        """ Tests api update
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
        modified_on = self.intervention.modified
 | 
			
		||||
        url = reverse("api:v1:intervention", args=(str(self.intervention.id),))
 | 
			
		||||
        json_file_path = "api/tests/v1/update/intervention_update_put_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            put_body = json.load(fp=json_file)
 | 
			
		||||
        self._test_update_object(url, put_body)
 | 
			
		||||
        self.intervention.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        put_props = put_body["properties"]
 | 
			
		||||
        put_geom = geos.fromstr(json.dumps(put_body))
 | 
			
		||||
        self.assertEqual(put_geom, self.intervention.geometry.geom)
 | 
			
		||||
        self.assertEqual(put_props["title"], self.intervention.title)
 | 
			
		||||
        self.assertNotEqual(modified_on, self.intervention.modified)
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["registration_file_number"], self.intervention.responsible.registration_file_number)
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["conservation_file_number"], self.intervention.responsible.conservation_file_number)
 | 
			
		||||
        self.assertEqual(put_props["legal"]["registration_date"], str(self.intervention.legal.registration_date))
 | 
			
		||||
        self.assertEqual(put_props["legal"]["binding_date"], str(self.intervention.legal.binding_date))
 | 
			
		||||
 | 
			
		||||
    def test_update_compensation(self):
 | 
			
		||||
        """ Tests api update
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.compensation.intervention = self.intervention
 | 
			
		||||
        self.compensation.save()
 | 
			
		||||
        self.intervention.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        modified_on = self.compensation.modified
 | 
			
		||||
        url = reverse("api:v1:compensation", args=(str(self.compensation.id),))
 | 
			
		||||
        json_file_path = "api/tests/v1/update/compensation_update_put_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            put_body = json.load(fp=json_file)
 | 
			
		||||
            put_body["properties"]["intervention"] = str(self.intervention.id)
 | 
			
		||||
        self._test_update_object(url, put_body)
 | 
			
		||||
        self.compensation.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        put_props = put_body["properties"]
 | 
			
		||||
        put_geom = geos.fromstr(json.dumps(put_body))
 | 
			
		||||
        self.assertEqual(put_geom, self.compensation.geometry.geom)
 | 
			
		||||
        self.assertEqual(put_props["title"], self.compensation.title)
 | 
			
		||||
        self.assertNotEqual(modified_on, self.compensation.modified)
 | 
			
		||||
        self.assertEqual(put_props["is_cef"], self.compensation.is_cef)
 | 
			
		||||
        self.assertEqual(put_props["is_coherence_keeping"], self.compensation.is_coherence_keeping)
 | 
			
		||||
        self.assertEqual(len(put_props["actions"]), self.compensation.actions.count())
 | 
			
		||||
        self.assertEqual(len(put_props["before_states"]), self.compensation.before_states.count())
 | 
			
		||||
        self.assertEqual(len(put_props["after_states"]), self.compensation.after_states.count())
 | 
			
		||||
        self.assertEqual(len(put_props["deadlines"]), self.compensation.deadlines.count())
 | 
			
		||||
 | 
			
		||||
    def test_update_ecoaccount(self):
 | 
			
		||||
        """ Tests api update
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.eco_account.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        modified_on = self.eco_account.modified
 | 
			
		||||
        url = reverse("api:v1:ecoaccount", args=(str(self.eco_account.id),))
 | 
			
		||||
        json_file_path = "api/tests/v1/update/ecoaccount_update_put_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            put_body = json.load(fp=json_file)
 | 
			
		||||
        self._test_update_object(url, put_body)
 | 
			
		||||
        self.eco_account.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        put_props = put_body["properties"]
 | 
			
		||||
        put_geom = geos.fromstr(json.dumps(put_body))
 | 
			
		||||
        self.assertEqual(put_geom, self.eco_account.geometry.geom)
 | 
			
		||||
        self.assertEqual(put_props["title"], self.eco_account.title)
 | 
			
		||||
        self.assertNotEqual(modified_on, self.eco_account.modified)
 | 
			
		||||
        self.assertEqual(put_props["deductable_surface"], str(self.eco_account.deductable_surface))
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["conservation_office"], self.eco_account.responsible.conservation_office)
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["conservation_file_number"], self.eco_account.responsible.conservation_file_number)
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["handler"], self.eco_account.responsible.handler)
 | 
			
		||||
        self.assertEqual(put_props["legal"]["agreement_date"], str(self.eco_account.legal.registration_date))
 | 
			
		||||
        self.assertEqual(len(put_props["actions"]), self.eco_account.actions.count())
 | 
			
		||||
        self.assertEqual(len(put_props["before_states"]), self.eco_account.before_states.count())
 | 
			
		||||
        self.assertEqual(len(put_props["after_states"]), self.eco_account.after_states.count())
 | 
			
		||||
        self.assertEqual(len(put_props["deadlines"]), self.eco_account.deadlines.count())
 | 
			
		||||
 | 
			
		||||
    def test_update_ema(self):
 | 
			
		||||
        """ Tests api update
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.ema.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        modified_on = self.ema.modified
 | 
			
		||||
        url = reverse("api:v1:ema", args=(str(self.ema.id),))
 | 
			
		||||
        json_file_path = "api/tests/v1/update/ema_update_put_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            put_body = json.load(fp=json_file)
 | 
			
		||||
        self._test_update_object(url, put_body)
 | 
			
		||||
        self.ema.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        put_props = put_body["properties"]
 | 
			
		||||
        put_geom = geos.fromstr(json.dumps(put_body))
 | 
			
		||||
        self.assertEqual(put_geom, self.ema.geometry.geom)
 | 
			
		||||
        self.assertEqual(put_props["title"], self.ema.title)
 | 
			
		||||
        self.assertNotEqual(modified_on, self.ema.modified)
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["conservation_office"], self.ema.responsible.conservation_office)
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["conservation_file_number"], self.ema.responsible.conservation_file_number)
 | 
			
		||||
        self.assertEqual(put_props["responsible"]["handler"], self.ema.responsible.handler)
 | 
			
		||||
        self.assertEqual(len(put_props["actions"]), self.ema.actions.count())
 | 
			
		||||
        self.assertEqual(len(put_props["before_states"]), self.ema.before_states.count())
 | 
			
		||||
        self.assertEqual(len(put_props["after_states"]), self.ema.after_states.count())
 | 
			
		||||
        self.assertEqual(len(put_props["deadlines"]), self.ema.deadlines.count())
 | 
			
		||||
 | 
			
		||||
    def test_update_deduction(self):
 | 
			
		||||
        """ Tests api update
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.deduction.intervention.share_with(self.superuser)
 | 
			
		||||
        self.deduction.account.share_with(self.superuser)
 | 
			
		||||
 | 
			
		||||
        url = reverse("api:v1:deduction", args=(str(self.deduction.id),))
 | 
			
		||||
        json_file_path = "api/tests/v1/update/deduction_update_put_body.json"
 | 
			
		||||
        with open(json_file_path) as json_file:
 | 
			
		||||
            put_body = json.load(fp=json_file)
 | 
			
		||||
            put_body["intervention"] = str(self.deduction.intervention.id)
 | 
			
		||||
            put_body["eco_account"] = str(self.deduction.account.id)
 | 
			
		||||
 | 
			
		||||
        self._test_update_object(url, put_body)
 | 
			
		||||
        self.deduction.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(put_body["intervention"], str(self.deduction.intervention.id))
 | 
			
		||||
        self.assertEqual(put_body["eco_account"], str(self.deduction.account.id))
 | 
			
		||||
        self.assertEqual(put_body["surface"], self.deduction.surface)
 | 
			
		||||
							
								
								
									
										8
									
								
								api/urls/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								api/urls/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from .urls import *
 | 
			
		||||
							
								
								
									
										17
									
								
								api/urls/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								api/urls/urls.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.urls import path, include
 | 
			
		||||
 | 
			
		||||
from api.views.method_views import generate_new_token_view
 | 
			
		||||
 | 
			
		||||
app_name = "api"
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("v1/", include("api.urls.v1.urls", namespace="v1")),
 | 
			
		||||
    path("token/generate", generate_new_token_view, name="generate-new-token"),
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										7
									
								
								api/urls/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/urls/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										34
									
								
								api/urls/v1/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								api/urls/v1/urls.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.urls import path
 | 
			
		||||
 | 
			
		||||
from api.views.v1.views import EmaAPIViewV1, EcoAccountAPIViewV1, CompensationAPIViewV1, InterventionAPIViewV1, \
 | 
			
		||||
    DeductionAPIViewV1
 | 
			
		||||
from api.views.views import InterventionCheckAPIView, InterventionAPIShareView, EcoAccountAPIShareView, EmaAPIShareView
 | 
			
		||||
 | 
			
		||||
app_name = "v1"
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("intervention/<id>/check", InterventionCheckAPIView.as_view(), name="intervention-check"),
 | 
			
		||||
    path("intervention/<id>/share", InterventionAPIShareView.as_view(), name="intervention-share"),
 | 
			
		||||
    path("intervention/<id>", InterventionAPIViewV1.as_view(), name="intervention"),
 | 
			
		||||
    path("intervention/", InterventionAPIViewV1.as_view(), name="intervention"),
 | 
			
		||||
 | 
			
		||||
    path("compensation/<id>", CompensationAPIViewV1.as_view(), name="compensation"),
 | 
			
		||||
    path("compensation/", CompensationAPIViewV1.as_view(), name="compensation"),
 | 
			
		||||
 | 
			
		||||
    path("ecoaccount/<id>/share", EcoAccountAPIShareView.as_view(), name="ecoaccount-share"),
 | 
			
		||||
    path("ecoaccount/<id>", EcoAccountAPIViewV1.as_view(), name="ecoaccount"),
 | 
			
		||||
    path("ecoaccount/", EcoAccountAPIViewV1.as_view(), name="ecoaccount"),
 | 
			
		||||
 | 
			
		||||
    path("deduction/<id>", DeductionAPIViewV1.as_view(), name="deduction"),
 | 
			
		||||
    path("deduction/", DeductionAPIViewV1.as_view(), name="deduction"),
 | 
			
		||||
 | 
			
		||||
    path("ema/<id>/share", EmaAPIShareView.as_view(), name="ema-share"),
 | 
			
		||||
    path("ema/<id>", EmaAPIViewV1.as_view(), name="ema"),
 | 
			
		||||
    path("ema/", EmaAPIViewV1.as_view(), name="ema"),
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										7
									
								
								api/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/utils/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										7
									
								
								api/utils/serializer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/utils/serializer/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										166
									
								
								api/utils/serializer/serializer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								api/utils/serializer/serializer.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,166 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import json
 | 
			
		||||
from abc import abstractmethod
 | 
			
		||||
 | 
			
		||||
from django.contrib.gis import geos
 | 
			
		||||
from django.contrib.gis.geos import GEOSGeometry
 | 
			
		||||
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractModelAPISerializer:
 | 
			
		||||
    model = None
 | 
			
		||||
    lookup = None
 | 
			
		||||
    properties_data = None
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.lookup = {
 | 
			
		||||
            "id": None,  # must be set
 | 
			
		||||
            "deleted__isnull": True,
 | 
			
		||||
            "users__in": [],  # must be set
 | 
			
		||||
        }
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def _model_to_geo_json(self, entry):
 | 
			
		||||
        """ Defines the model as geo json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            entry (): The found entry from the database
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError("Must be implemented in subclasses")
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def _extend_properties_data(self, entry):
 | 
			
		||||
        """ Defines the 'properties' part of geo json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            entry (): The found entry from the database
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError("Must be implemented in subclasses")
 | 
			
		||||
 | 
			
		||||
    def prepare_lookup(self, _id, user):
 | 
			
		||||
        """ Updates lookup dict for db fetching
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            _id (str): The object's id
 | 
			
		||||
            user (User): The user requesting for
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if _id is None:
 | 
			
		||||
            # Return all objects
 | 
			
		||||
            del self.lookup["id"]
 | 
			
		||||
        else:
 | 
			
		||||
            # Return certain object
 | 
			
		||||
            self.lookup["id"] = _id
 | 
			
		||||
        self.lookup["users__in"] = [user]
 | 
			
		||||
 | 
			
		||||
    def fetch_and_serialize(self):
 | 
			
		||||
        """ Serializes the model entry according to the given lookup data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_data (dict)
 | 
			
		||||
        """
 | 
			
		||||
        entries = self.model.objects.filter(**self.lookup)
 | 
			
		||||
        serialized_data = {}
 | 
			
		||||
        for entry in entries:
 | 
			
		||||
            serialized_data[str(entry.id)] = self._model_to_geo_json(entry)
 | 
			
		||||
        return serialized_data
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def update_model_from_json(self, id, json_model, user):
 | 
			
		||||
        """ Updates an instance from given json data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The instance's to be updated
 | 
			
		||||
            json_model (dict): JSON data
 | 
			
		||||
            user (User): The performing user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError("Must be implemented in subclasses")
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def create_model_from_json(self, json_model, user):
 | 
			
		||||
        """ Creates a new instance from given json data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): JSON data
 | 
			
		||||
            user (User): The performing user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError("Must be implemented in subclasses")
 | 
			
		||||
 | 
			
		||||
    def _create_geometry_from_json(self, geojson) -> GEOSGeometry:
 | 
			
		||||
        """ Creates a GEOSGeometry object based on the given geojson
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            geojson (str|dict): The geojson as str or dict
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            geometry (GEOSGeometry)
 | 
			
		||||
        """
 | 
			
		||||
        if isinstance(geojson, dict):
 | 
			
		||||
            geojson = json.dumps(geojson)
 | 
			
		||||
        geometry = geos.fromstr(geojson)
 | 
			
		||||
        if geometry.empty:
 | 
			
		||||
            geometry = None
 | 
			
		||||
        return geometry
 | 
			
		||||
 | 
			
		||||
    def _get_obj_from_db(self, id, user):
 | 
			
		||||
        """ Returns the object from database
 | 
			
		||||
 | 
			
		||||
        Fails if id not found or user does not have shared access
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        obj = self.model.objects.get(
 | 
			
		||||
            id=id,
 | 
			
		||||
            deleted__isnull=True,
 | 
			
		||||
        )
 | 
			
		||||
        is_shared = obj.is_shared_with(user)
 | 
			
		||||
        if not is_shared:
 | 
			
		||||
            raise PermissionError(DATA_UNSHARED)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def _initialize_objects(self, json_model, user):
 | 
			
		||||
        """ Initializes all needed objects from the json_model data
 | 
			
		||||
 | 
			
		||||
        Does not persist data to the DB!
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Intervention)
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError("Must be implemented in subclasses")
 | 
			
		||||
							
								
								
									
										7
									
								
								api/utils/serializer/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/utils/serializer/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										169
									
								
								api/utils/serializer/v1/compensation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								api/utils/serializer/v1/compensation.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,169 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
 | 
			
		||||
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin
 | 
			
		||||
from compensation.models import Compensation
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.models import Geometry
 | 
			
		||||
from konova.tasks import celery_update_parcels
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin):
 | 
			
		||||
    model = Compensation
 | 
			
		||||
 | 
			
		||||
    def prepare_lookup(self, id, user):
 | 
			
		||||
        super().prepare_lookup(id, user)
 | 
			
		||||
        del self.lookup["users__in"]
 | 
			
		||||
        self.lookup["intervention__users__in"] = [user]
 | 
			
		||||
 | 
			
		||||
    def intervention_to_json(self, entry):
 | 
			
		||||
        return {
 | 
			
		||||
            "id": entry.pk,
 | 
			
		||||
            "identifier": entry.identifier,
 | 
			
		||||
            "title": entry.title,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _extend_properties_data(self, entry):
 | 
			
		||||
        self.properties_data["is_cef"] = entry.is_cef
 | 
			
		||||
        self.properties_data["is_coherence_keeping"] = entry.is_coherence_keeping
 | 
			
		||||
        self.properties_data["intervention"] = self.intervention_to_json(entry.intervention)
 | 
			
		||||
        self.properties_data["before_states"] = self._compensation_state_to_json(entry.before_states.all())
 | 
			
		||||
        self.properties_data["after_states"] = self._compensation_state_to_json(entry.after_states.all())
 | 
			
		||||
        self.properties_data["actions"] = self._compensation_actions_to_json(entry.actions.all())
 | 
			
		||||
        self.properties_data["deadlines"] = self._deadlines_to_json(entry.deadlines.all())
 | 
			
		||||
 | 
			
		||||
    def _initialize_objects(self, json_model, user):
 | 
			
		||||
        """ Initializes all needed objects from the json_model data
 | 
			
		||||
 | 
			
		||||
        Does not persist data to the DB!
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Compensation)
 | 
			
		||||
        """
 | 
			
		||||
        create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
 | 
			
		||||
        # Create geometry
 | 
			
		||||
        json_geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
        geometry = Geometry()
 | 
			
		||||
        geometry.geom = json_geom
 | 
			
		||||
        geometry.created = create_action
 | 
			
		||||
 | 
			
		||||
        # Create linked objects
 | 
			
		||||
        obj = Compensation()
 | 
			
		||||
        created = create_action
 | 
			
		||||
        obj.created = created
 | 
			
		||||
        obj.geometry = geometry
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def set_intervention(self, obj, intervention_id, user):
 | 
			
		||||
        """ Sets the linked compensation according to the given id
 | 
			
		||||
 | 
			
		||||
        Fails if no such intervention found or user has no shared access
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Compensation): The Compensation object
 | 
			
		||||
            intervention_id (str): The intervention's id
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Compensation)
 | 
			
		||||
        """
 | 
			
		||||
        if obj.intervention is not None and obj.intervention.id == intervention_id:
 | 
			
		||||
            # Nothing to do here
 | 
			
		||||
            return obj
 | 
			
		||||
 | 
			
		||||
        intervention = Intervention.objects.get(
 | 
			
		||||
            id=intervention_id,
 | 
			
		||||
        )
 | 
			
		||||
        is_shared = intervention.is_shared_with(user)
 | 
			
		||||
 | 
			
		||||
        if not is_shared:
 | 
			
		||||
            raise PermissionError(DATA_UNSHARED)
 | 
			
		||||
 | 
			
		||||
        obj.intervention = intervention
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def create_model_from_json(self, json_model, user):
 | 
			
		||||
        """ Creates a new entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Compensation entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            obj = self._initialize_objects(json_model, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.identifier = obj.generate_new_identifier()
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
            obj.is_cef = properties["is_cef"]
 | 
			
		||||
            obj.is_coherence_keeping = properties["is_coherence_keeping"]
 | 
			
		||||
            obj = self.set_intervention(obj, properties["intervention"], user)
 | 
			
		||||
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj = self._set_compensation_actions(obj, properties["actions"])
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["before_states"], obj.before_states)
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
 | 
			
		||||
            obj = self._set_deadlines(obj, properties["deadlines"])
 | 
			
		||||
 | 
			
		||||
            obj.log.add(obj.created)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
 | 
			
		||||
    def update_model_from_json(self, id, json_model, user):
 | 
			
		||||
        """ Updates an entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Compensation entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            update_action = UserActionLogEntry.get_edited_action(user, "API update")
 | 
			
		||||
            obj = self._get_obj_from_db(id, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
            obj.is_cef = properties["is_cef"]
 | 
			
		||||
            obj.is_coherence_keeping = properties["is_coherence_keeping"]
 | 
			
		||||
            obj.modified = update_action
 | 
			
		||||
            obj.geometry.geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
            obj.geometry.modified = update_action
 | 
			
		||||
            obj = self.set_intervention(obj, properties["intervention"], user)
 | 
			
		||||
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj = self._set_compensation_actions(obj, properties["actions"])
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["before_states"], obj.before_states)
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
 | 
			
		||||
            obj = self._set_deadlines(obj, properties["deadlines"])
 | 
			
		||||
 | 
			
		||||
            obj.log.add(update_action)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
							
								
								
									
										166
									
								
								api/utils/serializer/v1/deduction.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								api/utils/serializer/v1/deduction.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,166 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 28.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
 | 
			
		||||
from api.utils.serializer.v1.serializer import DeductableAPISerializerV1Mixin, AbstractModelAPISerializerV1
 | 
			
		||||
from compensation.models import EcoAccountDeduction, EcoAccount
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeductionAPISerializerV1(AbstractModelAPISerializerV1,
 | 
			
		||||
                               DeductableAPISerializerV1Mixin):
 | 
			
		||||
    model = EcoAccountDeduction
 | 
			
		||||
 | 
			
		||||
    def prepare_lookup(self, _id, user):
 | 
			
		||||
        """ Updates lookup dict for db fetching
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            _id (str): The object's id
 | 
			
		||||
            user (User): The user requesting for
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        super().prepare_lookup(_id, user)
 | 
			
		||||
        del self.lookup["users__in"]
 | 
			
		||||
        del self.lookup["deleted__isnull"]
 | 
			
		||||
        self.lookup["intervention__users__in"] = [user]
 | 
			
		||||
 | 
			
		||||
    def _model_to_geo_json(self, entry):
 | 
			
		||||
        """ Adds the basic data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            entry (): The data entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        return self._single_deduction_to_json(entry)
 | 
			
		||||
 | 
			
		||||
    def create_model_from_json(self, json_model, user):
 | 
			
		||||
        """ Creates a new entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Intervention entry
 | 
			
		||||
        """
 | 
			
		||||
        acc_id = json_model["eco_account"]
 | 
			
		||||
        intervention_id = json_model["intervention"]
 | 
			
		||||
        surface = float(json_model["surface"])
 | 
			
		||||
        if surface <= 0:
 | 
			
		||||
            raise ValueError("Surface must be > 0 m²")
 | 
			
		||||
 | 
			
		||||
        acc = EcoAccount.objects.get(
 | 
			
		||||
            id=acc_id,
 | 
			
		||||
            deleted__isnull=True,
 | 
			
		||||
        )
 | 
			
		||||
        intervention = Intervention.objects.get(
 | 
			
		||||
            id=intervention_id,
 | 
			
		||||
            deleted__isnull=True,
 | 
			
		||||
        )
 | 
			
		||||
        acc_shared = acc.is_shared_with(user)
 | 
			
		||||
        intervention_shared = intervention.is_shared_with(user)
 | 
			
		||||
        if not acc_shared:
 | 
			
		||||
            raise PermissionError(f"Account: {DATA_UNSHARED}")
 | 
			
		||||
        if not intervention_shared:
 | 
			
		||||
            raise PermissionError(f"Intervention: {DATA_UNSHARED}")
 | 
			
		||||
 | 
			
		||||
        deduction = self.model.objects.create(
 | 
			
		||||
            intervention=intervention,
 | 
			
		||||
            account=acc,
 | 
			
		||||
            surface=surface
 | 
			
		||||
        )
 | 
			
		||||
        deduction.intervention.mark_as_edited(user)
 | 
			
		||||
        return str(deduction.id)
 | 
			
		||||
 | 
			
		||||
    def _get_obj_from_db(self, id, user):
 | 
			
		||||
        """ Returns the object from database
 | 
			
		||||
 | 
			
		||||
        Fails if id not found or user does not have shared access
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        obj = self.model.objects.get(
 | 
			
		||||
            id=id,
 | 
			
		||||
        )
 | 
			
		||||
        shared_with = obj.intervention.is_shared_with(user)
 | 
			
		||||
        if not shared_with:
 | 
			
		||||
            raise PermissionError(f"Intervention: {DATA_UNSHARED}")
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def update_model_from_json(self, id, json_model, user):
 | 
			
		||||
        """ Updates an entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Intervention entry
 | 
			
		||||
        """
 | 
			
		||||
        deduction = self._get_obj_from_db(id, user)
 | 
			
		||||
 | 
			
		||||
        acc_id = json_model["eco_account"]
 | 
			
		||||
        intervention_id = json_model["intervention"]
 | 
			
		||||
        surface = float(json_model["surface"])
 | 
			
		||||
        if surface <= 0:
 | 
			
		||||
            raise ValueError("Surface must be > 0 m²")
 | 
			
		||||
 | 
			
		||||
        acc = EcoAccount.objects.get(
 | 
			
		||||
            id=acc_id,
 | 
			
		||||
            deleted__isnull=True,
 | 
			
		||||
        )
 | 
			
		||||
        intervention = Intervention.objects.get(
 | 
			
		||||
            id=intervention_id,
 | 
			
		||||
            deleted__isnull=True,
 | 
			
		||||
        )
 | 
			
		||||
        acc_shared = acc.is_shared_with(user)
 | 
			
		||||
        intervention_shared = intervention.is_shared_with(user)
 | 
			
		||||
        if not acc_shared:
 | 
			
		||||
            raise PermissionError(f"Account: {DATA_UNSHARED}")
 | 
			
		||||
        if not intervention_shared:
 | 
			
		||||
            raise PermissionError(f"Intervention: {DATA_UNSHARED}")
 | 
			
		||||
 | 
			
		||||
        deduction.intervention = intervention
 | 
			
		||||
        deduction.account = acc
 | 
			
		||||
        deduction.surface = surface
 | 
			
		||||
        deduction.save()
 | 
			
		||||
 | 
			
		||||
        deduction.intervention.mark_as_edited(user)
 | 
			
		||||
 | 
			
		||||
        return str(deduction.id)
 | 
			
		||||
 | 
			
		||||
    def delete_entry(self, id, user):
 | 
			
		||||
        """ Deletes the entry
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The entry's id
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        entry = self._get_obj_from_db(id, user)
 | 
			
		||||
        entry.intervention.mark_as_edited(user)
 | 
			
		||||
        entry.delete()
 | 
			
		||||
        try:
 | 
			
		||||
            entry.refresh_from_db()
 | 
			
		||||
            success = False
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            success = True
 | 
			
		||||
        return success
 | 
			
		||||
							
								
								
									
										186
									
								
								api/utils/serializer/v1/ecoaccount.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								api/utils/serializer/v1/ecoaccount.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
 | 
			
		||||
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, \
 | 
			
		||||
    LegalAPISerializerV1Mixin, ResponsibilityAPISerializerV1Mixin, DeductableAPISerializerV1Mixin
 | 
			
		||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
 | 
			
		||||
from compensation.models import EcoAccount
 | 
			
		||||
from intervention.models import Legal, Responsibility
 | 
			
		||||
from konova.models import Geometry
 | 
			
		||||
from konova.tasks import celery_update_parcels
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
 | 
			
		||||
                                AbstractCompensationAPISerializerV1Mixin,
 | 
			
		||||
                                LegalAPISerializerV1Mixin,
 | 
			
		||||
                                ResponsibilityAPISerializerV1Mixin,
 | 
			
		||||
                                DeductableAPISerializerV1Mixin):
 | 
			
		||||
    model = EcoAccount
 | 
			
		||||
 | 
			
		||||
    def _extend_properties_data(self, entry):
 | 
			
		||||
        self.properties_data["deductable_surface"] = entry.deductable_surface
 | 
			
		||||
        self.properties_data["deductable_surface_available"] = entry.deductable_surface - entry.get_deductions_surface()
 | 
			
		||||
        self.properties_data["responsible"] = self._responsible_to_json(entry.responsible)
 | 
			
		||||
        self.properties_data["legal"] = self._legal_to_json(entry.legal)
 | 
			
		||||
        self.properties_data["before_states"] = self._compensation_state_to_json(entry.before_states.all())
 | 
			
		||||
        self.properties_data["after_states"] = self._compensation_state_to_json(entry.after_states.all())
 | 
			
		||||
        self.properties_data["actions"] = self._compensation_actions_to_json(entry.actions.all())
 | 
			
		||||
        self.properties_data["deadlines"] = self._deadlines_to_json(entry.deadlines.all())
 | 
			
		||||
        self.properties_data["deductions"] = self._deductions_to_json(entry.deductions.all())
 | 
			
		||||
 | 
			
		||||
    def _legal_to_json(self, legal: Legal):
 | 
			
		||||
        return {
 | 
			
		||||
            "agreement_date": legal.registration_date,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _responsible_to_json(self, responsible: Responsibility):
 | 
			
		||||
        return {
 | 
			
		||||
            "conservation_office": self._konova_code_to_json(responsible.conservation_office),
 | 
			
		||||
            "conservation_file_number": responsible.conservation_file_number,
 | 
			
		||||
            "handler": responsible.handler,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _set_responsibility(self, obj, responsibility_data: dict):
 | 
			
		||||
        """ Sets the responsible data contents to the provided responsibility_data dict
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Intervention): The intervention object
 | 
			
		||||
            responsibility_data (dict): The new data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj
 | 
			
		||||
        """
 | 
			
		||||
        if responsibility_data is None:
 | 
			
		||||
            return obj
 | 
			
		||||
        obj.responsible.conservation_office = self._konova_code_from_json(
 | 
			
		||||
            responsibility_data["conservation_office"],
 | 
			
		||||
            CODELIST_CONSERVATION_OFFICE_ID,
 | 
			
		||||
        )
 | 
			
		||||
        obj.responsible.conservation_file_number = responsibility_data["conservation_file_number"]
 | 
			
		||||
        obj.responsible.handler = responsibility_data["handler"]
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def _set_legal(self, obj, legal_data):
 | 
			
		||||
        obj.legal.registration_date = legal_data.get("agreement_date", None)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def _initialize_objects(self, json_model, user):
 | 
			
		||||
        """ Initializes all needed objects from the json_model data
 | 
			
		||||
 | 
			
		||||
        Does not persist data to the DB!
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Compensation)
 | 
			
		||||
        """
 | 
			
		||||
        create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
 | 
			
		||||
        # Create geometry
 | 
			
		||||
        json_geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
        geometry = Geometry()
 | 
			
		||||
        geometry.geom = json_geom
 | 
			
		||||
        geometry.created = create_action
 | 
			
		||||
 | 
			
		||||
        # Create linked objects
 | 
			
		||||
        obj = EcoAccount()
 | 
			
		||||
        obj.responsible = Responsibility()
 | 
			
		||||
        obj.legal = Legal()
 | 
			
		||||
        created = create_action
 | 
			
		||||
        obj.created = created
 | 
			
		||||
        obj.geometry = geometry
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def create_model_from_json(self, json_model, user):
 | 
			
		||||
        """ Creates a new entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created EcoAccount entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            obj = self._initialize_objects(json_model, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.identifier = obj.generate_new_identifier()
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                obj.deductable_surface = float(properties["deductable_surface"])
 | 
			
		||||
            except TypeError:
 | 
			
		||||
                raise ValueError("Deductable surface (m²) must be a number >= 0")
 | 
			
		||||
            if obj.deductable_surface < 0:
 | 
			
		||||
                raise ValueError("Deductable surface (m²) must be greater or equal 0")
 | 
			
		||||
 | 
			
		||||
            obj = self._set_responsibility(obj, properties["responsible"])
 | 
			
		||||
            obj = self._set_legal(obj, properties["legal"])
 | 
			
		||||
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.responsible.save()
 | 
			
		||||
            obj.legal.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj = self._set_compensation_actions(obj, properties["actions"])
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["before_states"], obj.before_states)
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
 | 
			
		||||
            obj = self._set_deadlines(obj, properties["deadlines"])
 | 
			
		||||
 | 
			
		||||
            obj.log.add(obj.created)
 | 
			
		||||
            obj.users.add(user)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
 | 
			
		||||
    def update_model_from_json(self, id, json_model, user):
 | 
			
		||||
        """ Updates an entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created EcoAccount entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            update_action = UserActionLogEntry.get_edited_action(user, "API update")
 | 
			
		||||
            obj = self._get_obj_from_db(id, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
            obj.deductable_surface = float(properties["deductable_surface"])
 | 
			
		||||
            obj.modified = update_action
 | 
			
		||||
            obj.geometry.geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
            obj.geometry.modified = update_action
 | 
			
		||||
            obj = self._set_responsibility(obj, properties["responsible"])
 | 
			
		||||
            obj = self._set_legal(obj, properties["legal"])
 | 
			
		||||
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.responsible.save()
 | 
			
		||||
            obj.legal.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj = self._set_compensation_actions(obj, properties["actions"])
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["before_states"], obj.before_states)
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
 | 
			
		||||
            obj = self._set_deadlines(obj, properties["deadlines"])
 | 
			
		||||
 | 
			
		||||
            obj.log.add(update_action)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
							
								
								
									
										155
									
								
								api/utils/serializer/v1/ema.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								api/utils/serializer/v1/ema.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,155 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
 | 
			
		||||
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, \
 | 
			
		||||
    ResponsibilityAPISerializerV1Mixin
 | 
			
		||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
 | 
			
		||||
from ema.models import Ema
 | 
			
		||||
from intervention.models import Responsibility
 | 
			
		||||
from konova.models import Geometry
 | 
			
		||||
from konova.tasks import celery_update_parcels
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, ResponsibilityAPISerializerV1Mixin):
 | 
			
		||||
    model = Ema
 | 
			
		||||
 | 
			
		||||
    def _extend_properties_data(self, entry):
 | 
			
		||||
        self.properties_data["responsible"] = self._responsible_to_json(entry.responsible)
 | 
			
		||||
        self.properties_data["before_states"] = self._compensation_state_to_json(entry.before_states.all())
 | 
			
		||||
        self.properties_data["after_states"] = self._compensation_state_to_json(entry.after_states.all())
 | 
			
		||||
        self.properties_data["actions"] = self._compensation_actions_to_json(entry.actions.all())
 | 
			
		||||
        self.properties_data["deadlines"] = self._deadlines_to_json(entry.deadlines.all())
 | 
			
		||||
 | 
			
		||||
    def _responsible_to_json(self, responsible: Responsibility):
 | 
			
		||||
        return {
 | 
			
		||||
            "conservation_office": self._konova_code_to_json(responsible.conservation_office),
 | 
			
		||||
            "conservation_file_number": responsible.conservation_file_number,
 | 
			
		||||
            "handler": responsible.handler,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _set_responsibility(self, obj, responsibility_data: dict):
 | 
			
		||||
        """ Sets the responsible data contents to the provided responsibility_data dict
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Intervention): The intervention object
 | 
			
		||||
            responsibility_data (dict): The new data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj
 | 
			
		||||
        """
 | 
			
		||||
        if responsibility_data is None:
 | 
			
		||||
            return obj
 | 
			
		||||
        obj.responsible.conservation_office = self._konova_code_from_json(
 | 
			
		||||
            responsibility_data["conservation_office"],
 | 
			
		||||
            CODELIST_CONSERVATION_OFFICE_ID,
 | 
			
		||||
        )
 | 
			
		||||
        obj.responsible.conservation_file_number = responsibility_data["conservation_file_number"]
 | 
			
		||||
        obj.responsible.handler = responsibility_data["handler"]
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def _initialize_objects(self, json_model, user):
 | 
			
		||||
        """ Initializes all needed objects from the json_model data
 | 
			
		||||
 | 
			
		||||
        Does not persist data to the DB!
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Compensation)
 | 
			
		||||
        """
 | 
			
		||||
        create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
 | 
			
		||||
        # Create geometry
 | 
			
		||||
        json_geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
        geometry = Geometry()
 | 
			
		||||
        geometry.geom = json_geom
 | 
			
		||||
        geometry.created = create_action
 | 
			
		||||
 | 
			
		||||
        # Create linked objects
 | 
			
		||||
        obj = Ema()
 | 
			
		||||
        obj.responsible = Responsibility()
 | 
			
		||||
        created = create_action
 | 
			
		||||
        obj.created = created
 | 
			
		||||
        obj.geometry = geometry
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def create_model_from_json(self, json_model, user):
 | 
			
		||||
        """ Creates a new entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Ema entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            obj = self._initialize_objects(json_model, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.identifier = obj.generate_new_identifier()
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
            obj = self._set_responsibility(obj, properties["responsible"])
 | 
			
		||||
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.responsible.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj = self._set_compensation_actions(obj, properties["actions"])
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["before_states"], obj.before_states)
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
 | 
			
		||||
            obj = self._set_deadlines(obj, properties["deadlines"])
 | 
			
		||||
 | 
			
		||||
            obj.log.add(obj.created)
 | 
			
		||||
            obj.users.add(user)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
 | 
			
		||||
    def update_model_from_json(self, id, json_model, user):
 | 
			
		||||
        """ Updates an entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Ema entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            update_action = UserActionLogEntry.get_edited_action(user, "API update")
 | 
			
		||||
            obj = self._get_obj_from_db(id, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
            obj.modified = update_action
 | 
			
		||||
            obj.geometry.geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
            obj.geometry.modified = update_action
 | 
			
		||||
            obj = self._set_responsibility(obj, properties["responsible"])
 | 
			
		||||
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.responsible.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj = self._set_compensation_actions(obj, properties["actions"])
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["before_states"], obj.before_states)
 | 
			
		||||
            obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
 | 
			
		||||
            obj = self._set_deadlines(obj, properties["deadlines"])
 | 
			
		||||
 | 
			
		||||
            obj.log.add(update_action)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
							
								
								
									
										200
									
								
								api/utils/serializer/v1/intervention.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								api/utils/serializer/v1/intervention.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,200 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.db.models import QuerySet
 | 
			
		||||
 | 
			
		||||
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, \
 | 
			
		||||
    ResponsibilityAPISerializerV1Mixin, LegalAPISerializerV1Mixin, DeductableAPISerializerV1Mixin
 | 
			
		||||
from compensation.models import Payment
 | 
			
		||||
from intervention.models import Intervention, Responsibility, Legal
 | 
			
		||||
from konova.models import Geometry
 | 
			
		||||
from konova.tasks import celery_update_parcels
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
 | 
			
		||||
                                  ResponsibilityAPISerializerV1Mixin,
 | 
			
		||||
                                  LegalAPISerializerV1Mixin,
 | 
			
		||||
                                  DeductableAPISerializerV1Mixin):
 | 
			
		||||
    model = Intervention
 | 
			
		||||
 | 
			
		||||
    def _compensations_to_json(self, qs: QuerySet):
 | 
			
		||||
        return list(
 | 
			
		||||
            qs.values(
 | 
			
		||||
                "id", "identifier", "title"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def _payments_to_json(self, qs: QuerySet):
 | 
			
		||||
        """ Serializes payments into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            qs (QuerySet): A queryset of Payment entries
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (list)
 | 
			
		||||
        """
 | 
			
		||||
        return list(qs.values("amount", "due_on", "comment"))
 | 
			
		||||
 | 
			
		||||
    def _extend_properties_data(self, entry):
 | 
			
		||||
        self.properties_data["responsible"] = self._responsible_to_json(entry.responsible)
 | 
			
		||||
        self.properties_data["legal"] = self._legal_to_json(entry.legal)
 | 
			
		||||
        self.properties_data["compensations"] = self._compensations_to_json(entry.compensations.all())
 | 
			
		||||
        self.properties_data["payments"] = self._payments_to_json(entry.payments.all())
 | 
			
		||||
        self.properties_data["deductions"] = self._deductions_to_json(entry.deductions.all())
 | 
			
		||||
 | 
			
		||||
    def _initialize_objects(self, json_model, user):
 | 
			
		||||
        """ Initializes all needed objects from the json_model data
 | 
			
		||||
 | 
			
		||||
        Does not persist data to the DB!
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Intervention)
 | 
			
		||||
        """
 | 
			
		||||
        create_action = UserActionLogEntry.get_created_action(user, comment="API Import")
 | 
			
		||||
        # Create geometry
 | 
			
		||||
        json_geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
        geometry = Geometry()
 | 
			
		||||
        geometry.geom = json_geom
 | 
			
		||||
        geometry.created = create_action
 | 
			
		||||
 | 
			
		||||
        # Create linked objects
 | 
			
		||||
        obj = Intervention()
 | 
			
		||||
        resp = Responsibility()
 | 
			
		||||
        legal = Legal()
 | 
			
		||||
        created = create_action
 | 
			
		||||
        obj.legal = legal
 | 
			
		||||
        obj.created = created
 | 
			
		||||
        obj.geometry = geometry
 | 
			
		||||
        obj.responsible = resp
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def _set_payments(self, obj, payment_data):
 | 
			
		||||
        """ Sets the linked Payment data according to the given payment_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Compensation): The Compensation object
 | 
			
		||||
            payment_data (dict): The posted payment_data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (intervention)
 | 
			
		||||
        """
 | 
			
		||||
        if payment_data is None:
 | 
			
		||||
            return obj
 | 
			
		||||
        payments = []
 | 
			
		||||
        for entry in payment_data:
 | 
			
		||||
            due_on = entry["due_on"]
 | 
			
		||||
            amount = float(entry["amount"])
 | 
			
		||||
            comment = entry["comment"]
 | 
			
		||||
 | 
			
		||||
            # Check on validity
 | 
			
		||||
            if amount <= 0:
 | 
			
		||||
                raise ValueError("Payment amount must be > 0")
 | 
			
		||||
 | 
			
		||||
            no_due_on = due_on is None or len(due_on) == 0
 | 
			
		||||
            no_comment = comment is None or len(comment) == 0
 | 
			
		||||
 | 
			
		||||
            if no_due_on and no_comment:
 | 
			
		||||
                raise ValueError("If no due_on can be provided, you need to explain why using the comment")
 | 
			
		||||
 | 
			
		||||
            # If this exact data is already existing, we do not create it new. Instead put it's id in the list of
 | 
			
		||||
            # entries, we will use to set the new actions
 | 
			
		||||
            pre_existing_payment = obj.payments.filter(
 | 
			
		||||
                amount=amount,
 | 
			
		||||
                due_on=due_on,
 | 
			
		||||
                comment=comment,
 | 
			
		||||
            ).exclude(
 | 
			
		||||
                id__in=payments
 | 
			
		||||
            ).first()
 | 
			
		||||
            if pre_existing_payment is not None:
 | 
			
		||||
                payments.append(pre_existing_payment.id)
 | 
			
		||||
            else:
 | 
			
		||||
                # Create and add id to list
 | 
			
		||||
                new_payment = Payment.objects.create(
 | 
			
		||||
                    amount=amount,
 | 
			
		||||
                    due_on=due_on,
 | 
			
		||||
                    comment=comment,
 | 
			
		||||
                )
 | 
			
		||||
                payments.append(new_payment.id)
 | 
			
		||||
        payments = Payment.objects.filter(
 | 
			
		||||
            id__in=payments
 | 
			
		||||
        )
 | 
			
		||||
        obj.payments.set(payments)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def create_model_from_json(self, json_model, user):
 | 
			
		||||
        """ Creates a new entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Intervention entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            obj = self._initialize_objects(json_model, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.identifier = obj.generate_new_identifier()
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
            self._set_responsibility(obj, properties["responsible"])
 | 
			
		||||
            self._set_legal(obj, properties["legal"])
 | 
			
		||||
 | 
			
		||||
            obj.responsible.save()
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.legal.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj.users.add(user)
 | 
			
		||||
            obj.log.add(obj.created)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
 | 
			
		||||
    def update_model_from_json(self, id, json_model, user):
 | 
			
		||||
        """ Updates an entry for the model based on the contents of json_model
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
            json_model (dict): The json containing data
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_id (str): The id of the newly created Intervention entry
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            update_action = UserActionLogEntry.get_edited_action(user, "API update")
 | 
			
		||||
            obj = self._get_obj_from_db(id, user)
 | 
			
		||||
 | 
			
		||||
            # Fill in data to objects
 | 
			
		||||
            properties = json_model["properties"]
 | 
			
		||||
            obj.title = properties["title"]
 | 
			
		||||
            self._set_responsibility(obj, properties.get("responsible", None))
 | 
			
		||||
            self._set_legal(obj, properties.get("legal", None))
 | 
			
		||||
            self._set_payments(obj, properties.get("payments", None))
 | 
			
		||||
            obj.geometry.geom = self._create_geometry_from_json(json_model)
 | 
			
		||||
            obj.geometry.modified = update_action
 | 
			
		||||
 | 
			
		||||
            obj.responsible.save()
 | 
			
		||||
            obj.geometry.save()
 | 
			
		||||
            obj.legal.save()
 | 
			
		||||
            obj.save()
 | 
			
		||||
 | 
			
		||||
            obj.mark_as_edited(user)
 | 
			
		||||
 | 
			
		||||
            celery_update_parcels.delay(obj.geometry.id)
 | 
			
		||||
 | 
			
		||||
            return obj.id
 | 
			
		||||
							
								
								
									
										450
									
								
								api/utils/serializer/v1/serializer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								api/utils/serializer/v1/serializer.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,450 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 24.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon
 | 
			
		||||
from django.db.models import QuerySet
 | 
			
		||||
 | 
			
		||||
from api.utils.serializer.serializer import AbstractModelAPISerializer
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
 | 
			
		||||
    CODELIST_LAW_ID, CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID
 | 
			
		||||
from compensation.models import CompensationAction, UnitChoices, CompensationState
 | 
			
		||||
from intervention.models import Responsibility, Legal
 | 
			
		||||
from konova.models import Deadline, DeadlineType
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
 | 
			
		||||
    def _model_to_geo_json(self, entry):
 | 
			
		||||
        """ Adds the basic data, which all elements hold
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            entry (): The data entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if entry.geometry.geom is not None:
 | 
			
		||||
            geom = entry.geometry.geom.geojson
 | 
			
		||||
        else:
 | 
			
		||||
            geom = MultiPolygon().geojson
 | 
			
		||||
        geo_json = json.loads(geom)
 | 
			
		||||
        self.properties_data = {
 | 
			
		||||
            "id": entry.id,
 | 
			
		||||
            "identifier": entry.identifier,
 | 
			
		||||
            "title": entry.title,
 | 
			
		||||
            "created_on": self._created_on_to_json(entry),
 | 
			
		||||
            "modified_on": self._modified_on_to_json(entry),
 | 
			
		||||
        }
 | 
			
		||||
        self._extend_properties_data(entry)
 | 
			
		||||
        geo_json["properties"] = self.properties_data
 | 
			
		||||
        return geo_json
 | 
			
		||||
 | 
			
		||||
    def _konova_code_to_json(self, konova_code: KonovaCode):
 | 
			
		||||
        """ Serializes KonovaCode model into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            konova_code (KonovaCode): The KonovaCode entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (dict)
 | 
			
		||||
        """
 | 
			
		||||
        if konova_code is None:
 | 
			
		||||
            return None
 | 
			
		||||
        return {
 | 
			
		||||
            "atom_id": konova_code.atom_id,
 | 
			
		||||
            "long_name": konova_code.long_name,
 | 
			
		||||
            "short_name": konova_code.short_name,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _konova_code_from_json(self, json_str, code_list_identifier):
 | 
			
		||||
        """ Returns a konova code instance
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            json_str (str): The value for the code (atom id)
 | 
			
		||||
            code_list_identifier (str): From which konova code list this code is supposed to be from
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if json_str is None or len(json_str) == 0:
 | 
			
		||||
            return None
 | 
			
		||||
        code = KonovaCode.objects.get(
 | 
			
		||||
            atom_id=json_str,
 | 
			
		||||
            code_lists__in=[code_list_identifier]
 | 
			
		||||
        )
 | 
			
		||||
        return code
 | 
			
		||||
 | 
			
		||||
    def _created_on_to_json(self, entry):
 | 
			
		||||
        """ Serializes the created_on into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            entry (BaseObject): The entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            created_on (timestamp)
 | 
			
		||||
        """
 | 
			
		||||
        return entry.created.timestamp if entry.created is not None else None
 | 
			
		||||
 | 
			
		||||
    def _modified_on_to_json(self, entry):
 | 
			
		||||
        """ Serializes the modified_on into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            entry (BaseObject): The entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            modified_on (timestamp)
 | 
			
		||||
        """
 | 
			
		||||
        modified_on = entry.modified or entry.created
 | 
			
		||||
        modified_on = modified_on.timestamp if modified_on is not None else None
 | 
			
		||||
        return modified_on
 | 
			
		||||
 | 
			
		||||
    def delete_entry(self, id, user):
 | 
			
		||||
        """ Marks an entry as deleted
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The entry's id
 | 
			
		||||
            user (User): The API user
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        entry = self._get_obj_from_db(id, user)
 | 
			
		||||
        is_shared = entry.is_shared_with(user)
 | 
			
		||||
        if not is_shared:
 | 
			
		||||
            raise PermissionError(DATA_UNSHARED)
 | 
			
		||||
        # Do not send mails if entry is deleting using API. THere could be hundreds of deletion resulting in hundreds of
 | 
			
		||||
        # mails at once.
 | 
			
		||||
        entry.mark_as_deleted(user, send_mail=False)
 | 
			
		||||
        entry.refresh_from_db()
 | 
			
		||||
        success = entry.deleted is not None
 | 
			
		||||
        return success
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeductableAPISerializerV1Mixin:
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def _single_deduction_to_json(self, entry):
 | 
			
		||||
        """ Serializes a single eco account deduction into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            entry (EcoAccountDeduction): An EcoAccountDeduction
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (dict)
 | 
			
		||||
        """
 | 
			
		||||
        return {
 | 
			
		||||
            "id": entry.pk,
 | 
			
		||||
            "eco_account": {
 | 
			
		||||
                "id": entry.account.pk,
 | 
			
		||||
                "identifier": entry.account.identifier,
 | 
			
		||||
                "title": entry.account.title,
 | 
			
		||||
            },
 | 
			
		||||
            "surface": entry.surface,
 | 
			
		||||
            "intervention": {
 | 
			
		||||
                "id": entry.intervention.pk,
 | 
			
		||||
                "identifier": entry.intervention.identifier,
 | 
			
		||||
                "title": entry.intervention.title,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _deductions_to_json(self, qs: QuerySet):
 | 
			
		||||
        """ Serializes eco account deductions into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            qs (QuerySet): A queryset of EcoAccountDeduction entries
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (list)
 | 
			
		||||
        """
 | 
			
		||||
        return [
 | 
			
		||||
            self._single_deduction_to_json(entry)
 | 
			
		||||
            for entry in qs
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResponsibilityAPISerializerV1Mixin:
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def _responsible_to_json(self, responsible: Responsibility):
 | 
			
		||||
        """ Serializes Responsibility model into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            responsible (Responsibility): The Responsibility entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (dict)
 | 
			
		||||
        """
 | 
			
		||||
        return {
 | 
			
		||||
            "registration_office": self._konova_code_to_json(responsible.registration_office),
 | 
			
		||||
            "registration_file_number": responsible.registration_file_number,
 | 
			
		||||
            "conservation_office": self._konova_code_to_json(responsible.conservation_office),
 | 
			
		||||
            "conservation_file_number": responsible.conservation_file_number,
 | 
			
		||||
            "handler": responsible.handler,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _set_responsibility(self, obj, responsibility_data: dict):
 | 
			
		||||
        """ Sets the responsible data contents to the provided responsibility_data dict
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Intervention): The intervention object
 | 
			
		||||
            responsibility_data (dict): The new data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj
 | 
			
		||||
        """
 | 
			
		||||
        if responsibility_data is None:
 | 
			
		||||
            return obj
 | 
			
		||||
        obj.responsible.registration_office = self._konova_code_from_json(
 | 
			
		||||
            responsibility_data["registration_office"],
 | 
			
		||||
            CODELIST_REGISTRATION_OFFICE_ID
 | 
			
		||||
        )
 | 
			
		||||
        obj.responsible.registration_file_number = responsibility_data["registration_file_number"]
 | 
			
		||||
        obj.responsible.conservation_office = self._konova_code_from_json(
 | 
			
		||||
            responsibility_data["conservation_office"],
 | 
			
		||||
            CODELIST_CONSERVATION_OFFICE_ID,
 | 
			
		||||
        )
 | 
			
		||||
        obj.responsible.conservation_file_number = responsibility_data["conservation_file_number"]
 | 
			
		||||
        obj.responsible.handler = responsibility_data["handler"]
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LegalAPISerializerV1Mixin:
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def _legal_to_json(self, legal: Legal):
 | 
			
		||||
        """ Serializes Legal model into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            legal (Legal): The Legal entry
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (dict)
 | 
			
		||||
        """
 | 
			
		||||
        return {
 | 
			
		||||
            "registration_date": legal.registration_date,
 | 
			
		||||
            "binding_date": legal.binding_date,
 | 
			
		||||
            "process_type": self._konova_code_to_json(legal.process_type),
 | 
			
		||||
            "laws": [self._konova_code_to_json(law) for law in legal.laws.all()],
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def _set_legal(self, obj, legal_data):
 | 
			
		||||
        """ Sets the legal data contents to the provided legal_data dict
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Intervention): The intervention object
 | 
			
		||||
            legal_data (dict): The new data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj
 | 
			
		||||
        """
 | 
			
		||||
        if legal_data is None:
 | 
			
		||||
            return obj
 | 
			
		||||
        obj.legal.registration_date = legal_data.get("registration_date", None)
 | 
			
		||||
        obj.legal.binding_date = legal_data.get("binding_date", None)
 | 
			
		||||
        obj.legal.process_type = self._konova_code_from_json(
 | 
			
		||||
            legal_data.get("process_type", None),
 | 
			
		||||
            CODELIST_PROCESS_TYPE_ID,
 | 
			
		||||
        )
 | 
			
		||||
        laws = [self._konova_code_from_json(law, CODELIST_LAW_ID) for law in legal_data.get("laws", [])]
 | 
			
		||||
        obj.legal.laws.set(laws)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractCompensationAPISerializerV1Mixin:
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def _set_deadlines(self, obj, deadline_data):
 | 
			
		||||
        """ Sets the linked deadline data according to the given deadline_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Compensation): The Compensation object
 | 
			
		||||
            deadline_data (dict): The posted deadline_data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Compensation)
 | 
			
		||||
        """
 | 
			
		||||
        deadlines = []
 | 
			
		||||
        for entry in deadline_data:
 | 
			
		||||
            deadline_type = entry["type"]
 | 
			
		||||
            date = entry["date"]
 | 
			
		||||
            comment = entry["comment"]
 | 
			
		||||
 | 
			
		||||
            # Check on validity
 | 
			
		||||
            if deadline_type not in DeadlineType:
 | 
			
		||||
                raise ValueError(f"Invalid deadline type. Choices are {DeadlineType.values}")
 | 
			
		||||
 | 
			
		||||
            # If this exact data is already existing, we do not create it new. Instead put it's id in the list of
 | 
			
		||||
            # entries, we will use to set the new actions
 | 
			
		||||
            pre_existing_deadline = obj.deadlines.filter(
 | 
			
		||||
                type=deadline_type,
 | 
			
		||||
                date=date,
 | 
			
		||||
                comment=comment,
 | 
			
		||||
            ).exclude(
 | 
			
		||||
                id__in=deadlines
 | 
			
		||||
            ).first()
 | 
			
		||||
            if pre_existing_deadline is not None:
 | 
			
		||||
                deadlines.append(pre_existing_deadline.id)
 | 
			
		||||
            else:
 | 
			
		||||
                # Create and add id to list
 | 
			
		||||
                new_deadline = Deadline.objects.create(
 | 
			
		||||
                    type=deadline_type,
 | 
			
		||||
                    date=date,
 | 
			
		||||
                    comment=comment,
 | 
			
		||||
                )
 | 
			
		||||
                deadlines.append(new_deadline.id)
 | 
			
		||||
        obj.deadlines.set(deadlines)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def _set_compensation_states(self, obj, states_data, states_manager):
 | 
			
		||||
        """ Sets the linked compensation state data according to the given states_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Compensation): The Compensation object
 | 
			
		||||
            states_data (dict): The posted states_data
 | 
			
		||||
            states_manager (Manager): The before_states or after_states manager
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Compensation)
 | 
			
		||||
        """
 | 
			
		||||
        states = []
 | 
			
		||||
        for entry in states_data:
 | 
			
		||||
            biotope_type = entry["biotope"]
 | 
			
		||||
            surface = float(entry["surface"])
 | 
			
		||||
 | 
			
		||||
            # Check on validity
 | 
			
		||||
            if surface <= 0:
 | 
			
		||||
                raise ValueError("State surfaces must be > 0")
 | 
			
		||||
 | 
			
		||||
            # If this exact data is already existing, we do not create it new. Instead put it's id in the list of
 | 
			
		||||
            # entries, we will use to set the new actions
 | 
			
		||||
            pre_existing_state = states_manager.filter(
 | 
			
		||||
                biotope_type__atom_id=biotope_type,
 | 
			
		||||
                surface=surface,
 | 
			
		||||
            ).exclude(
 | 
			
		||||
                id__in=states
 | 
			
		||||
            ).first()
 | 
			
		||||
            if pre_existing_state is not None:
 | 
			
		||||
                states.append(pre_existing_state.id)
 | 
			
		||||
            else:
 | 
			
		||||
                # Create and add id to list
 | 
			
		||||
                new_state = CompensationState.objects.create(
 | 
			
		||||
                    biotope_type=self._konova_code_from_json(biotope_type, CODELIST_BIOTOPES_ID),
 | 
			
		||||
                    surface=surface
 | 
			
		||||
                )
 | 
			
		||||
                states.append(new_state.id)
 | 
			
		||||
 | 
			
		||||
        states_manager.set(states)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def _set_compensation_actions(self, obj, actions_data):
 | 
			
		||||
        """ Sets the linked compensation action data according to the given actions_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Compensation): The Compensation object
 | 
			
		||||
            actions_data (dict): The posted actions_data
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            obj (Compensation)
 | 
			
		||||
        """
 | 
			
		||||
        actions = []
 | 
			
		||||
        for entry in actions_data:
 | 
			
		||||
            action = entry["action"]
 | 
			
		||||
            amount = float(entry["amount"])
 | 
			
		||||
            unit = entry["unit"]
 | 
			
		||||
            comment = entry["comment"]
 | 
			
		||||
 | 
			
		||||
            # Check on validity
 | 
			
		||||
            if amount <= 0:
 | 
			
		||||
                raise ValueError("Action amount must be > 0")
 | 
			
		||||
            if unit not in UnitChoices:
 | 
			
		||||
                raise ValueError(f"Invalid unit. Choices are {UnitChoices.values}")
 | 
			
		||||
 | 
			
		||||
            # If this exact data is already existing, we do not create it new. Instead put it's id in the list of
 | 
			
		||||
            # entries, we will use to set the new actions
 | 
			
		||||
            pre_existing_action = obj.actions.filter(
 | 
			
		||||
                action_type__atom_id=action,
 | 
			
		||||
                amount=amount,
 | 
			
		||||
                unit=unit,
 | 
			
		||||
                comment=comment,
 | 
			
		||||
            ).exclude(
 | 
			
		||||
                id__in=actions
 | 
			
		||||
            ).first()
 | 
			
		||||
            if pre_existing_action is not None:
 | 
			
		||||
                actions.append(pre_existing_action.id)
 | 
			
		||||
            else:
 | 
			
		||||
                # Create and add id to list
 | 
			
		||||
                new_action = CompensationAction.objects.create(
 | 
			
		||||
                    action_type=self._konova_code_from_json(action, CODELIST_COMPENSATION_ACTION_ID),
 | 
			
		||||
                    amount=amount,
 | 
			
		||||
                    unit=unit,
 | 
			
		||||
                    comment=comment,
 | 
			
		||||
                )
 | 
			
		||||
                actions.append(new_action.id)
 | 
			
		||||
        obj.actions.set(actions)
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
    def _compensation_state_to_json(self, qs: QuerySet):
 | 
			
		||||
        """ Serializes compensation states into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            qs (QuerySet): A queryset of CompensationState entries
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (list)
 | 
			
		||||
        """
 | 
			
		||||
        return [
 | 
			
		||||
            {
 | 
			
		||||
                "biotope": self._konova_code_to_json(entry.biotope_type),
 | 
			
		||||
                "surface": entry.surface,
 | 
			
		||||
            }
 | 
			
		||||
            for entry in qs
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def _compensation_actions_to_json(self, qs: QuerySet):
 | 
			
		||||
        """ Serializes CompensationActions into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            qs (QuerySet): A queryset of CompensationAction entries
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (list)
 | 
			
		||||
        """
 | 
			
		||||
        return [
 | 
			
		||||
            {
 | 
			
		||||
                "action": self._konova_code_to_json(entry.action_type),
 | 
			
		||||
                "amount": entry.amount,
 | 
			
		||||
                "unit": entry.unit,
 | 
			
		||||
                "comment": entry.comment,
 | 
			
		||||
            }
 | 
			
		||||
            for entry in qs
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def _deadlines_to_json(self, qs: QuerySet):
 | 
			
		||||
        """ Serializes deadlines into json
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            qs (QuerySet): A queryset of Deadline entries
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            serialized_json (list)
 | 
			
		||||
        """
 | 
			
		||||
        return list(qs.values(
 | 
			
		||||
            "type",
 | 
			
		||||
            "date",
 | 
			
		||||
            "comment",
 | 
			
		||||
        ))
 | 
			
		||||
							
								
								
									
										8
									
								
								api/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								api/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from .v1 import *
 | 
			
		||||
							
								
								
									
										35
									
								
								api/views/method_views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								api/views/method_views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 27.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.http import HttpRequest, JsonResponse
 | 
			
		||||
 | 
			
		||||
from api.models import APIUserToken
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def generate_new_token_view(request: HttpRequest):
 | 
			
		||||
    """ Handles request for fetching
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        request (HttpRequest): The incoming request
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if request.method == "GET":
 | 
			
		||||
        token = APIUserToken()
 | 
			
		||||
        while APIUserToken.objects.filter(token=token.token).exists():
 | 
			
		||||
            token = APIUserToken()
 | 
			
		||||
        return JsonResponse(
 | 
			
		||||
            data={
 | 
			
		||||
                "gen_data": token.token
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
							
								
								
									
										7
									
								
								api/views/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								api/views/v1/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
							
								
								
									
										132
									
								
								api/views/v1/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								api/views/v1/views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.http import JsonResponse, HttpRequest
 | 
			
		||||
 | 
			
		||||
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
 | 
			
		||||
from api.utils.serializer.v1.deduction import DeductionAPISerializerV1
 | 
			
		||||
from api.utils.serializer.v1.ecoaccount import EcoAccountAPISerializerV1
 | 
			
		||||
from api.utils.serializer.v1.ema import EmaAPISerializerV1
 | 
			
		||||
from api.utils.serializer.v1.intervention import InterventionAPISerializerV1
 | 
			
		||||
from api.views.views import AbstractAPIView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractAPIViewV1(AbstractAPIView):
 | 
			
		||||
    """ Holds general serialization functions for API v1
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    serializer = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.lookup = {
 | 
			
		||||
            "id": None,  # must be set in subclasses
 | 
			
		||||
            "deleted__isnull": True,
 | 
			
		||||
            "users__in": [],  # must be set in subclasses
 | 
			
		||||
        }
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.serializer = self.serializer()
 | 
			
		||||
 | 
			
		||||
    def get(self, request: HttpRequest, id=None):
 | 
			
		||||
        """ Handles the GET request
 | 
			
		||||
 | 
			
		||||
        Performs the fetching and serialization of the data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
            id (str): The entries id (optional)
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            response (JsonResponse)
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            self.serializer.prepare_lookup(id, self.user)
 | 
			
		||||
            data = self.serializer.fetch_and_serialize()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return self.return_error_response(e, 500)
 | 
			
		||||
        return JsonResponse(data)
 | 
			
		||||
 | 
			
		||||
    def post(self, request: HttpRequest):
 | 
			
		||||
        """ Handles the POST request
 | 
			
		||||
 | 
			
		||||
        Performs creation of new data
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            response (JsonResponse)
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            body = request.body.decode("utf-8")
 | 
			
		||||
            body = json.loads(body)
 | 
			
		||||
            created_id = self.serializer.create_model_from_json(body, self.user)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return self.return_error_response(e, 500)
 | 
			
		||||
        return JsonResponse({"id": created_id})
 | 
			
		||||
 | 
			
		||||
    def put(self, request: HttpRequest, id=None):
 | 
			
		||||
        """ Handles the PUT request
 | 
			
		||||
 | 
			
		||||
        Performs updating
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
            id (str): The entries id
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            response (JsonResponse)
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            body = request.body.decode("utf-8")
 | 
			
		||||
            body = json.loads(body)
 | 
			
		||||
            updated_id = self.serializer.update_model_from_json(id, body, self.user)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return self.return_error_response(e, 500)
 | 
			
		||||
        return JsonResponse({"id": updated_id})
 | 
			
		||||
 | 
			
		||||
    def delete(self, request: HttpRequest, id=None):
 | 
			
		||||
        """ Handles a DELETE request
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            response (JsonResponse)
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            success = self.serializer.delete_entry(id, self.user)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return self.return_error_response(e, 500)
 | 
			
		||||
        return JsonResponse(
 | 
			
		||||
            {
 | 
			
		||||
                "success": success,
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InterventionAPIViewV1(AbstractAPIViewV1):
 | 
			
		||||
    serializer = InterventionAPISerializerV1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CompensationAPIViewV1(AbstractAPIViewV1):
 | 
			
		||||
    serializer = CompensationAPISerializerV1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountAPIViewV1(AbstractAPIViewV1):
 | 
			
		||||
    serializer = EcoAccountAPISerializerV1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmaAPIViewV1(AbstractAPIViewV1):
 | 
			
		||||
    serializer = EmaAPISerializerV1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeductionAPIViewV1(AbstractAPIViewV1):
 | 
			
		||||
    serializer = DeductionAPISerializerV1
 | 
			
		||||
							
								
								
									
										270
									
								
								api/views/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								api/views/views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,270 @@
 | 
			
		||||
"""
 | 
			
		||||
Author: Michel Peltriaux
 | 
			
		||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
 | 
			
		||||
Contact: michel.peltriaux@sgdnord.rlp.de
 | 
			
		||||
Created on: 21.01.22
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from django.db.models import QuerySet
 | 
			
		||||
from django.http import JsonResponse, HttpRequest
 | 
			
		||||
from django.views import View
 | 
			
		||||
from django.views.decorators.csrf import csrf_exempt
 | 
			
		||||
 | 
			
		||||
from api.models import APIUserToken
 | 
			
		||||
from api.settings import KSP_TOKEN_HEADER_IDENTIFIER, KSP_USER_HEADER_IDENTIFIER
 | 
			
		||||
from compensation.models import EcoAccount
 | 
			
		||||
from ema.models import Ema
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.utils.message_templates import DATA_UNSHARED
 | 
			
		||||
from konova.utils.user_checks import is_default_group_only
 | 
			
		||||
from user.models import User
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractAPIView(View):
 | 
			
		||||
    """ Base class for API views
 | 
			
		||||
 | 
			
		||||
    The API must follow the GeoJSON Specification RFC 7946
 | 
			
		||||
    https://geojson.org/
 | 
			
		||||
    https://datatracker.ietf.org/doc/html/rfc7946
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    user = None
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    @csrf_exempt
 | 
			
		||||
    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)
 | 
			
		||||
            ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
 | 
			
		||||
            self.user = APIUserToken.get_user_from_token(ksp_token, ksp_user)
 | 
			
		||||
            if not self.user.is_default_user():
 | 
			
		||||
                raise PermissionError("Default permissions required")
 | 
			
		||||
        except PermissionError as e:
 | 
			
		||||
            return self.return_error_response(e, 403)
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def return_error_response(self, error, status_code=500):
 | 
			
		||||
        """ Returns an error as JsonReponse
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            error (): The error/exception
 | 
			
		||||
            status_code (): The desired status code
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        content = [error.__str__()]
 | 
			
		||||
        if hasattr(error, "messages"):
 | 
			
		||||
            content = error.messages
 | 
			
		||||
        return JsonResponse(
 | 
			
		||||
            {
 | 
			
		||||
                "errors": content
 | 
			
		||||
            },
 | 
			
		||||
            status=status_code
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InterventionCheckAPIView(AbstractAPIView):
 | 
			
		||||
 | 
			
		||||
    def get(self, request: HttpRequest, id):
 | 
			
		||||
        """ Takes the GET request
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
            id (str): The intervention's id
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            response (JsonResponse)
 | 
			
		||||
        """
 | 
			
		||||
        if not self.user.is_zb_user():
 | 
			
		||||
            return self.return_error_response("Permission not granted", 403)
 | 
			
		||||
        try:
 | 
			
		||||
            obj = Intervention.objects.get(
 | 
			
		||||
                id=id,
 | 
			
		||||
                users__in=[self.user]
 | 
			
		||||
            )
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return self.return_error_response(e)
 | 
			
		||||
 | 
			
		||||
        all_valid, check_details = self.run_quality_checks(obj)
 | 
			
		||||
 | 
			
		||||
        if all_valid:
 | 
			
		||||
            log_entry = obj.set_checked(self.user)
 | 
			
		||||
            obj.log.add(log_entry)
 | 
			
		||||
 | 
			
		||||
        data = {
 | 
			
		||||
            "success": all_valid,
 | 
			
		||||
            "details": check_details
 | 
			
		||||
        }
 | 
			
		||||
        return JsonResponse(data)
 | 
			
		||||
 | 
			
		||||
    def run_quality_checks(self, obj: Intervention) -> (bool, dict):
 | 
			
		||||
        """ Performs a check for intervention and related compensations
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (Intervention): The intervention
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            all_valid (boold): Whether an error occured or not
 | 
			
		||||
            check_details (dict): A dict containg details on which elements have errors
 | 
			
		||||
        """
 | 
			
		||||
        # Run quality check for Intervention
 | 
			
		||||
        all_valid = True
 | 
			
		||||
        intervention_checker = obj.quality_check()
 | 
			
		||||
        all_valid = intervention_checker.valid and all_valid
 | 
			
		||||
 | 
			
		||||
        # Run quality checks for linked compensations
 | 
			
		||||
        comps = obj.compensations.all()
 | 
			
		||||
        comp_checkers = []
 | 
			
		||||
        for comp in comps:
 | 
			
		||||
            comp_checker = comp.quality_check()
 | 
			
		||||
            comp_checkers.append(comp_checker)
 | 
			
		||||
            all_valid = comp_checker.valid and all_valid
 | 
			
		||||
 | 
			
		||||
        check_details = {
 | 
			
		||||
            "intervention": {
 | 
			
		||||
                "id": obj.id,
 | 
			
		||||
                "errors": intervention_checker.messages
 | 
			
		||||
            },
 | 
			
		||||
            "compensations": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": comp_checker.obj.id,
 | 
			
		||||
                    "errors": comp_checker.messages
 | 
			
		||||
                }
 | 
			
		||||
                for comp_checker in comp_checkers
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
        return all_valid, check_details
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AbstractModelShareAPIView(AbstractAPIView):
 | 
			
		||||
    model = None
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = True
 | 
			
		||||
 | 
			
		||||
    def get(self, request: HttpRequest, id):
 | 
			
		||||
        """ Performs the GET request handling
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            users = self._get_shared_users_of_object(id)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return self.return_error_response(e)
 | 
			
		||||
 | 
			
		||||
        data = {
 | 
			
		||||
            "users": [
 | 
			
		||||
                user.username for user in users
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return JsonResponse(data)
 | 
			
		||||
 | 
			
		||||
    def put(self, request: HttpRequest, id):
 | 
			
		||||
        """ Performs the PUT request handling
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            request (HttpRequest): The incoming request
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            success = self._process_put_body(request.body, id)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            return self.return_error_response(e)
 | 
			
		||||
        data = {
 | 
			
		||||
            "success": success,
 | 
			
		||||
        }
 | 
			
		||||
        return JsonResponse(data)
 | 
			
		||||
 | 
			
		||||
    def _check_user_has_shared_access(self, obj):
 | 
			
		||||
        """ Raises a PermissionError if user has no shared access
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            obj (BaseObject): The object
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        is_shared = obj.is_shared_with(self.user)
 | 
			
		||||
        if not is_shared:
 | 
			
		||||
            raise PermissionError(DATA_UNSHARED)
 | 
			
		||||
 | 
			
		||||
    def _get_shared_users_of_object(self, id) -> QuerySet:
 | 
			
		||||
        """ Check permissions and get the users
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            users (QuerySet)
 | 
			
		||||
        """
 | 
			
		||||
        obj = self.model.objects.get(
 | 
			
		||||
            id=id
 | 
			
		||||
        )
 | 
			
		||||
        self._check_user_has_shared_access(obj)
 | 
			
		||||
        users = obj.shared_users
 | 
			
		||||
        return users
 | 
			
		||||
 | 
			
		||||
    def _process_put_body(self, body: bytes, id: str):
 | 
			
		||||
        """ Reads the body data, performs validity checks and sets the new users
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            body (bytes): The request.body
 | 
			
		||||
            id (str): The object's id
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            success (bool)
 | 
			
		||||
        """
 | 
			
		||||
        obj = self.model.objects.get(id=id)
 | 
			
		||||
        self._check_user_has_shared_access(obj)
 | 
			
		||||
 | 
			
		||||
        new_users = json.loads(body.decode("utf-8"))
 | 
			
		||||
        new_users = new_users.get("users", [])
 | 
			
		||||
        if len(new_users) == 0:
 | 
			
		||||
            raise ValueError("Shared user list must not be empty!")
 | 
			
		||||
 | 
			
		||||
        # Eliminate duplicates
 | 
			
		||||
        new_users = list(dict.fromkeys(new_users))
 | 
			
		||||
 | 
			
		||||
        # Make sure each of these names exist as a user
 | 
			
		||||
        new_users_objs = []
 | 
			
		||||
        for user in new_users:
 | 
			
		||||
            new_users_objs.append(User.objects.get(username=user))
 | 
			
		||||
 | 
			
		||||
        if is_default_group_only(self.user):
 | 
			
		||||
            # Default only users are not allowed to remove other users from having access. They can only add new ones!
 | 
			
		||||
            new_users_to_be_added = User.objects.filter(
 | 
			
		||||
                username__in=new_users
 | 
			
		||||
            ).exclude(
 | 
			
		||||
                id__in=obj.shared_users
 | 
			
		||||
            )
 | 
			
		||||
            new_users_objs = obj.shared_users.union(new_users_to_be_added)
 | 
			
		||||
        obj.share_with_list(new_users_objs)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InterventionAPIShareView(AbstractModelShareAPIView):
 | 
			
		||||
    model = Intervention
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EcoAccountAPIShareView(AbstractModelShareAPIView):
 | 
			
		||||
    model = EcoAccount
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmaAPIShareView(AbstractModelShareAPIView):
 | 
			
		||||
    model = Ema
 | 
			
		||||
@ -29,6 +29,7 @@ class CompensationAdmin(BaseObjectAdmin):
 | 
			
		||||
        "identifier",
 | 
			
		||||
        "title",
 | 
			
		||||
        "created",
 | 
			
		||||
        "deleted",
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,9 +35,7 @@ class CompensationManager(models.Manager):
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return super().get_queryset().filter(
 | 
			
		||||
            deleted__isnull=True,
 | 
			
		||||
        ).select_related(
 | 
			
		||||
        return super().get_queryset().select_related(
 | 
			
		||||
            "modified",
 | 
			
		||||
            "intervention",
 | 
			
		||||
            "intervention__recorded",
 | 
			
		||||
 | 
			
		||||
@ -245,6 +245,38 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
 | 
			
		||||
        # Compensations inherit their shared state from the interventions
 | 
			
		||||
        return self.intervention.is_shared_with(user)
 | 
			
		||||
 | 
			
		||||
    def share_with(self, user: User):
 | 
			
		||||
        """ Adds user to list of shared access users
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            user (User): The user to be added to the object
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        if not self.intervention.is_shared_with(user):
 | 
			
		||||
            self.intervention.users.add(user)
 | 
			
		||||
 | 
			
		||||
    def share_with_list(self, user_list: list):
 | 
			
		||||
        """ Sets the list of shared access users
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            user_list (list): The users to be added to the object
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self.intervention.users.set(user_list)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def shared_users(self) -> QuerySet:
 | 
			
		||||
        """ Shortcut for fetching the users which have shared access on this object
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            users (QuerySet)
 | 
			
		||||
        """
 | 
			
		||||
        return self.intervention.users.all()
 | 
			
		||||
 | 
			
		||||
    def get_LANIS_link(self) -> str:
 | 
			
		||||
        """ Generates a link for LANIS depending on the geometry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -108,7 +108,7 @@ def new_id_view(request: HttpRequest):
 | 
			
		||||
        identifier = tmp.generate_new_identifier()
 | 
			
		||||
    return JsonResponse(
 | 
			
		||||
        data={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
            "gen_data": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -118,7 +118,7 @@ def new_id_view(request: HttpRequest):
 | 
			
		||||
        identifier = tmp.generate_new_identifier()
 | 
			
		||||
    return JsonResponse(
 | 
			
		||||
        data={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
            "gen_data": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -108,7 +108,7 @@ def new_id_view(request: HttpRequest):
 | 
			
		||||
        identifier = tmp.generate_new_identifier()
 | 
			
		||||
    return JsonResponse(
 | 
			
		||||
        data={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
            "gen_data": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -116,7 +116,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
                    z_l = v_zoom
 | 
			
		||||
                    break
 | 
			
		||||
            zoom_lvl = z_l
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
        except (AttributeError, IndexError) as e:
 | 
			
		||||
            # If no geometry has been added, yet.
 | 
			
		||||
            x = 1
 | 
			
		||||
            y = 1
 | 
			
		||||
@ -154,6 +154,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
    def set_unrecorded(self, user: User):
 | 
			
		||||
        log_entry = super().set_unrecorded(user)
 | 
			
		||||
        self.add_log_entry_to_compensations(log_entry)
 | 
			
		||||
        return log_entry
 | 
			
		||||
 | 
			
		||||
    def set_recorded(self, user: User) -> UserActionLogEntry:
 | 
			
		||||
        log_entry = super().set_recorded(user)
 | 
			
		||||
@ -259,11 +260,6 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        user_action_edit = UserActionLogEntry.get_edited_action(performing_user, comment=edit_comment)
 | 
			
		||||
        self.log.add(user_action_edit)
 | 
			
		||||
        self.modified = user_action_edit
 | 
			
		||||
        self.save()
 | 
			
		||||
 | 
			
		||||
        super().mark_as_edited(performing_user, request)
 | 
			
		||||
        if self.checked:
 | 
			
		||||
            self.set_unchecked()
 | 
			
		||||
 | 
			
		||||
@ -360,19 +360,21 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
 | 
			
		||||
            self.eco_account.recorded = rec_action
 | 
			
		||||
        self.eco_account.share_with_list([self.superuser])
 | 
			
		||||
        self.eco_account.save()
 | 
			
		||||
        num_all_deducs = EcoAccountDeduction.objects.count()
 | 
			
		||||
 | 
			
		||||
        # Run the request
 | 
			
		||||
        self.client_user.post(new_url, post_data)
 | 
			
		||||
 | 
			
		||||
        # Expect the deduction to be created, since all constraints are fulfilled
 | 
			
		||||
        self.assertEqual(1, self.eco_account.deductions.count())
 | 
			
		||||
        self.assertEqual(1, EcoAccountDeduction.objects.count())
 | 
			
		||||
        self.assertEqual(num_all_deducs + 1, EcoAccountDeduction.objects.count())
 | 
			
		||||
 | 
			
		||||
        # Make sure the deduction contains the expected data
 | 
			
		||||
        deduction = EcoAccountDeduction.objects.first()
 | 
			
		||||
        deduction = EcoAccountDeduction.objects.get(
 | 
			
		||||
            account=self.eco_account,
 | 
			
		||||
            intervention=self.intervention
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(deduction.surface, test_surface)
 | 
			
		||||
        self.assertEqual(deduction.intervention, self.intervention)
 | 
			
		||||
        self.assertEqual(deduction.account, self.eco_account)
 | 
			
		||||
 | 
			
		||||
        # Return deduction for further usage in tests
 | 
			
		||||
        return deduction
 | 
			
		||||
 | 
			
		||||
@ -108,7 +108,7 @@ def new_id_view(request: HttpRequest):
 | 
			
		||||
        identifier = tmp_intervention.generate_new_identifier()
 | 
			
		||||
    return JsonResponse(
 | 
			
		||||
        data={
 | 
			
		||||
            "identifier": identifier
 | 
			
		||||
            "gen_data": identifier
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -75,9 +75,7 @@ def default_group_required(function):
 | 
			
		||||
    @wraps(function)
 | 
			
		||||
    def wrap(request, *args, **kwargs):
 | 
			
		||||
        user = request.user
 | 
			
		||||
        has_group = user.groups.filter(
 | 
			
		||||
            name=DEFAULT_GROUP
 | 
			
		||||
        ).exists()
 | 
			
		||||
        has_group = user.is_default_user()
 | 
			
		||||
        if has_group:
 | 
			
		||||
            return function(request, *args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
@ -95,9 +93,7 @@ def registration_office_group_required(function):
 | 
			
		||||
    @wraps(function)
 | 
			
		||||
    def wrap(request, *args, **kwargs):
 | 
			
		||||
        user = request.user
 | 
			
		||||
        has_group = user.groups.filter(
 | 
			
		||||
            name=ZB_GROUP
 | 
			
		||||
        ).exists()
 | 
			
		||||
        has_group = user.is_zb_user()
 | 
			
		||||
        if has_group:
 | 
			
		||||
            return function(request, *args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
@ -115,9 +111,7 @@ def conservation_office_group_required(function):
 | 
			
		||||
    @wraps(function)
 | 
			
		||||
    def wrap(request, *args, **kwargs):
 | 
			
		||||
        user = request.user
 | 
			
		||||
        has_group = user.groups.filter(
 | 
			
		||||
            name=ETS_GROUP
 | 
			
		||||
        ).exists()
 | 
			
		||||
        has_group = user.is_ets_user()
 | 
			
		||||
        if has_group:
 | 
			
		||||
            return function(request, *args, **kwargs)
 | 
			
		||||
        else:
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ from ema.models import Ema
 | 
			
		||||
from intervention.models import Intervention
 | 
			
		||||
from konova.management.commands.setup import BaseKonovaCommand
 | 
			
		||||
from konova.models import Deadline, Geometry, Parcel, District
 | 
			
		||||
from user.models import UserActionLogEntry
 | 
			
		||||
from user.models import UserActionLogEntry, UserAction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command(BaseKonovaCommand):
 | 
			
		||||
@ -55,7 +55,11 @@ class Command(BaseKonovaCommand):
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        self._write_warning("=== Sanitize log entries ===")
 | 
			
		||||
        all_log_entries = UserActionLogEntry.objects.all()
 | 
			
		||||
        # Exclude created log entries from being cleaned, since they can be part of objects which do not have logs
 | 
			
		||||
        # Being in a log (or not) is essential for this cleanup
 | 
			
		||||
        all_log_entries = UserActionLogEntry.objects.all().exclude(
 | 
			
		||||
            action=UserAction.CREATED
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        intervention_log_entries_ids = self.get_all_log_entries_ids(Intervention)
 | 
			
		||||
        attached_log_entries_id = intervention_log_entries_ids.union(
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import uuid
 | 
			
		||||
from abc import abstractmethod
 | 
			
		||||
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
from django.db.models import QuerySet
 | 
			
		||||
 | 
			
		||||
from konova.tasks import celery_send_mail_shared_access_removed, celery_send_mail_shared_access_given, \
 | 
			
		||||
    celery_send_mail_shared_data_recorded, celery_send_mail_shared_data_unrecorded, \
 | 
			
		||||
@ -103,7 +104,7 @@ class BaseObject(BaseResource):
 | 
			
		||||
    def set_status_messages(self, request: HttpRequest):
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def mark_as_deleted(self, user: User):
 | 
			
		||||
    def mark_as_deleted(self, user: User, send_mail: bool = True):
 | 
			
		||||
        """ Mark an entry as deleted
 | 
			
		||||
 | 
			
		||||
        Does not delete from database but sets a timestamp for being deleted on and which user deleted the object
 | 
			
		||||
@ -123,10 +124,11 @@ class BaseObject(BaseResource):
 | 
			
		||||
            self.deleted = action
 | 
			
		||||
            self.log.add(action)
 | 
			
		||||
 | 
			
		||||
            # Send mail
 | 
			
		||||
            shared_users = self.users.all().values_list("id", flat=True)
 | 
			
		||||
            for user_id in shared_users:
 | 
			
		||||
                celery_send_mail_shared_data_deleted.delay(self.identifier, user_id)
 | 
			
		||||
            if send_mail:
 | 
			
		||||
                # Send mail
 | 
			
		||||
                shared_users = self.shared_users.values_list("id", flat=True)
 | 
			
		||||
                for user_id in shared_users:
 | 
			
		||||
                    celery_send_mail_shared_data_deleted.delay(self.identifier, user_id)
 | 
			
		||||
 | 
			
		||||
            self.save()
 | 
			
		||||
 | 
			
		||||
@ -276,7 +278,8 @@ class RecordableObjectMixin(models.Model):
 | 
			
		||||
        self.save()
 | 
			
		||||
 | 
			
		||||
        if self.recorded:
 | 
			
		||||
            self.set_unrecorded(performing_user)
 | 
			
		||||
            action = self.set_unrecorded(performing_user)
 | 
			
		||||
            self.log.add(action)
 | 
			
		||||
            if request:
 | 
			
		||||
                messages.info(
 | 
			
		||||
                    request,
 | 
			
		||||
@ -464,6 +467,15 @@ class ShareableObjectMixin(models.Model):
 | 
			
		||||
        # Set new shared users
 | 
			
		||||
        self.share_with_list(users)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def shared_users(self) -> QuerySet:
 | 
			
		||||
        """ Shortcut for fetching the users which have shared access on this object
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            users (QuerySet)
 | 
			
		||||
        """
 | 
			
		||||
        return self.users.all()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GeoReferencedMixin(models.Model):
 | 
			
		||||
    geometry = models.ForeignKey("konova.Geometry", null=True, blank=True, on_delete=models.SET_NULL)
 | 
			
		||||
 | 
			
		||||
@ -70,6 +70,7 @@ INSTALLED_APPS = [
 | 
			
		||||
    'ema',
 | 
			
		||||
    'codelist',
 | 
			
		||||
    'analysis',
 | 
			
		||||
    'api',
 | 
			
		||||
]
 | 
			
		||||
if DEBUG:
 | 
			
		||||
    INSTALLED_APPS += [
 | 
			
		||||
@ -213,6 +214,7 @@ if DEBUG:
 | 
			
		||||
DEFAULT_FROM_EMAIL = "no-reply@ksp.de"  # The default email address for the 'from' element
 | 
			
		||||
EMAIL_HOST = os.environ.get('SMTP_HOST'),
 | 
			
		||||
EMAIL_REPLY_TO = os.environ.get('SMTP_REAL_REPLY_MAIL')
 | 
			
		||||
SUPPORT_MAIL_RECIPIENT = EMAIL_REPLY_TO
 | 
			
		||||
EMAIL_PORT = os.environ.get('SMTP_PORT'),
 | 
			
		||||
 | 
			
		||||
# LOGGING
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
<div class="input-group w-100" title="{{ widget.value|stringformat:'s' }}">
 | 
			
		||||
    <input id="gen-id-input" aria-describedby="gen-id-btn" type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
 | 
			
		||||
    <input id="gen-data-input" aria-describedby="gen-data-btn" type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}>
 | 
			
		||||
    <div class="input-group-append" onclick="fetchNewIdentifier()">
 | 
			
		||||
        <span id="gen-id-btn" class="btn btn-default" value="{% trans 'Generate new' %}" title="{% trans 'Generate new' %}">{% fa5_icon 'dice' %}</span>
 | 
			
		||||
        <span id="gen-data-btn" class="btn btn-default" value="{% trans 'Generate new' %}" title="{% trans 'Generate new' %}">{% fa5_icon 'dice' %}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<script>
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
                return response.json();
 | 
			
		||||
            })
 | 
			
		||||
            .then(function(data){
 | 
			
		||||
                document.getElementById("gen-id-input").value = data["identifier"];
 | 
			
		||||
                document.getElementById("gen-data-input").value = data["gen_data"];
 | 
			
		||||
            })
 | 
			
		||||
            .catch(function(error){
 | 
			
		||||
                console.log(error);
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ Created on: 26.10.21
 | 
			
		||||
"""
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
from ema.models import Ema
 | 
			
		||||
from user.models import User
 | 
			
		||||
from django.contrib.auth.models import Group
 | 
			
		||||
from django.contrib.gis.geos import MultiPolygon, Polygon
 | 
			
		||||
@ -15,7 +16,7 @@ from django.test import TestCase, Client
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from codelist.models import KonovaCode
 | 
			
		||||
from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount
 | 
			
		||||
from compensation.models import Compensation, CompensationState, CompensationAction, EcoAccount, EcoAccountDeduction
 | 
			
		||||
from intervention.models import Legal, Responsibility, Intervention
 | 
			
		||||
from konova.management.commands.setup_data import GROUPS_DATA
 | 
			
		||||
from konova.models import Geometry
 | 
			
		||||
@ -52,6 +53,8 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        cls.intervention = cls.create_dummy_intervention()
 | 
			
		||||
        cls.compensation = cls.create_dummy_compensation()
 | 
			
		||||
        cls.eco_account = cls.create_dummy_eco_account()
 | 
			
		||||
        cls.ema = cls.create_dummy_ema()
 | 
			
		||||
        cls.deduction = cls.create_dummy_deduction()
 | 
			
		||||
        cls.create_dummy_states()
 | 
			
		||||
        cls.create_dummy_action()
 | 
			
		||||
        cls.codes = cls.create_dummy_codes()
 | 
			
		||||
@ -168,6 +171,38 @@ class BaseTestCase(TestCase):
 | 
			
		||||
        )
 | 
			
		||||
        return eco_account
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_ema(cls):
 | 
			
		||||
        """ Creates an ema which can be used for tests
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        # Create dummy data
 | 
			
		||||
        # Create log entry
 | 
			
		||||
        action = UserActionLogEntry.get_created_action(cls.superuser)
 | 
			
		||||
        geometry = Geometry.objects.create()
 | 
			
		||||
        # Create responsible data object
 | 
			
		||||
        responsible_data = Responsibility.objects.create()
 | 
			
		||||
        # Finally create main object, holding the other objects
 | 
			
		||||
        ema = Ema.objects.create(
 | 
			
		||||
            identifier="TEST",
 | 
			
		||||
            title="Test_title",
 | 
			
		||||
            responsible=responsible_data,
 | 
			
		||||
            created=action,
 | 
			
		||||
            geometry=geometry,
 | 
			
		||||
            comment="Test",
 | 
			
		||||
        )
 | 
			
		||||
        return ema
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_deduction(cls):
 | 
			
		||||
        return EcoAccountDeduction.objects.create(
 | 
			
		||||
            account=cls.create_dummy_eco_account(),
 | 
			
		||||
            intervention=cls.create_dummy_intervention(),
 | 
			
		||||
            surface=100,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create_dummy_states(cls):
 | 
			
		||||
        """ Creates an intervention which can be used for tests
 | 
			
		||||
 | 
			
		||||
@ -38,6 +38,7 @@ urlpatterns = [
 | 
			
		||||
    path('news/', include("news.urls")),
 | 
			
		||||
    path('cl/', include("codelist.urls")),
 | 
			
		||||
    path('analysis/', include("analysis.urls")),
 | 
			
		||||
    path('api/', include("api.urls")),
 | 
			
		||||
 | 
			
		||||
    # Generic deadline routes
 | 
			
		||||
    path('deadline/<id>/remove', remove_deadline_view, name="deadline-remove"),
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,19 @@ import qrcode.image.svg
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_token() -> str:
 | 
			
		||||
    """ Shortcut for default generating of e.g. API token
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        token (str)
 | 
			
		||||
    """
 | 
			
		||||
    return generate_random_string(
 | 
			
		||||
        length=64,
 | 
			
		||||
        use_numbers=True,
 | 
			
		||||
        use_letters_lc=True
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_random_string(length: int, use_numbers: bool = False, use_letters_lc: bool = False, use_letters_uc: bool = False) -> str:
 | 
			
		||||
    """
 | 
			
		||||
    Generates a random string of variable length
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ from django.core.mail import send_mail
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL, EMAIL_REPLY_TO
 | 
			
		||||
from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL, EMAIL_REPLY_TO, SUPPORT_MAIL_RECIPIENT
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@ -181,3 +181,24 @@ class Mailer:
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def send_mail_verify_api_token(self, user):
 | 
			
		||||
        """ Send a mail if a user creates a new token
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            user (User): The user, having a new api token
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        context = {
 | 
			
		||||
            "user": user,
 | 
			
		||||
            "EMAIL_REPLY_TO": EMAIL_REPLY_TO,
 | 
			
		||||
        }
 | 
			
		||||
        msg = render_to_string("email/api/verify_token.html", context)
 | 
			
		||||
        user_mail_address = [SUPPORT_MAIL_RECIPIENT]
 | 
			
		||||
        self.send(
 | 
			
		||||
            user_mail_address,
 | 
			
		||||
            _("Request for new API token"),
 | 
			
		||||
            msg
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@ -20,13 +20,13 @@
 | 
			
		||||
#: konova/filters/mixins.py:385 konova/filters/mixins.py:386
 | 
			
		||||
#: konova/forms.py:140 konova/forms.py:241 konova/forms.py:312
 | 
			
		||||
#: konova/forms.py:339 konova/forms.py:349 konova/forms.py:362
 | 
			
		||||
#: konova/forms.py:374 konova/forms.py:392 user/forms.py:38
 | 
			
		||||
#: konova/forms.py:374 konova/forms.py:392 user/forms.py:42
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2022-01-20 12:30+0100\n"
 | 
			
		||||
"POT-Creation-Date: 2022-01-28 16:27+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"
 | 
			
		||||
@ -317,6 +317,7 @@ msgid "Identifier"
 | 
			
		||||
msgstr "Kennung"
 | 
			
		||||
 | 
			
		||||
#: compensation/forms/forms.py:35 intervention/forms/forms.py:29
 | 
			
		||||
#: user/forms.py:126
 | 
			
		||||
msgid "Generated automatically"
 | 
			
		||||
msgstr "Automatisch generiert"
 | 
			
		||||
 | 
			
		||||
@ -1716,11 +1717,11 @@ msgstr "Kontrolle am"
 | 
			
		||||
msgid "Other"
 | 
			
		||||
msgstr "Sonstige"
 | 
			
		||||
 | 
			
		||||
#: konova/sub_settings/django_settings.py:154
 | 
			
		||||
#: konova/sub_settings/django_settings.py:155
 | 
			
		||||
msgid "German"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: konova/sub_settings/django_settings.py:155
 | 
			
		||||
#: konova/sub_settings/django_settings.py:156
 | 
			
		||||
msgid "English"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@ -1803,6 +1804,10 @@ msgstr "{} - Freigegebene Daten gelöscht"
 | 
			
		||||
msgid "{} - Shared data checked"
 | 
			
		||||
msgstr "{} - Freigegebene Daten geprüft"
 | 
			
		||||
 | 
			
		||||
#: konova/utils/mailer.py:201 templates/email/api/verify_token.html:4
 | 
			
		||||
msgid "Request for new API token"
 | 
			
		||||
msgstr "Anfrage für neuen API Token"
 | 
			
		||||
 | 
			
		||||
#: konova/utils/message_templates.py:11
 | 
			
		||||
msgid "There was an error on this form."
 | 
			
		||||
msgstr "Es gab einen Fehler im Formular."
 | 
			
		||||
@ -1937,6 +1942,33 @@ msgstr ""
 | 
			
		||||
msgid "Something happened. We are working on it!"
 | 
			
		||||
msgstr "Irgendetwas ist passiert. Wir arbeiten daran!"
 | 
			
		||||
 | 
			
		||||
#: templates/email/api/verify_token.html:7
 | 
			
		||||
msgid "Hello support"
 | 
			
		||||
msgstr "Hallo Support"
 | 
			
		||||
 | 
			
		||||
#: templates/email/api/verify_token.html:9
 | 
			
		||||
msgid "you need to verify the API token for user"
 | 
			
		||||
msgstr "Sie müssen einen API Token für folgenden Nutzer freischalten"
 | 
			
		||||
 | 
			
		||||
#: templates/email/api/verify_token.html:15
 | 
			
		||||
msgid ""
 | 
			
		||||
"If unsure, please contact the user. The API token can not be used until you "
 | 
			
		||||
"activated it in the admin backend."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Falls Sie sich unsicher sind, kontaktieren Sie den Nutzer vorher. Der API "
 | 
			
		||||
"Token kann so lange nicht verwendet werden, wie er noch nicht von Ihnen im "
 | 
			
		||||
"Admin Backend aktiviert worden ist."
 | 
			
		||||
 | 
			
		||||
#: templates/email/api/verify_token.html:18
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:17
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:17
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:17
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:18
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:18
 | 
			
		||||
msgid "Best regards"
 | 
			
		||||
msgstr "Beste Grüße"
 | 
			
		||||
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:4
 | 
			
		||||
msgid "Shared data checked"
 | 
			
		||||
msgstr "Freigegebene Daten geprüft"
 | 
			
		||||
@ -1962,15 +1994,6 @@ msgstr ""
 | 
			
		||||
"Das bedeutet, dass die zuständige Zulassungsbehörde die Korrektheit des "
 | 
			
		||||
"Datensatzes soeben bestätigt hat."
 | 
			
		||||
 | 
			
		||||
#: templates/email/checking/shared_data_checked.html:17
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:17
 | 
			
		||||
#: templates/email/recording/shared_data_recorded.html:17
 | 
			
		||||
#: templates/email/recording/shared_data_unrecorded.html:17
 | 
			
		||||
#: templates/email/sharing/shared_access_given.html:18
 | 
			
		||||
#: templates/email/sharing/shared_access_removed.html:18
 | 
			
		||||
msgid "Best regards"
 | 
			
		||||
msgstr "Beste Grüße"
 | 
			
		||||
 | 
			
		||||
#: templates/email/deleting/shared_data_deleted.html:4
 | 
			
		||||
msgid "Shared data deleted"
 | 
			
		||||
msgstr "Freigegebene Daten gelöscht"
 | 
			
		||||
@ -2214,39 +2237,51 @@ msgid ""
 | 
			
		||||
"            "
 | 
			
		||||
msgstr ""
 | 
			
		||||
"\n"
 | 
			
		||||
"            Diese Daten sind noch nicht veröffentlicht und "
 | 
			
		||||
"können daher aktuell nicht eingesehen werden. Schauen Sie zu einem späteren "
 | 
			
		||||
"Zeitpunkt wieder vorbei. \n"
 | 
			
		||||
"            Diese Daten sind noch nicht veröffentlicht und können daher "
 | 
			
		||||
"aktuell nicht eingesehen werden. Schauen Sie zu einem späteren Zeitpunkt "
 | 
			
		||||
"wieder vorbei. \n"
 | 
			
		||||
"            "
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:23
 | 
			
		||||
#: user/forms.py:27
 | 
			
		||||
msgid "Notifications"
 | 
			
		||||
msgstr "Benachrichtigungen"
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:25
 | 
			
		||||
#: user/forms.py:29
 | 
			
		||||
msgid "Select the situations when you want to receive a notification"
 | 
			
		||||
msgstr "Wann wollen Sie per E-Mail benachrichtigt werden?"
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:37
 | 
			
		||||
#: user/forms.py:41
 | 
			
		||||
msgid "Edit notifications"
 | 
			
		||||
msgstr "Benachrichtigungen bearbeiten"
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:72 user/templates/user/index.html:9
 | 
			
		||||
#: user/forms.py:76 user/templates/user/index.html:9
 | 
			
		||||
msgid "Username"
 | 
			
		||||
msgstr "Nutzername"
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:83
 | 
			
		||||
#: user/forms.py:87
 | 
			
		||||
msgid "Person name"
 | 
			
		||||
msgstr "Name"
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:94 user/templates/user/index.html:17
 | 
			
		||||
#: user/forms.py:98 user/templates/user/index.html:17
 | 
			
		||||
msgid "E-Mail"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:108
 | 
			
		||||
#: user/forms.py:112
 | 
			
		||||
msgid "User contact data"
 | 
			
		||||
msgstr "Kontaktdaten"
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:122
 | 
			
		||||
msgid "Token"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:137
 | 
			
		||||
msgid "Create new token"
 | 
			
		||||
msgstr "Neuen Token generieren"
 | 
			
		||||
 | 
			
		||||
#: user/forms.py:138
 | 
			
		||||
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:20
 | 
			
		||||
msgid "Unrecorded"
 | 
			
		||||
msgstr "Entzeichnet"
 | 
			
		||||
@ -2300,18 +2335,58 @@ msgstr "Benachrichtigungseinstellungen ändern"
 | 
			
		||||
msgid "Notification settings"
 | 
			
		||||
msgstr "Benachrichtigungen"
 | 
			
		||||
 | 
			
		||||
#: user/views.py:29
 | 
			
		||||
#: user/templates/user/index.html:58
 | 
			
		||||
msgid "See or edit your API token"
 | 
			
		||||
msgstr "API token einsehen oder neu generieren"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/index.html:61
 | 
			
		||||
msgid "API"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:6
 | 
			
		||||
msgid "API settings"
 | 
			
		||||
msgstr "API Einstellungen"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:10
 | 
			
		||||
msgid "Current token"
 | 
			
		||||
msgstr "Aktueller Token"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:14
 | 
			
		||||
msgid "Authenticated by admins"
 | 
			
		||||
msgstr "Von Admin freigeschaltet"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:18
 | 
			
		||||
msgid "Token has been verified and can be used"
 | 
			
		||||
msgstr "Token wurde freigeschaltet und kann verwendet werden"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:20
 | 
			
		||||
msgid "Token waiting for verification"
 | 
			
		||||
msgstr "Token noch nicht freigeschaltet"
 | 
			
		||||
 | 
			
		||||
#: user/templates/user/token.html:24
 | 
			
		||||
msgid "Valid until"
 | 
			
		||||
msgstr "Läuft ab am"
 | 
			
		||||
 | 
			
		||||
#: user/views.py:31
 | 
			
		||||
msgid "User settings"
 | 
			
		||||
msgstr "Einstellungen"
 | 
			
		||||
 | 
			
		||||
#: user/views.py:55
 | 
			
		||||
#: user/views.py:57
 | 
			
		||||
msgid "Notifications edited"
 | 
			
		||||
msgstr "Benachrichtigungen bearbeitet"
 | 
			
		||||
 | 
			
		||||
#: user/views.py:67
 | 
			
		||||
#: user/views.py:69
 | 
			
		||||
msgid "User notifications"
 | 
			
		||||
msgstr "Benachrichtigungen"
 | 
			
		||||
 | 
			
		||||
#: user/views.py:92
 | 
			
		||||
msgid "New token generated. Administrators need to validate."
 | 
			
		||||
msgstr "Neuer Token generiert. Administratoren sind informiert."
 | 
			
		||||
 | 
			
		||||
#: user/views.py:103
 | 
			
		||||
msgid "User API token"
 | 
			
		||||
msgstr "API Nutzer Token"
 | 
			
		||||
 | 
			
		||||
#: venv/lib/python3.7/site-packages/bootstrap4/components.py:17
 | 
			
		||||
#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/form_errors.html:3
 | 
			
		||||
#: venv/lib/python3.7/site-packages/bootstrap4/templates/bootstrap4/messages.html:4
 | 
			
		||||
@ -3056,7 +3131,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: venv/lib/python3.7/site-packages/django/forms/fields.py:54
 | 
			
		||||
msgid "This field is required."
 | 
			
		||||
msgstr ""
 | 
			
		||||
msgstr "Pflichtfeld"
 | 
			
		||||
 | 
			
		||||
#: venv/lib/python3.7/site-packages/django/forms/fields.py:247
 | 
			
		||||
msgid "Enter a whole number."
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								templates/email/api/verify_token.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								templates/email/api/verify_token.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
    <h2>{% trans 'Request for new API token' %}</h2>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <article>
 | 
			
		||||
        {% trans 'Hello support' %},
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'you need to verify the API token for user' %}:
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
        <strong>{{user.username}}</strong>
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'If unsure, please contact the user. The API token can not be used until you activated it in the admin backend.' %}
 | 
			
		||||
        <br>
 | 
			
		||||
        <br>
 | 
			
		||||
        {% trans 'Best regards' %}
 | 
			
		||||
        <br>
 | 
			
		||||
        KSP
 | 
			
		||||
    </article>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,12 @@ Created on: 08.07.21
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.db import IntegrityError
 | 
			
		||||
from django.urls import reverse, reverse_lazy
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from api.models import APIUserToken
 | 
			
		||||
from intervention.inputs import GenerateInput
 | 
			
		||||
from user.models import User
 | 
			
		||||
 | 
			
		||||
from konova.forms import BaseForm, BaseModalForm
 | 
			
		||||
@ -113,3 +117,46 @@ class UserContactForm(BaseModalForm):
 | 
			
		||||
        self.initialize_form_field("mail", self.instance.email)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserAPITokenForm(BaseForm):
 | 
			
		||||
    token = forms.CharField(
 | 
			
		||||
        label=_("Token"),
 | 
			
		||||
        label_suffix="",
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        required=True,
 | 
			
		||||
        help_text=_("Generated automatically"),
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
@ -9,18 +9,57 @@ from django.contrib.auth.models import AbstractUser
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
 | 
			
		||||
from api.models import APIUserToken
 | 
			
		||||
from konova.settings import ZB_GROUP, DEFAULT_GROUP, ETS_GROUP
 | 
			
		||||
from konova.utils.mailer import Mailer
 | 
			
		||||
from user.enums import UserNotificationEnum
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class User(AbstractUser):
 | 
			
		||||
    notifications = models.ManyToManyField("user.UserNotification", related_name="+", blank=True)
 | 
			
		||||
    api_token = models.OneToOneField(
 | 
			
		||||
        "api.APIUserToken",
 | 
			
		||||
        blank=True,
 | 
			
		||||
        null=True,
 | 
			
		||||
        help_text="The user's API token",
 | 
			
		||||
        on_delete=models.SET_NULL
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def is_notification_setting_set(self, notification_enum: UserNotificationEnum):
 | 
			
		||||
        return self.notifications.filter(
 | 
			
		||||
            id=notification_enum.value
 | 
			
		||||
        ).exists()
 | 
			
		||||
 | 
			
		||||
    def is_zb_user(self):
 | 
			
		||||
        """ Shortcut for checking whether a user is of a special group or not
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bool
 | 
			
		||||
        """
 | 
			
		||||
        return self.groups.filter(
 | 
			
		||||
            name=ZB_GROUP
 | 
			
		||||
        ).exists()
 | 
			
		||||
 | 
			
		||||
    def is_default_user(self):
 | 
			
		||||
        """ Shortcut for checking whether a user is of a special group or not
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bool
 | 
			
		||||
        """
 | 
			
		||||
        return self.groups.filter(
 | 
			
		||||
            name=DEFAULT_GROUP
 | 
			
		||||
        ).exists()
 | 
			
		||||
 | 
			
		||||
    def is_ets_user(self):
 | 
			
		||||
        """ Shortcut for checking whether a user is of a special group or not
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            bool
 | 
			
		||||
        """
 | 
			
		||||
        return self.groups.filter(
 | 
			
		||||
            name=ETS_GROUP
 | 
			
		||||
        ).exists()
 | 
			
		||||
 | 
			
		||||
    def send_mail_shared_access_removed(self, obj_identifier):
 | 
			
		||||
        """ Sends a mail to the user in case of removed shared access
 | 
			
		||||
 | 
			
		||||
@ -104,3 +143,19 @@ class User(AbstractUser):
 | 
			
		||||
        if notification_set:
 | 
			
		||||
            mailer = Mailer()
 | 
			
		||||
            mailer.send_mail_shared_data_checked(obj_identifier, self)
 | 
			
		||||
 | 
			
		||||
    def get_API_token(self):
 | 
			
		||||
        """ Getter for an API token
 | 
			
		||||
 | 
			
		||||
        Creates a new one if none exists, yet.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            token (APIUserToken)
 | 
			
		||||
        """
 | 
			
		||||
        if self.api_token is None:
 | 
			
		||||
            token = APIUserToken.objects.create()
 | 
			
		||||
            self.api_token = token
 | 
			
		||||
            self.save()
 | 
			
		||||
        else:
 | 
			
		||||
            token = self.api_token
 | 
			
		||||
        return token
 | 
			
		||||
 | 
			
		||||
@ -54,6 +54,14 @@
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row mb-2">
 | 
			
		||||
                    <a href="{% url 'user:api-token' %}" title="{% trans 'See or edit your API token' %}">
 | 
			
		||||
                        <button class="btn btn-default">
 | 
			
		||||
                            {% fa5_icon 'code' %}
 | 
			
		||||
                            <span>{% trans 'API' %}</span>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								user/templates/user/token.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								user/templates/user/token.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
{% load i18n fontawesome_5 %}
 | 
			
		||||
 | 
			
		||||
{% block body %}
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <h3>{% trans 'API settings' %}</h3>
 | 
			
		||||
        <div class="table-container">
 | 
			
		||||
            <table class="table table-hover">
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th scope="row">{% trans 'Current token' %}</th>
 | 
			
		||||
                    <td>{{ user.api_token.token }}</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th scope="row">{% trans 'Authenticated by admins' %}</th>
 | 
			
		||||
                    {% if user.api_token is None %}
 | 
			
		||||
                        <td></td>
 | 
			
		||||
                    {% elif user.api_token.is_active %}
 | 
			
		||||
                        <td class="text-success" title="{% trans 'Token has been verified and can be used' %}">{% fa5_icon 'check-circle' %}</td>
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                        <td class="text-primary" title="{% trans 'Token waiting for verification' %}">{% fa5_icon 'hourglass-half' %}</td>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th scope="row">{% trans 'Valid until' %}</th>
 | 
			
		||||
                    <td>{{ user.api_token.valid_until|default_if_none:"-" }}</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </table>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
    {% include 'form/table/generic_table_form.html' %}
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@ -13,6 +13,7 @@ app_name = "user"
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("", index_view, name="index"),
 | 
			
		||||
    path("notifications/", notifications_view, name="notifications"),
 | 
			
		||||
    path("token/api", api_token_view, name="api-token"),
 | 
			
		||||
    path("contact/<id>", contact_view, name="contact"),
 | 
			
		||||
 | 
			
		||||
]
 | 
			
		||||
@ -2,14 +2,16 @@ from django.contrib import messages
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
 | 
			
		||||
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.models import User
 | 
			
		||||
from django.http import HttpRequest
 | 
			
		||||
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
 | 
			
		||||
from user.forms import UserNotificationForm, UserContactForm
 | 
			
		||||
from konova.decorators import any_group_check, default_group_required
 | 
			
		||||
from user.forms import UserNotificationForm, UserContactForm, UserAPITokenForm
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
@ -70,6 +72,40 @@ 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
 | 
			
		||||
def contact_view(request: HttpRequest, id: str):
 | 
			
		||||
    """ Renders contact modal view of a users contact data
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user