Merge pull request '31_API' (#90) from 31_API into master
Reviewed-on: SGD-Nord/konova#90pull/92/head
commit
14a38b43de
@ -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)
|
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
name = 'api'
|
@ -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 *
|
@ -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
|
@ -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"
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"eco_account": "CHANGE_BEFORE_RUN!!!",
|
||||||
|
"surface": 500.0,
|
||||||
|
"intervention": "CHANGE_BEFORE_RUN!!!"
|
||||||
|
}
|
@ -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": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -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": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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)
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"eco_account": "CHANGE_BEFORE_RUN!!!",
|
||||||
|
"surface": 523400.0,
|
||||||
|
"intervention": "CHANGE_BEFORE_RUN!!!"
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
@ -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 *
|
@ -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"),
|
||||||
|
]
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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"),
|
||||||
|
]
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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")
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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",
|
||||||
|
))
|
@ -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 *
|
@ -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
|
@ -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
|
||||||
|
|
||||||
|
"""
|
@ -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
|
@ -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
|
Binary file not shown.
@ -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>
|
||||||
|
|
@ -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 %}
|
Loading…
Reference in New Issue