Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6dcb6c6db | |||
| 7bacbecdec | |||
| 58ce00a5a6 | |||
| be885306c5 | |||
| 8b67df7617 | |||
| ab9af7ae2f | |||
| f085caac5d | |||
| 7f8d900c10 | |||
| e7031d0bc2 | |||
| 8aa3fbd97a | |||
| eb3b9eb5c1 | |||
| 1e86a1ce5e | |||
| fbab67f897 | |||
| 59c5072619 | |||
| 51525d79f5 | |||
| 4f482595c6 | |||
| 57aa39a670 | |||
| 2f4301d09f | |||
| fec43e1bed | |||
| 9f32c2fdd9 | |||
| b310349c1a | |||
| 946f3af77c | |||
| e73b7633a3 | |||
| d1dc61cbd7 | |||
| 3ba3785ef1 | |||
| 71afdd8b36 | |||
| a334fff54d | |||
| b65dae5b95 | |||
| bb399571b1 | |||
| 92211445e7 | |||
| 73c61e96f5 | |||
| 339f074681 | |||
| 989d256521 | |||
| 623c29f827 | |||
| eb975cd3c5 | |||
| 5a8765f638 | |||
| 9c2bdcdacf | |||
| 48e3e84b4c | |||
| db884baa09 | |||
| 253b509122 | |||
| c60afb0391 | |||
| 49c14a67b6 | |||
| d13c3e8094 | |||
| c6e784e6d4 | |||
| 940aa38154 | |||
| 887a3552b4 | |||
| 3b36193566 | |||
| 090f6faa4e | |||
| d2ec3d9c08 | |||
| 8165540c00 | |||
| 87fae51144 | |||
| bf1c0e2078 | |||
| b85e33dc22 | |||
| 83d70b6d59 | |||
| 60e23d15fc | |||
| fb1dce9d3c | |||
| 6ecbd74b93 | |||
| a6551534dc | |||
| 6060f1c1bd | |||
| 59ff1c79a8 | |||
| 64d0a3bd12 | |||
| a34a0b4d8a | |||
| 1be77e8b22 | |||
| f7b074ab23 | |||
| ac4dacefe0 | |||
| fc31ad4ae0 | |||
| 7689e0b80d | |||
| 17c954e844 | |||
| 06ad0fdc2d | |||
| 98a1a70a69 | |||
| 22a3339157 | |||
| c98f41c9a8 | |||
| f7dbf428ac | |||
| fe409605aa | |||
| a4f6519601 | |||
| 0b9587f17c | |||
| 9794688f20 | |||
| 310f7d124c | |||
| cecceba8b2 | |||
| ce7d207a6d | |||
| 25d04006d8 | |||
| fe29035158 | |||
| f6c39304ab | |||
| f05bb6ff5c | |||
| aa675aa046 | |||
| 6dac847d22 | |||
| 02a6b815ea | |||
| 262f1a6298 | |||
| b831a63db6 | |||
| 3e2c5b7e47 | |||
| e8fae7a6f4 | |||
| 7a760332fa | |||
| 7091b3d707 | |||
| a58532322e | |||
| cf43a4351e | |||
| e65b7ec45c | |||
| a1d3fafc61 | |||
| 767285112d | |||
| b62113df8b | |||
| 0658a2f6f1 | |||
| 117f0eaeb6 | |||
| 46ff1d2bc5 | |||
| ec69556b1c | |||
| eb22dcf9b4 | |||
| 30d1e4033d | |||
| b34aa44d44 | |||
| f3a837a8a6 | |||
| f36f219d2e | |||
| 3df605376c | |||
| 23790bca8d | |||
| 925d5f5070 | |||
| 3814c2749a | |||
| d0991ea832 |
@@ -6,6 +6,7 @@
|
||||
"title": "Test_compensation",
|
||||
"is_cef": false,
|
||||
"is_coherence_keeping": false,
|
||||
"is_pik": false,
|
||||
"intervention": "MUST_BE_SET_IN_TEST",
|
||||
"before_states": [
|
||||
],
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
"properties": {
|
||||
"title": "Test_ecoaccount",
|
||||
"deductable_surface": 10000.0,
|
||||
"is_pik": false,
|
||||
"responsible": {
|
||||
"conservation_office": null,
|
||||
"conservation_file_number": null,
|
||||
"handler": null
|
||||
"handler": {
|
||||
"type": null,
|
||||
"detail": "Someone"
|
||||
}
|
||||
},
|
||||
"legal": {
|
||||
"agreement_date": null
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
],
|
||||
"properties": {
|
||||
"title": "Test_ema",
|
||||
"is_pik": false,
|
||||
"responsible": {
|
||||
"conservation_office": null,
|
||||
"conservation_file_number": null,
|
||||
"handler": null
|
||||
"handler": {
|
||||
"type": null,
|
||||
"detail": "Someone"
|
||||
}
|
||||
},
|
||||
"before_states": [
|
||||
],
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"registration_file_number": null,
|
||||
"conservation_office": null,
|
||||
"conservation_file_number": null,
|
||||
"handler": null
|
||||
"handler": {
|
||||
"type": null,
|
||||
"detail": "Someone"
|
||||
}
|
||||
},
|
||||
"legal": {
|
||||
"registration_date": null,
|
||||
|
||||
@@ -109,8 +109,8 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.eco_account.share_with(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
self.eco_account.share_with_user(self.superuser)
|
||||
|
||||
url = reverse("api:v1:deduction")
|
||||
json_file_path = "api/tests/v1/create/deduction_create_post_body.json"
|
||||
|
||||
@@ -57,7 +57,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase):
|
||||
|
||||
"""
|
||||
test_intervention = self.create_dummy_intervention()
|
||||
test_intervention.share_with(self.superuser)
|
||||
test_intervention.share_with_user(self.superuser)
|
||||
url = reverse("api:v1:intervention", args=(str(test_intervention.id),))
|
||||
self._test_delete_object(test_intervention, url)
|
||||
|
||||
@@ -68,7 +68,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase):
|
||||
|
||||
"""
|
||||
test_comp = self.create_dummy_compensation()
|
||||
test_comp.share_with(self.superuser)
|
||||
test_comp.share_with_user(self.superuser)
|
||||
url = reverse("api:v1:compensation", args=(str(test_comp.id),))
|
||||
self._test_delete_object(test_comp, url)
|
||||
|
||||
@@ -79,7 +79,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase):
|
||||
|
||||
"""
|
||||
test_acc = self.create_dummy_eco_account()
|
||||
test_acc.share_with(self.superuser)
|
||||
test_acc.share_with_user(self.superuser)
|
||||
url = reverse("api:v1:ecoaccount", args=(str(test_acc.id),))
|
||||
self._test_delete_object(test_acc, url)
|
||||
|
||||
@@ -90,7 +90,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase):
|
||||
|
||||
"""
|
||||
test_ema = self.create_dummy_ema()
|
||||
test_ema.share_with(self.superuser)
|
||||
test_ema.share_with_user(self.superuser)
|
||||
url = reverse("api:v1:ema", args=(str(test_ema.id),))
|
||||
self._test_delete_object(test_ema, url)
|
||||
|
||||
@@ -101,7 +101,7 @@ class APIV1DeleteTestCase(BaseAPIV1TestCase):
|
||||
|
||||
"""
|
||||
test_deduction = self.create_dummy_deduction()
|
||||
test_deduction.intervention.share_with(self.superuser)
|
||||
test_deduction.intervention.share_with_user(self.superuser)
|
||||
url = reverse("api:v1:deduction", args=(str(test_deduction.id),))
|
||||
|
||||
response = self._run_delete_request(url)
|
||||
|
||||
@@ -36,7 +36,12 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
||||
"""
|
||||
response = self._run_get_request(url)
|
||||
content = json.loads(response.content)
|
||||
geojson = content[str(obj.id)]
|
||||
self.assertIn("rpp", content)
|
||||
self.assertIn("p", content)
|
||||
self.assertIn("next", content)
|
||||
self.assertIn("results", content)
|
||||
paginated_content = content["results"]
|
||||
geojson = paginated_content[str(obj.id)]
|
||||
self.assertEqual(response.status_code, 200, msg=response.content)
|
||||
return geojson
|
||||
|
||||
@@ -59,7 +64,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(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)
|
||||
@@ -80,13 +85,33 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
||||
except KeyError as e:
|
||||
self.fail(e)
|
||||
|
||||
def test_get_shared(self):
|
||||
""" Tests api GET on shared info of the intervention
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
self.intervention.share_with_team(self.team)
|
||||
url = reverse("api:v1:intervention-share", args=(str(self.intervention.id),))
|
||||
response = self._run_get_request(url)
|
||||
content = json.loads(response.content)
|
||||
self.assertIn("users", content)
|
||||
self.assertIn(self.superuser.username, content["users"])
|
||||
self.assertEqual(1, len(content["users"]))
|
||||
self.assertIn("teams", content)
|
||||
self.assertEqual(1, len(content["teams"]))
|
||||
for team in content["teams"]:
|
||||
self.assertEqual(team["id"], str(self.team.id))
|
||||
self.assertEqual(team["name"], self.team.name)
|
||||
|
||||
def test_get_compensation(self):
|
||||
""" Tests api GET
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
self.compensation.intervention = self.intervention
|
||||
self.compensation.save()
|
||||
|
||||
@@ -97,6 +122,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
||||
props = geojson["properties"]
|
||||
props["is_cef"]
|
||||
props["is_coherence_keeping"]
|
||||
props["is_pik"]
|
||||
props["intervention"]
|
||||
props["intervention"]["id"]
|
||||
props["intervention"]["identifier"]
|
||||
@@ -114,7 +140,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.eco_account.share_with(self.superuser)
|
||||
self.eco_account.share_with_user(self.superuser)
|
||||
|
||||
url = reverse("api:v1:ecoaccount", args=(str(self.eco_account.id),))
|
||||
geojson = self._test_get_object(self.eco_account, url)
|
||||
@@ -143,7 +169,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.ema.share_with(self.superuser)
|
||||
self.ema.share_with_user(self.superuser)
|
||||
|
||||
url = reverse("api:v1:ema", args=(str(self.ema.id),))
|
||||
geojson = self._test_get_object(self.ema, url)
|
||||
@@ -167,7 +193,7 @@ class APIV1GetTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.deduction.intervention.share_with(self.superuser)
|
||||
self.deduction.intervention.share_with_user(self.superuser)
|
||||
|
||||
url = reverse("api:v1:deduction", args=(str(self.deduction.id),))
|
||||
_json = self._test_get_object(self.deduction, url)
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"title": "TEST_compensation_CHANGED",
|
||||
"is_cef": true,
|
||||
"is_coherence_keeping": true,
|
||||
"is_pik": true,
|
||||
"intervention": "CHANGE_BEFORE_RUN!!!",
|
||||
"before_states": [],
|
||||
"after_states": [],
|
||||
|
||||
@@ -45,10 +45,14 @@
|
||||
"properties": {
|
||||
"title": "TEST_account_CHANGED",
|
||||
"deductable_surface": "100000.0",
|
||||
"is_pik": true,
|
||||
"responsible": {
|
||||
"conservation_office": null,
|
||||
"conservation_file_number": "123-TEST",
|
||||
"handler": "TEST_HANDLER_CHANGED"
|
||||
"handler": {
|
||||
"type": null,
|
||||
"detail": "TEST HANDLER CHANGED"
|
||||
}
|
||||
},
|
||||
"legal": {
|
||||
"agreement_date": "2022-01-11"
|
||||
|
||||
@@ -47,8 +47,12 @@
|
||||
"responsible": {
|
||||
"conservation_office": null,
|
||||
"conservation_file_number": "TEST_CHANGED",
|
||||
"handler": "TEST_HANDLER_CHANGED"
|
||||
"handler": {
|
||||
"type": null,
|
||||
"detail": "TEST_HANDLER_CHANGED"
|
||||
}
|
||||
},
|
||||
"is_pik": true,
|
||||
"before_states": [],
|
||||
"after_states": [],
|
||||
"actions": [],
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"users": [
|
||||
"CHANGE_ME"
|
||||
],
|
||||
"teams": [
|
||||
"CHANGE_ME"
|
||||
]
|
||||
}
|
||||
@@ -49,7 +49,10 @@
|
||||
"registration_file_number": "CHANGED",
|
||||
"conservation_office": null,
|
||||
"conservation_file_number": "CHANGED",
|
||||
"handler": null
|
||||
"handler": {
|
||||
"type": null,
|
||||
"detail": "TEST_HANDLER_CHANGED"
|
||||
}
|
||||
},
|
||||
"legal": {
|
||||
"registration_date": "2022-02-01",
|
||||
|
||||
@@ -52,7 +52,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(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"
|
||||
@@ -79,7 +79,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
"""
|
||||
self.compensation.intervention = self.intervention
|
||||
self.compensation.save()
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
|
||||
modified_on = self.compensation.modified
|
||||
url = reverse("api:v1:compensation", args=(str(self.compensation.id),))
|
||||
@@ -97,6 +97,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
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(put_props["is_pik"], self.compensation.is_pik)
|
||||
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())
|
||||
@@ -108,7 +109,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.eco_account.share_with(self.superuser)
|
||||
self.eco_account.share_with_user(self.superuser)
|
||||
|
||||
modified_on = self.eco_account.modified
|
||||
url = reverse("api:v1:ecoaccount", args=(str(self.eco_account.id),))
|
||||
@@ -126,7 +127,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
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["responsible"]["handler"]["detail"], self.eco_account.responsible.handler.detail)
|
||||
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())
|
||||
@@ -139,7 +140,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.ema.share_with(self.superuser)
|
||||
self.ema.share_with_user(self.superuser)
|
||||
|
||||
modified_on = self.ema.modified
|
||||
url = reverse("api:v1:ema", args=(str(self.ema.id),))
|
||||
@@ -156,7 +157,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
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(put_props["responsible"]["handler"]["detail"], self.ema.responsible.handler.detail)
|
||||
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())
|
||||
@@ -168,8 +169,8 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.deduction.intervention.share_with(self.superuser)
|
||||
self.deduction.account.share_with(self.superuser)
|
||||
self.deduction.intervention.share_with_user(self.superuser)
|
||||
self.deduction.account.share_with_user(self.superuser)
|
||||
|
||||
url = reverse("api:v1:deduction", args=(str(self.deduction.id),))
|
||||
json_file_path = "api/tests/v1/update/deduction_update_put_body.json"
|
||||
@@ -184,3 +185,24 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||
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)
|
||||
|
||||
def test_update_share_intervention(self):
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
url = reverse("api:v1:intervention-share", args=(str(self.intervention.id),))
|
||||
json_file_path = "api/tests/v1/update/intervention_share_update_put_body.json"
|
||||
with open(json_file_path) as json_file:
|
||||
put_body = json.load(fp=json_file)
|
||||
put_body["users"] = [self.user.username]
|
||||
put_body["teams"] = [self.team.name]
|
||||
|
||||
self.assertFalse(self.intervention.is_shared_with(self.user))
|
||||
self.assertEqual(0, self.intervention.shared_teams.count())
|
||||
|
||||
response = self._run_update_request(url, put_body)
|
||||
self.assertEqual(response.status_code, 200, msg=response.content)
|
||||
self.intervention.refresh_from_db()
|
||||
|
||||
self.assertEqual(1, self.intervention.shared_teams.count())
|
||||
self.assertEqual(2, self.intervention.shared_users.count())
|
||||
self.assertEqual(self.team.name, self.intervention.shared_teams.first().name)
|
||||
self.assertTrue(self.intervention.is_shared_with(self.user))
|
||||
|
||||
@@ -10,6 +10,7 @@ from abc import abstractmethod
|
||||
|
||||
from django.contrib.gis import geos
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
from konova.utils.message_templates import DATA_UNSHARED
|
||||
|
||||
@@ -19,6 +20,10 @@ class AbstractModelAPISerializer:
|
||||
lookup = None
|
||||
properties_data = None
|
||||
|
||||
rpp = None
|
||||
page_number = None
|
||||
paginator = None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -80,9 +85,12 @@ class AbstractModelAPISerializer:
|
||||
Returns:
|
||||
serialized_data (dict)
|
||||
"""
|
||||
entries = self.model.objects.filter(**self.lookup)
|
||||
entries = self.model.objects.filter(**self.lookup).order_by("id")
|
||||
self.paginator = Paginator(entries, self.rpp)
|
||||
requested_entries = self.paginator.page(self.page_number)
|
||||
|
||||
serialized_data = {}
|
||||
for entry in entries:
|
||||
for entry in requested_entries.object_list:
|
||||
serialized_data[str(entry.id)] = self._model_to_geo_json(entry)
|
||||
return serialized_data
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
||||
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["is_pik"] = entry.is_pik
|
||||
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())
|
||||
@@ -113,6 +114,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
||||
obj.title = properties["title"]
|
||||
obj.is_cef = properties["is_cef"]
|
||||
obj.is_coherence_keeping = properties["is_coherence_keeping"]
|
||||
obj.is_pik = properties.get("is_pik", False)
|
||||
obj = self.set_intervention(obj, properties["intervention"], user)
|
||||
|
||||
obj.geometry.save()
|
||||
@@ -149,6 +151,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
||||
obj.title = properties["title"]
|
||||
obj.is_cef = properties["is_cef"]
|
||||
obj.is_coherence_keeping = properties["is_coherence_keeping"]
|
||||
obj.is_pik = properties.get("is_pik", False)
|
||||
obj.modified = update_action
|
||||
obj.geometry.geom = self._create_geometry_from_json(json_model)
|
||||
obj.geometry.modified = update_action
|
||||
|
||||
@@ -9,9 +9,9 @@ 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 codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
|
||||
from compensation.models import EcoAccount
|
||||
from intervention.models import Legal, Responsibility
|
||||
from intervention.models import Legal, Responsibility, Handler
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from user.models import UserActionLogEntry
|
||||
@@ -25,6 +25,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
model = EcoAccount
|
||||
|
||||
def _extend_properties_data(self, entry):
|
||||
self.properties_data["is_pik"] = entry.is_pik
|
||||
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)
|
||||
@@ -44,7 +45,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
return {
|
||||
"conservation_office": self._konova_code_to_json(responsible.conservation_office),
|
||||
"conservation_file_number": responsible.conservation_file_number,
|
||||
"handler": responsible.handler,
|
||||
"handler": self._handler_to_json(responsible.handler),
|
||||
}
|
||||
|
||||
def _set_responsibility(self, obj, responsibility_data: dict):
|
||||
@@ -64,7 +65,11 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
CODELIST_CONSERVATION_OFFICE_ID,
|
||||
)
|
||||
obj.responsible.conservation_file_number = responsibility_data["conservation_file_number"]
|
||||
obj.responsible.handler = responsibility_data["handler"]
|
||||
obj.responsible.handler.type = self._konova_code_from_json(
|
||||
responsibility_data["handler"]["type"],
|
||||
CODELIST_HANDLER_ID,
|
||||
)
|
||||
obj.responsible.handler.detail = responsibility_data["handler"]["detail"]
|
||||
return obj
|
||||
|
||||
def _set_legal(self, obj, legal_data):
|
||||
@@ -92,7 +97,9 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
|
||||
# Create linked objects
|
||||
obj = EcoAccount()
|
||||
obj.responsible = Responsibility()
|
||||
obj.responsible = Responsibility(
|
||||
handler=Handler()
|
||||
)
|
||||
obj.legal = Legal()
|
||||
created = create_action
|
||||
obj.created = created
|
||||
@@ -116,6 +123,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
properties = json_model["properties"]
|
||||
obj.identifier = obj.generate_new_identifier()
|
||||
obj.title = properties["title"]
|
||||
obj.is_pik = properties.get("is_pik", False)
|
||||
|
||||
try:
|
||||
obj.deductable_surface = float(properties["deductable_surface"])
|
||||
@@ -128,6 +136,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj = self._set_legal(obj, properties["legal"])
|
||||
|
||||
obj.geometry.save()
|
||||
obj.responsible.handler.save()
|
||||
obj.responsible.save()
|
||||
obj.legal.save()
|
||||
obj.save()
|
||||
@@ -162,6 +171,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
# Fill in data to objects
|
||||
properties = json_model["properties"]
|
||||
obj.title = properties["title"]
|
||||
obj.is_pik = properties.get("is_pik", False)
|
||||
obj.deductable_surface = float(properties["deductable_surface"])
|
||||
obj.modified = update_action
|
||||
obj.geometry.geom = self._create_geometry_from_json(json_model)
|
||||
@@ -170,6 +180,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj = self._set_legal(obj, properties["legal"])
|
||||
|
||||
obj.geometry.save()
|
||||
obj.responsible.handler.save()
|
||||
obj.responsible.save()
|
||||
obj.legal.save()
|
||||
obj.save()
|
||||
|
||||
@@ -9,9 +9,9 @@ from django.db import transaction
|
||||
|
||||
from api.utils.serializer.v1.serializer import AbstractModelAPISerializerV1, AbstractCompensationAPISerializerV1Mixin, \
|
||||
ResponsibilityAPISerializerV1Mixin
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
|
||||
from ema.models import Ema
|
||||
from intervention.models import Responsibility
|
||||
from intervention.models import Responsibility, Handler
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from user.models import UserActionLogEntry
|
||||
@@ -21,6 +21,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
model = Ema
|
||||
|
||||
def _extend_properties_data(self, entry):
|
||||
self.properties_data["is_pik"] = entry.is_pik
|
||||
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())
|
||||
@@ -31,7 +32,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
return {
|
||||
"conservation_office": self._konova_code_to_json(responsible.conservation_office),
|
||||
"conservation_file_number": responsible.conservation_file_number,
|
||||
"handler": responsible.handler,
|
||||
"handler": self._handler_to_json(responsible.handler),
|
||||
}
|
||||
|
||||
def _set_responsibility(self, obj, responsibility_data: dict):
|
||||
@@ -51,7 +52,11 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
CODELIST_CONSERVATION_OFFICE_ID,
|
||||
)
|
||||
obj.responsible.conservation_file_number = responsibility_data["conservation_file_number"]
|
||||
obj.responsible.handler = responsibility_data["handler"]
|
||||
obj.responsible.handler.type = self._konova_code_from_json(
|
||||
responsibility_data["handler"]["type"],
|
||||
CODELIST_HANDLER_ID,
|
||||
)
|
||||
obj.responsible.handler.detail = responsibility_data["handler"]["detail"]
|
||||
return obj
|
||||
|
||||
def _initialize_objects(self, json_model, user):
|
||||
@@ -75,7 +80,9 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
|
||||
# Create linked objects
|
||||
obj = Ema()
|
||||
obj.responsible = Responsibility()
|
||||
obj.responsible = Responsibility(
|
||||
handler=Handler()
|
||||
)
|
||||
created = create_action
|
||||
obj.created = created
|
||||
obj.geometry = geometry
|
||||
@@ -98,9 +105,11 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
properties = json_model["properties"]
|
||||
obj.identifier = obj.generate_new_identifier()
|
||||
obj.title = properties["title"]
|
||||
obj.is_pik = properties.get("is_pik", False)
|
||||
obj = self._set_responsibility(obj, properties["responsible"])
|
||||
|
||||
obj.geometry.save()
|
||||
obj.responsible.handler.save()
|
||||
obj.responsible.save()
|
||||
obj.save()
|
||||
|
||||
@@ -134,12 +143,14 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
||||
# Fill in data to objects
|
||||
properties = json_model["properties"]
|
||||
obj.title = properties["title"]
|
||||
obj.is_pik = properties.get("is_pik", False)
|
||||
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.handler.save()
|
||||
obj.responsible.save()
|
||||
obj.save()
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ 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 intervention.models import Intervention, Responsibility, Legal, Handler
|
||||
from konova.models import Geometry
|
||||
from konova.tasks import celery_update_parcels
|
||||
from user.models import UserActionLogEntry
|
||||
@@ -69,7 +69,9 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
|
||||
# Create linked objects
|
||||
obj = Intervention()
|
||||
resp = Responsibility()
|
||||
resp = Responsibility(
|
||||
handler=Handler()
|
||||
)
|
||||
legal = Legal()
|
||||
created = create_action
|
||||
obj.legal = legal
|
||||
@@ -130,6 +132,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
id__in=payments
|
||||
)
|
||||
obj.payments.set(payments)
|
||||
obj.send_data_to_egon()
|
||||
return obj
|
||||
|
||||
def create_model_from_json(self, json_model, user):
|
||||
@@ -152,6 +155,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
self._set_responsibility(obj, properties["responsible"])
|
||||
self._set_legal(obj, properties["legal"])
|
||||
|
||||
obj.responsible.handler.save()
|
||||
obj.responsible.save()
|
||||
obj.geometry.save()
|
||||
obj.legal.save()
|
||||
@@ -188,12 +192,13 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
||||
obj.geometry.geom = self._create_geometry_from_json(json_model)
|
||||
obj.geometry.modified = update_action
|
||||
|
||||
obj.responsible.handler.save()
|
||||
obj.responsible.save()
|
||||
obj.geometry.save()
|
||||
obj.legal.save()
|
||||
obj.save()
|
||||
|
||||
obj.mark_as_edited(user)
|
||||
obj.mark_as_edited(user, edit_comment="API update")
|
||||
|
||||
celery_update_parcels.delay(obj.geometry.id)
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ 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, \
|
||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_HANDLER_ID
|
||||
from compensation.models import CompensationAction, UnitChoices, CompensationState
|
||||
from intervention.models import Responsibility, Legal
|
||||
from intervention.models import Responsibility, Legal, Handler
|
||||
from konova.models import Deadline, DeadlineType
|
||||
from konova.utils.message_templates import DATA_UNSHARED
|
||||
|
||||
@@ -75,7 +75,10 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if json_str is None or len(json_str) == 0:
|
||||
if json_str is None:
|
||||
return None
|
||||
json_str = str(json_str)
|
||||
if len(json_str) == 0:
|
||||
return None
|
||||
code = KonovaCode.objects.get(
|
||||
atom_id=json_str,
|
||||
@@ -176,6 +179,12 @@ class ResponsibilityAPISerializerV1Mixin:
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def _handler_to_json(self, handler: Handler):
|
||||
return {
|
||||
"type": self._konova_code_to_json(handler.type),
|
||||
"detail": handler.detail
|
||||
}
|
||||
|
||||
def _responsible_to_json(self, responsible: Responsibility):
|
||||
""" Serializes Responsibility model into json
|
||||
|
||||
@@ -190,7 +199,7 @@ class ResponsibilityAPISerializerV1Mixin:
|
||||
"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,
|
||||
"handler": self._handler_to_json(responsible.handler),
|
||||
}
|
||||
|
||||
def _set_responsibility(self, obj, responsibility_data: dict):
|
||||
@@ -215,7 +224,11 @@ class ResponsibilityAPISerializerV1Mixin:
|
||||
CODELIST_CONSERVATION_OFFICE_ID,
|
||||
)
|
||||
obj.responsible.conservation_file_number = responsibility_data["conservation_file_number"]
|
||||
obj.responsible.handler = responsibility_data["handler"]
|
||||
obj.responsible.handler.type = self._konova_code_from_json(
|
||||
responsibility_data["handler"]["type"],
|
||||
CODELIST_HANDLER_ID,
|
||||
)
|
||||
obj.responsible.handler.detail = responsibility_data["handler"]["detail"]
|
||||
return obj
|
||||
|
||||
|
||||
@@ -367,7 +380,9 @@ class AbstractCompensationAPISerializerV1Mixin:
|
||||
"""
|
||||
actions = []
|
||||
for entry in actions_data:
|
||||
action = entry["action"]
|
||||
action_types = [
|
||||
self._konova_code_from_json(e, CODELIST_COMPENSATION_ACTION_ID) for e in entry["action_types"]
|
||||
]
|
||||
action_details = [
|
||||
self._konova_code_from_json(e, CODELIST_COMPENSATION_ACTION_DETAIL_ID) for e in entry["action_details"]
|
||||
]
|
||||
@@ -384,7 +399,7 @@ class AbstractCompensationAPISerializerV1Mixin:
|
||||
# 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
|
||||
action_entry = obj.actions.filter(
|
||||
action_type__atom_id=action,
|
||||
action_type__in=action_types,
|
||||
amount=amount,
|
||||
unit=unit,
|
||||
comment=comment,
|
||||
@@ -396,13 +411,13 @@ class AbstractCompensationAPISerializerV1Mixin:
|
||||
else:
|
||||
# Create and add id to list
|
||||
action_entry = CompensationAction.objects.create(
|
||||
action_type=self._konova_code_from_json(action, CODELIST_COMPENSATION_ACTION_ID),
|
||||
amount=amount,
|
||||
unit=unit,
|
||||
comment=comment,
|
||||
)
|
||||
actions.append(action_entry.id)
|
||||
|
||||
action_entry.action_type.set(action_types)
|
||||
action_entry.action_type_details.set(action_details)
|
||||
obj.actions.set(actions)
|
||||
return obj
|
||||
@@ -438,7 +453,9 @@ class AbstractCompensationAPISerializerV1Mixin:
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"action": self._konova_code_to_json(entry.action_type),
|
||||
"action_types": [
|
||||
self._konova_code_to_json(action) for action in entry.action_type.all()
|
||||
],
|
||||
"action_details": [
|
||||
self._konova_code_to_json(detail) for detail in entry.action_type_details.all()
|
||||
],
|
||||
|
||||
@@ -21,7 +21,6 @@ class AbstractAPIViewV1(AbstractAPIView):
|
||||
""" Holds general serialization functions for API v1
|
||||
|
||||
"""
|
||||
serializer = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.lookup = {
|
||||
@@ -45,11 +44,17 @@ class AbstractAPIViewV1(AbstractAPIView):
|
||||
response (JsonResponse)
|
||||
"""
|
||||
try:
|
||||
self.rpp = int(request.GET.get("rpp", self.rpp))
|
||||
self.page_number = int(request.GET.get("p", self.page_number))
|
||||
|
||||
self.serializer.rpp = self.rpp
|
||||
self.serializer.page_number = self.page_number
|
||||
|
||||
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)
|
||||
return self._return_error_response(e, 500)
|
||||
return self._return_response(request, data)
|
||||
|
||||
def post(self, request: HttpRequest):
|
||||
""" Handles the POST request
|
||||
@@ -67,7 +72,7 @@ class AbstractAPIViewV1(AbstractAPIView):
|
||||
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 self._return_error_response(e, 500)
|
||||
return JsonResponse({"id": created_id})
|
||||
|
||||
def put(self, request: HttpRequest, id=None):
|
||||
@@ -87,7 +92,7 @@ class AbstractAPIViewV1(AbstractAPIView):
|
||||
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 self._return_error_response(e, 500)
|
||||
return JsonResponse({"id": updated_id})
|
||||
|
||||
def delete(self, request: HttpRequest, id=None):
|
||||
@@ -104,7 +109,7 @@ class AbstractAPIViewV1(AbstractAPIView):
|
||||
try:
|
||||
success = self.serializer.delete_entry(id, self.user)
|
||||
except Exception as e:
|
||||
return self.return_error_response(e, 500)
|
||||
return self._return_error_response(e, 500)
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": success,
|
||||
|
||||
@@ -19,7 +19,7 @@ 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
|
||||
from user.models import User, Team
|
||||
|
||||
|
||||
class AbstractAPIView(View):
|
||||
@@ -31,10 +31,22 @@ class AbstractAPIView(View):
|
||||
|
||||
"""
|
||||
user = None
|
||||
serializer = None
|
||||
rpp = 5 # Results per page default
|
||||
page_number = 1 # Page number default
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.response_body_base = {
|
||||
"rpp": None,
|
||||
"p": None,
|
||||
"next": None,
|
||||
"results": None
|
||||
}
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
@@ -42,13 +54,14 @@ class AbstractAPIView(View):
|
||||
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)
|
||||
request.user = self.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 self._return_error_response(e, 403)
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def return_error_response(self, error, status_code=500):
|
||||
def _return_error_response(self, error, status_code=500):
|
||||
""" Returns an error as JsonReponse
|
||||
|
||||
Args:
|
||||
@@ -68,6 +81,31 @@ class AbstractAPIView(View):
|
||||
status=status_code
|
||||
)
|
||||
|
||||
def _return_response(self, request: HttpRequest, data):
|
||||
""" Returns all important data into a response object
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming request
|
||||
data (dict): The serialized data
|
||||
|
||||
Returns:
|
||||
response (JsonResponse): The response to be returned
|
||||
"""
|
||||
response = self.response_body_base
|
||||
next_page = self.page_number + 1
|
||||
next_page = next_page if next_page in self.serializer.paginator.page_range else None
|
||||
if next_page is not None:
|
||||
next_url = request.build_absolute_uri(
|
||||
request.path + f"?rpp={self.rpp}&p={next_page}"
|
||||
)
|
||||
else:
|
||||
next_url = None
|
||||
response["rpp"] = self.rpp
|
||||
response["p"] = self.page_number
|
||||
response["next"] = next_url
|
||||
response["results"] = data
|
||||
return JsonResponse(response)
|
||||
|
||||
|
||||
class InterventionCheckAPIView(AbstractAPIView):
|
||||
|
||||
@@ -82,14 +120,14 @@ class InterventionCheckAPIView(AbstractAPIView):
|
||||
response (JsonResponse)
|
||||
"""
|
||||
if not self.user.is_zb_user():
|
||||
return self.return_error_response("Permission not granted", 403)
|
||||
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)
|
||||
return self._return_error_response(e)
|
||||
|
||||
all_valid, check_details = self.run_quality_checks(obj)
|
||||
|
||||
@@ -160,13 +198,21 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
||||
"""
|
||||
try:
|
||||
users = self._get_shared_users_of_object(id)
|
||||
teams = self._get_shared_teams_of_object(id)
|
||||
except Exception as e:
|
||||
return self.return_error_response(e)
|
||||
return self._return_error_response(e)
|
||||
|
||||
data = {
|
||||
"users": [
|
||||
user.username for user in users
|
||||
]
|
||||
],
|
||||
"teams": [
|
||||
{
|
||||
"id": team.id,
|
||||
"name": team.name,
|
||||
}
|
||||
for team in teams
|
||||
],
|
||||
}
|
||||
|
||||
return JsonResponse(data)
|
||||
@@ -185,7 +231,7 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
||||
try:
|
||||
success = self._process_put_body(request.body, id)
|
||||
except Exception as e:
|
||||
return self.return_error_response(e)
|
||||
return self._return_error_response(e)
|
||||
data = {
|
||||
"success": success,
|
||||
}
|
||||
@@ -220,6 +266,22 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
||||
users = obj.shared_users
|
||||
return users
|
||||
|
||||
def _get_shared_teams_of_object(self, id) -> QuerySet:
|
||||
""" Check permissions and get the teams
|
||||
|
||||
Args:
|
||||
id (str): The object's id
|
||||
|
||||
Returns:
|
||||
users (QuerySet)
|
||||
"""
|
||||
obj = self.model.objects.get(
|
||||
id=id
|
||||
)
|
||||
self._check_user_has_shared_access(obj)
|
||||
teams = obj.shared_teams
|
||||
return teams
|
||||
|
||||
def _process_put_body(self, body: bytes, id: str):
|
||||
""" Reads the body data, performs validity checks and sets the new users
|
||||
|
||||
@@ -233,19 +295,26 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
||||
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", [])
|
||||
content = json.loads(body.decode("utf-8"))
|
||||
new_users = content.get("users", [])
|
||||
if len(new_users) == 0:
|
||||
raise ValueError("Shared user list must not be empty!")
|
||||
new_teams = content.get("teams", [])
|
||||
|
||||
# Eliminate duplicates
|
||||
new_users = list(dict.fromkeys(new_users))
|
||||
new_teams = list(dict.fromkeys(new_teams))
|
||||
|
||||
# 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))
|
||||
|
||||
# Make sure each of these names exist as a user
|
||||
new_teams_objs = []
|
||||
for team_name in new_teams:
|
||||
new_teams_objs.append(Team.objects.get(name=team_name))
|
||||
|
||||
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(
|
||||
@@ -254,7 +323,16 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
||||
id__in=obj.shared_users
|
||||
)
|
||||
new_users_objs = obj.shared_users.union(new_users_to_be_added)
|
||||
obj.share_with_list(new_users_objs)
|
||||
|
||||
new_teams_to_be_added = Team.objects.filter(
|
||||
name__in=new_teams
|
||||
).exclude(
|
||||
id__in=obj.shared_teams
|
||||
)
|
||||
new_teams_objs = obj.shared_teams.union(new_teams_to_be_added)
|
||||
|
||||
obj.share_with_user_list(new_users_objs)
|
||||
obj.share_with_team_list(new_teams_objs)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class KonovaCodeAdmin(admin.ModelAdmin):
|
||||
"is_selectable",
|
||||
"is_leaf",
|
||||
"parent",
|
||||
"found_in_codelists",
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
@@ -42,6 +43,12 @@ class KonovaCodeAdmin(admin.ModelAdmin):
|
||||
"short_name",
|
||||
]
|
||||
|
||||
def found_in_codelists(self, obj):
|
||||
codelists = KonovaCodeList.objects.filter(
|
||||
codes__in=[obj]
|
||||
).values_list("id", flat=True)
|
||||
codelists = "\n".join(str(x) for x in codelists)
|
||||
return codelists
|
||||
|
||||
#admin.site.register(KonovaCodeList, KonovaCodeListAdmin)
|
||||
admin.site.register(KonovaCode, KonovaCodeAdmin)
|
||||
|
||||
165
codelist/management/commands/sync_codelist.py
Normal file
165
codelist/management/commands/sync_codelist.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 31.05.22
|
||||
|
||||
"""
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from compensation.models import CompensationAction, CompensationState
|
||||
from intervention.models import Legal, Handler, Responsibility
|
||||
from konova.management.commands.setup import BaseKonovaCommand
|
||||
|
||||
|
||||
class Command(BaseKonovaCommand):
|
||||
help = "Updates internal codelist by external API"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.sync_codelist()
|
||||
except KeyboardInterrupt:
|
||||
self._break_line()
|
||||
exit(-1)
|
||||
|
||||
def __get_newest_code(self, code):
|
||||
code = KonovaCode.objects.filter(
|
||||
atom_id=code.atom_id,
|
||||
parent=code.parent,
|
||||
code_lists__in=code.code_lists.all(),
|
||||
).order_by(
|
||||
"-id"
|
||||
).first()
|
||||
return code
|
||||
|
||||
def __migrate_compensation_action_codes(self):
|
||||
all_actions = CompensationAction.objects.all()
|
||||
used_codes = []
|
||||
for action in all_actions:
|
||||
stored_codes = action.action_type.all()
|
||||
codes = []
|
||||
for code in stored_codes:
|
||||
codes.append(self.__get_newest_code(code))
|
||||
action.action_type.set(codes)
|
||||
used_codes += codes
|
||||
|
||||
stored_codes = action.action_type_details.all()
|
||||
codes = []
|
||||
for code in stored_codes:
|
||||
codes.append(self.__get_newest_code(code))
|
||||
action.action_type_details.set(codes)
|
||||
used_codes += codes
|
||||
|
||||
action.save()
|
||||
return used_codes
|
||||
|
||||
def __migrate_compensation_state_codes(self):
|
||||
all_states = CompensationState.objects.all()
|
||||
used_codes = []
|
||||
for state in all_states:
|
||||
code = state.biotope_type
|
||||
if code is not None:
|
||||
new_code = self.__get_newest_code(code)
|
||||
state.biotope_type = new_code
|
||||
used_codes.append(new_code)
|
||||
|
||||
stored_codes = state.biotope_type_details.all()
|
||||
codes = []
|
||||
for code in stored_codes:
|
||||
codes.append(self.__get_newest_code(code))
|
||||
state.biotope_type_details.set(codes)
|
||||
|
||||
used_codes += codes
|
||||
state.save()
|
||||
return used_codes
|
||||
|
||||
def __migrate_legal_codes(self):
|
||||
all_legal = Legal.objects.all()
|
||||
used_codes = []
|
||||
for legal in all_legal:
|
||||
code = legal.process_type
|
||||
if code is not None:
|
||||
new_code = self.__get_newest_code(code)
|
||||
legal.process_type = new_code
|
||||
used_codes.append(new_code)
|
||||
|
||||
stored_codes = legal.laws.all()
|
||||
codes = []
|
||||
for code in stored_codes:
|
||||
codes.append(self.__get_newest_code(code))
|
||||
legal.laws.set(codes)
|
||||
|
||||
used_codes += codes
|
||||
legal.save()
|
||||
return used_codes
|
||||
|
||||
def __migrate_handler_codes(apps):
|
||||
all_handlers = Handler.objects.all()
|
||||
used_codes = []
|
||||
for handler in all_handlers:
|
||||
code = handler.type
|
||||
if code is None:
|
||||
continue
|
||||
new_code = apps.__get_newest_code(code)
|
||||
handler.type = new_code
|
||||
used_codes.append(new_code)
|
||||
handler.save()
|
||||
return used_codes
|
||||
|
||||
def __migrate_responsibility_codes(apps):
|
||||
all_resps = Responsibility.objects.all()
|
||||
used_codes = []
|
||||
for responsibility in all_resps:
|
||||
code = responsibility.registration_office
|
||||
if code is not None:
|
||||
new_code = apps.__get_newest_code(code)
|
||||
responsibility.registration_office = new_code
|
||||
used_codes.append(new_code)
|
||||
|
||||
code = responsibility.conservation_office
|
||||
if code is not None:
|
||||
new_code = apps.__get_newest_code(code)
|
||||
responsibility.conservation_office = new_code
|
||||
used_codes.append(new_code)
|
||||
|
||||
responsibility.save()
|
||||
return used_codes
|
||||
|
||||
def sync_codelist(self):
|
||||
""" Due to issues on the external codelist app there can be multiple entries of the same code
|
||||
(atom_id, parent, list) but with different identifiers.
|
||||
|
||||
These issues have been resolved but already
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self._write_warning("Sync codes in usage and replace by newest entries...")
|
||||
used_codes = []
|
||||
used_codes += self.__migrate_compensation_action_codes()
|
||||
used_codes += self.__migrate_compensation_state_codes()
|
||||
used_codes += self.__migrate_legal_codes()
|
||||
used_codes += self.__migrate_handler_codes()
|
||||
used_codes += self.__migrate_responsibility_codes()
|
||||
self._write_success(f"Synced {len(used_codes)} code usages!")
|
||||
|
||||
all_codes = KonovaCode.objects.all()
|
||||
newest_code_ids = []
|
||||
for code in all_codes:
|
||||
newest_code = self.__get_newest_code(code)
|
||||
newest_code_ids.append(newest_code.id)
|
||||
|
||||
code_ids_to_keep = set(newest_code_ids)
|
||||
self._write_warning(f"Of {all_codes.count()} KonovaCodes there are {len(code_ids_to_keep)} to keep as newest versions...")
|
||||
|
||||
deletable_codes = KonovaCode.objects.all().exclude(
|
||||
id__in=code_ids_to_keep
|
||||
)
|
||||
deletable_codes_count = deletable_codes.count()
|
||||
self._write_warning(f"{deletable_codes_count} found which are obsolet...")
|
||||
if deletable_codes_count > 0:
|
||||
deletable_codes.delete()
|
||||
self._write_success("Obsolete codes deleted!")
|
||||
@@ -6,12 +6,11 @@ Created on: 23.08.21
|
||||
|
||||
"""
|
||||
import requests
|
||||
from django.core.management import BaseCommand
|
||||
from xml.etree import ElementTree as etree
|
||||
|
||||
from codelist.models import KonovaCode, KonovaCodeList
|
||||
from codelist.settings import CODELIST_INTERVENTION_HANDLER_ID, CODELIST_CONSERVATION_OFFICE_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_COMPENSATION_HANDLER_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, CODELIST_HANDLER_ID, \
|
||||
CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_CLASS_ID, CODELIST_COMPENSATION_ADDITIONAL_TYPE_ID, \
|
||||
CODELIST_BASE_URL, CODELIST_PROCESS_TYPE_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \
|
||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||
@@ -36,7 +35,7 @@ class Command(BaseKonovaCommand):
|
||||
CODELIST_BIOTOPES_ID,
|
||||
CODELIST_BIOTOPES_EXTRA_CODES_ID,
|
||||
CODELIST_LAW_ID,
|
||||
CODELIST_COMPENSATION_HANDLER_ID,
|
||||
CODELIST_HANDLER_ID,
|
||||
CODELIST_COMPENSATION_ACTION_ID,
|
||||
CODELIST_COMPENSATION_ACTION_CLASS_ID,
|
||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
# Generated by Django 3.1.3 on 2022-01-14 08:36
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def load_initial_codes(apps, schema_editor):
|
||||
call_command('update_codelist')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
@@ -32,4 +36,5 @@ class Migration(migrations.Migration):
|
||||
('codes', models.ManyToManyField(blank=True, help_text='Codes for this list', related_name='code_lists', to='codelist.KonovaCode')),
|
||||
],
|
||||
),
|
||||
migrations.RunPython(load_initial_codes),
|
||||
]
|
||||
|
||||
@@ -65,6 +65,25 @@ class KonovaCode(models.Model):
|
||||
ret_val += ", " + self.parent.long_name
|
||||
return ret_val
|
||||
|
||||
def add_children(self, order_by: str = "long_name"):
|
||||
""" Adds all children (resurcively until leaf) as .children to the KonovaCode
|
||||
|
||||
Returns:
|
||||
code (KonovaCode): The manipulated KonovaCode instance
|
||||
"""
|
||||
if self.is_leaf:
|
||||
return self
|
||||
|
||||
children = KonovaCode.objects.filter(
|
||||
parent=self
|
||||
).order_by(
|
||||
order_by
|
||||
)
|
||||
self.children = children
|
||||
for child in children:
|
||||
child.add_children(order_by)
|
||||
return self
|
||||
|
||||
|
||||
class KonovaCodeList(models.Model):
|
||||
"""
|
||||
|
||||
@@ -13,12 +13,13 @@ CODELIST_BASE_URL = "https://codelisten.naturschutz.rlp.de/repository/referenzli
|
||||
CODELIST_INTERVENTION_HANDLER_ID = 903 # CLMassnahmeträger
|
||||
CODELIST_CONSERVATION_OFFICE_ID = 907 # CLNaturschutzbehörden
|
||||
CODELIST_REGISTRATION_OFFICE_ID = 1053 # CLZulassungsbehörden
|
||||
CODELIST_BIOTOPES_ID = 974 # CL_EIV_Biotoptypen
|
||||
CODELIST_BIOTOPES_ID = 654 # CL_Biotoptypen
|
||||
CODELIST_AFTER_STATE_BIOTOPES_ID = 974 # CL-KSP_ZielBiotoptypen - USAGE HAS BEEN DROPPED IN 2022 IN FAVOR OF 654
|
||||
CODELIST_BIOTOPES_EXTRA_CODES_ID = 975 # CLZusatzbezeichnung
|
||||
CODELIST_LAW_ID = 1048 # CLVerfahrensrecht
|
||||
CODELIST_PROCESS_TYPE_ID = 44382 # CLVerfahrenstyp
|
||||
|
||||
CODELIST_COMPENSATION_HANDLER_ID = 1052 # CLEingreifer
|
||||
CODELIST_HANDLER_ID = 1052 # CLEingreifer
|
||||
CODELIST_COMPENSATION_ACTION_ID = 1026 # CLMassnahmedetail
|
||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID = 1035 # CLZusatzmerkmal
|
||||
CODELIST_COMPENSATION_ACTION_CLASS_ID = 1034 # CLMassnahmeklasse
|
||||
|
||||
@@ -21,16 +21,30 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
|
||||
"identifier",
|
||||
"title",
|
||||
"comment",
|
||||
"after_states",
|
||||
"before_states",
|
||||
"list_after_states",
|
||||
"list_before_states",
|
||||
"geometry",
|
||||
]
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
return super().get_readonly_fields(request, obj) + [
|
||||
"after_states",
|
||||
"before_states",
|
||||
"list_after_states",
|
||||
"list_before_states",
|
||||
"geometry",
|
||||
]
|
||||
|
||||
def list_after_states(self, obj):
|
||||
states = obj.after_states.all()
|
||||
states = [str(state) for state in states]
|
||||
states = "\n".join(states)
|
||||
return states
|
||||
|
||||
def list_before_states(self, obj):
|
||||
states = obj.before_states.all()
|
||||
states = [str(state) for state in states]
|
||||
states = "\n".join(states)
|
||||
return states
|
||||
|
||||
|
||||
class CompensationAdmin(AbstractCompensationAdmin):
|
||||
autocomplete_fields = [
|
||||
@@ -41,6 +55,7 @@ class CompensationAdmin(AbstractCompensationAdmin):
|
||||
return super().get_fields(request, obj) + [
|
||||
"is_cef",
|
||||
"is_coherence_keeping",
|
||||
"is_pik",
|
||||
"intervention",
|
||||
]
|
||||
|
||||
|
||||
@@ -59,8 +59,9 @@ class CheckboxCompensationTableFilter(CheckboxTableFilter):
|
||||
"""
|
||||
if not value:
|
||||
return queryset.filter(
|
||||
intervention__users__in=[self.user], # requesting user has access
|
||||
)
|
||||
Q(intervention__users__in=[self.user]) | # requesting user has access
|
||||
Q(intervention__teams__in=self.user.shared_teams)
|
||||
).distinct()
|
||||
else:
|
||||
return queryset
|
||||
|
||||
@@ -127,24 +128,6 @@ class CheckboxEcoAccountTableFilter(CheckboxTableFilter):
|
||||
)
|
||||
)
|
||||
|
||||
def filter_show_all(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_all' setting
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not value:
|
||||
return queryset.filter(
|
||||
users__in=[self.user], # requesting user has access
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
def filter_only_show_unrecorded(self, queryset, name, value) -> QuerySet:
|
||||
""" Filters queryset depending on value of 'show_recorded' setting
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django import forms
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
|
||||
from compensation.models import Compensation, EcoAccount
|
||||
from intervention.inputs import GenerateInput
|
||||
from intervention.models import Intervention, Responsibility, Legal
|
||||
from intervention.models import Intervention, Responsibility, Legal, Handler
|
||||
from konova.forms import BaseForm, SimpleGeomForm
|
||||
from konova.utils.message_templates import EDITED_GENERAL_DATA, COMPENSATION_ADDED_TEMPLATE
|
||||
from user.models import UserActionLogEntry
|
||||
@@ -101,12 +101,30 @@ class CompensationResponsibleFormMixin(forms.Form):
|
||||
}
|
||||
)
|
||||
)
|
||||
handler = forms.CharField(
|
||||
label=_("Eco-account handler"),
|
||||
|
||||
handler_type = forms.ModelChoiceField(
|
||||
label=_("Eco-Account handler type"),
|
||||
label_suffix="",
|
||||
help_text=_("What type of handler is responsible for the ecoaccount?"),
|
||||
required=False,
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_HANDLER_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-handler-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
),
|
||||
)
|
||||
handler_detail = forms.CharField(
|
||||
label=_("Eco-Account handler detail"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=False,
|
||||
help_text=_("Who handles the eco-account"),
|
||||
help_text=_("Detail input on the handler"),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Company Mustermann"),
|
||||
@@ -142,7 +160,23 @@ class CoherenceCompensationFormMixin(forms.Form):
|
||||
)
|
||||
|
||||
|
||||
class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, CoherenceCompensationFormMixin):
|
||||
class PikCompensationFormMixin(forms.Form):
|
||||
""" A form mixin, providing PIK compensation field
|
||||
|
||||
"""
|
||||
is_pik = forms.BooleanField(
|
||||
label_suffix="",
|
||||
label=_("Is PIK"),
|
||||
help_text=_("Optionally: Whether this compensation is a compensation integrated in production?"),
|
||||
required=False,
|
||||
widget=forms.CheckboxInput()
|
||||
)
|
||||
|
||||
|
||||
class NewCompensationForm(AbstractCompensationForm,
|
||||
CEFCompensationFormMixin,
|
||||
CoherenceCompensationFormMixin,
|
||||
PikCompensationFormMixin):
|
||||
""" Form for creating new compensations.
|
||||
|
||||
Can be initialized with an intervention id for preselecting the related intervention.
|
||||
@@ -173,6 +207,7 @@ class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, Co
|
||||
"identifier",
|
||||
"title",
|
||||
"intervention",
|
||||
"is_pik",
|
||||
"is_cef",
|
||||
"is_coherence_keeping",
|
||||
"comment",
|
||||
@@ -216,6 +251,7 @@ class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, Co
|
||||
intervention = self.cleaned_data.get("intervention", None)
|
||||
is_cef = self.cleaned_data.get("is_cef", None)
|
||||
is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
|
||||
is_pik = self.cleaned_data.get("is_pik", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
@@ -231,6 +267,7 @@ class NewCompensationForm(AbstractCompensationForm, CEFCompensationFormMixin, Co
|
||||
created=action,
|
||||
is_cef=is_cef,
|
||||
is_coherence_keeping=is_coherence_keeping,
|
||||
is_pik=is_pik,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
)
|
||||
@@ -263,6 +300,7 @@ class EditCompensationForm(NewCompensationForm):
|
||||
"intervention": self.instance.intervention,
|
||||
"is_cef": self.instance.is_cef,
|
||||
"is_coherence_keeping": self.instance.is_coherence_keeping,
|
||||
"is_pik": self.instance.is_pik,
|
||||
"comment": self.instance.comment,
|
||||
}
|
||||
disabled_fields = []
|
||||
@@ -279,6 +317,7 @@ class EditCompensationForm(NewCompensationForm):
|
||||
intervention = self.cleaned_data.get("intervention", None)
|
||||
is_cef = self.cleaned_data.get("is_cef", None)
|
||||
is_coherence_keeping = self.cleaned_data.get("is_coherence_keeping", None)
|
||||
is_pik = self.cleaned_data.get("is_pik", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
@@ -295,6 +334,7 @@ class EditCompensationForm(NewCompensationForm):
|
||||
self.instance.is_cef = is_cef
|
||||
self.instance.is_coherence_keeping = is_coherence_keeping
|
||||
self.instance.comment = comment
|
||||
self.instance.is_pik = is_pik
|
||||
self.instance.modified = action
|
||||
self.instance.save()
|
||||
|
||||
@@ -304,7 +344,7 @@ class EditCompensationForm(NewCompensationForm):
|
||||
return self.instance
|
||||
|
||||
|
||||
class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMixin, PikCompensationFormMixin):
|
||||
""" Form for creating eco accounts
|
||||
|
||||
Inherits from basic AbstractCompensationForm and further form fields from CompensationResponsibleFormMixin
|
||||
@@ -345,7 +385,9 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
|
||||
"registration_date",
|
||||
"surface",
|
||||
"conservation_file_number",
|
||||
"handler",
|
||||
"is_pik",
|
||||
"handler_type",
|
||||
"handler_detail",
|
||||
"comment",
|
||||
]
|
||||
|
||||
@@ -368,10 +410,12 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
registration_date = self.cleaned_data.get("registration_date", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
handler_type = self.cleaned_data.get("handler_type", None)
|
||||
handler_detail = self.cleaned_data.get("handler_detail", None)
|
||||
surface = self.cleaned_data.get("surface", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
is_pik = self.cleaned_data.get("is_pik", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
@@ -379,6 +423,11 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
handler = Handler.objects.create(
|
||||
type=handler_type,
|
||||
detail=handler_detail,
|
||||
)
|
||||
|
||||
responsible = Responsibility.objects.create(
|
||||
handler=handler,
|
||||
conservation_file_number=conservation_file_number,
|
||||
@@ -398,9 +447,10 @@ class NewEcoAccountForm(AbstractCompensationForm, CompensationResponsibleFormMix
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
is_pik=is_pik,
|
||||
legal=legal
|
||||
)
|
||||
acc.share_with(user)
|
||||
acc.share_with_user(user)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
acc.log.add(action)
|
||||
@@ -423,14 +473,17 @@ class EditEcoAccountForm(NewEcoAccountForm):
|
||||
reg_date = self.instance.legal.registration_date
|
||||
if reg_date is not None:
|
||||
reg_date = reg_date.isoformat()
|
||||
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"surface": self.instance.deductable_surface,
|
||||
"handler": self.instance.responsible.handler,
|
||||
"handler_type": self.instance.responsible.handler.type,
|
||||
"handler_detail": self.instance.responsible.handler.detail,
|
||||
"registration_date": reg_date,
|
||||
"conservation_office": self.instance.responsible.conservation_office,
|
||||
"conservation_file_number": self.instance.responsible.conservation_file_number,
|
||||
"is_pik": self.instance.is_pik,
|
||||
"comment": self.instance.comment,
|
||||
}
|
||||
disabled_fields = []
|
||||
@@ -445,11 +498,13 @@ class EditEcoAccountForm(NewEcoAccountForm):
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
registration_date = self.cleaned_data.get("registration_date", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
handler_type = self.cleaned_data.get("handler_type", None)
|
||||
handler_detail = self.cleaned_data.get("handler_detail", None)
|
||||
surface = self.cleaned_data.get("surface", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
is_pik = self.cleaned_data.get("is_pik", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_edited_action(user)
|
||||
@@ -458,7 +513,9 @@ class EditEcoAccountForm(NewEcoAccountForm):
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Update responsible data
|
||||
self.instance.responsible.handler = handler
|
||||
self.instance.responsible.handler.type = handler_type
|
||||
self.instance.responsible.handler.detail = handler_detail
|
||||
self.instance.responsible.handler.save()
|
||||
self.instance.responsible.conservation_office = conservation_office
|
||||
self.instance.responsible.conservation_file_number = conservation_file_number
|
||||
self.instance.responsible.save()
|
||||
@@ -473,6 +530,7 @@ class EditEcoAccountForm(NewEcoAccountForm):
|
||||
self.instance.deductable_surface = surface
|
||||
self.instance.geometry = geometry
|
||||
self.instance.comment = comment
|
||||
self.instance.is_pik = is_pik
|
||||
self.instance.modified = action
|
||||
self.instance.save()
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID, \
|
||||
CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||
from compensation.models import CompensationDocument, EcoAccountDocument
|
||||
from intervention.inputs import CompensationActionTreeCheckboxSelectMultiple, \
|
||||
CompensationStateTreeRadioSelect
|
||||
from konova.contexts import BaseContext
|
||||
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
|
||||
from konova.models import DeadlineType
|
||||
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, ADDED_DEADLINE, \
|
||||
from konova.utils.message_templates import FORM_INVALID, ADDED_COMPENSATION_STATE, \
|
||||
ADDED_COMPENSATION_ACTION, PAYMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
|
||||
|
||||
|
||||
@@ -112,6 +114,7 @@ class EditPaymentModalForm(NewPaymentForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.payment = kwargs.pop("payment", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit payment")
|
||||
form_date = {
|
||||
"amount": self.payment.amount,
|
||||
"due": str(self.payment.due_on),
|
||||
@@ -126,6 +129,7 @@ class EditPaymentModalForm(NewPaymentForm):
|
||||
payment.comment = self.cleaned_data.get("comment", None)
|
||||
payment.save()
|
||||
self.instance.mark_as_edited(self.user, self.request, edit_comment=PAYMENT_EDITED)
|
||||
self.instance.send_data_to_egon()
|
||||
return payment
|
||||
|
||||
|
||||
@@ -153,22 +157,12 @@ class NewStateModalForm(BaseModalForm):
|
||||
What has been on this area before changes/compensations have been applied and what will be the result ('after')?
|
||||
|
||||
"""
|
||||
biotope_type = forms.ModelChoiceField(
|
||||
biotope_type = forms.ChoiceField(
|
||||
label=_("Biotope Type"),
|
||||
label_suffix="",
|
||||
required=True,
|
||||
help_text=_("Select the biotope type"),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_BIOTOPES_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-biotope-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Biotope Type"),
|
||||
}
|
||||
),
|
||||
widget=CompensationStateTreeRadioSelect(),
|
||||
)
|
||||
biotope_extra = forms.ModelMultipleChoiceField(
|
||||
label=_("Biotope additional type"),
|
||||
@@ -206,6 +200,16 @@ class NewStateModalForm(BaseModalForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New state")
|
||||
self.form_caption = _("Insert data for the new state")
|
||||
choices = KonovaCode.objects.filter(
|
||||
code_lists__in=[CODELIST_BIOTOPES_ID],
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
).values_list("id", flat=True)
|
||||
choices = [
|
||||
(choice, choice)
|
||||
for choice in choices
|
||||
]
|
||||
self.fields["biotope_type"].choices = choices
|
||||
|
||||
def save(self, is_before_state: bool = False):
|
||||
state = self.instance.add_state(self, is_before_state)
|
||||
@@ -267,8 +271,10 @@ class EditCompensationStateModalForm(NewStateModalForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.state = kwargs.pop("state", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit state")
|
||||
biotope_type_id = self.state.biotope_type.id if self.state.biotope_type else None
|
||||
form_data = {
|
||||
"biotope_type": self.state.biotope_type,
|
||||
"biotope_type": biotope_type_id,
|
||||
"biotope_extra": self.state.biotope_type_details.all(),
|
||||
"surface": self.state.surface,
|
||||
}
|
||||
@@ -276,7 +282,8 @@ class EditCompensationStateModalForm(NewStateModalForm):
|
||||
|
||||
def save(self, is_before_state: bool = False):
|
||||
state = self.state
|
||||
state.biotope_type = self.cleaned_data.get("biotope_type", None)
|
||||
biotope_type_id = self.cleaned_data.get("biotope_type", None)
|
||||
state.biotope_type = KonovaCode.objects.get(id=biotope_type_id)
|
||||
state.biotope_type_details.set(self.cleaned_data.get("biotope_extra", []))
|
||||
state.surface = self.cleaned_data.get("surface", None)
|
||||
state.save()
|
||||
@@ -379,6 +386,7 @@ class EditDeadlineModalForm(NewDeadlineModalForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.deadline = kwargs.pop("deadline", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit deadline")
|
||||
form_data = {
|
||||
"type": self.deadline.type,
|
||||
"date": str(self.deadline.date),
|
||||
@@ -405,22 +413,13 @@ class NewActionModalForm(BaseModalForm):
|
||||
|
||||
"""
|
||||
from compensation.models import UnitChoices
|
||||
action_type = forms.ModelChoiceField(
|
||||
action_type = forms.MultipleChoiceField(
|
||||
label=_("Action Type"),
|
||||
label_suffix="",
|
||||
required=True,
|
||||
help_text=_("Select the action type"),
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_COMPENSATION_ACTION_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-compensation-action-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Action"),
|
||||
}
|
||||
),
|
||||
help_text=_("An action can consist of multiple different action types. All the selected action types are expected to be performed according to the amount and unit below on this form."),
|
||||
choices=[],
|
||||
widget=CompensationActionTreeCheckboxSelectMultiple(),
|
||||
)
|
||||
action_type_details = forms.ModelMultipleChoiceField(
|
||||
label=_("Action Type detail"),
|
||||
@@ -482,6 +481,16 @@ class NewActionModalForm(BaseModalForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("New action")
|
||||
self.form_caption = _("Insert data for the new action")
|
||||
choices =KonovaCode.objects.filter(
|
||||
code_lists__in=[CODELIST_COMPENSATION_ACTION_ID],
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
).values_list("id", flat=True)
|
||||
choices = [
|
||||
(choice, choice)
|
||||
for choice in choices
|
||||
]
|
||||
self.fields["action_type"].choices = choices
|
||||
|
||||
def save(self):
|
||||
action = self.instance.add_action(self)
|
||||
@@ -495,8 +504,9 @@ class EditCompensationActionModalForm(NewActionModalForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.action = kwargs.pop("action", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit action")
|
||||
form_data = {
|
||||
"action_type": self.action.action_type,
|
||||
"action_type": list(self.action.action_type.values_list("id", flat=True)),
|
||||
"action_type_details": self.action.action_type_details.all(),
|
||||
"amount": self.action.amount,
|
||||
"unit": self.action.unit,
|
||||
@@ -506,7 +516,7 @@ class EditCompensationActionModalForm(NewActionModalForm):
|
||||
|
||||
def save(self):
|
||||
action = self.action
|
||||
action.action_type = self.cleaned_data.get("action_type", None)
|
||||
action.action_type.set(self.cleaned_data.get("action_type", []))
|
||||
action.action_type_details.set(self.cleaned_data.get("action_type_details", []))
|
||||
action.amount = self.cleaned_data.get("amount", None)
|
||||
action.unit = self.cleaned_data.get("unit", None)
|
||||
|
||||
@@ -8,17 +8,6 @@ Created on: 14.10.21
|
||||
from django.db import models
|
||||
|
||||
|
||||
class CompensationActionManager(models.Manager):
|
||||
""" Holds default db fetch setting for this model type
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_related(
|
||||
"action_type",
|
||||
"action_type__parent"
|
||||
)
|
||||
|
||||
|
||||
class CompensationStateManager(models.Manager):
|
||||
""" Holds default db fetch setting for this model type
|
||||
|
||||
|
||||
42
compensation/migrations/0004_auto_20220210_1402.py
Normal file
42
compensation/migrations/0004_auto_20220210_1402.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 3.1.3 on 2022-02-10 13:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_actions(apps, schema_editor):
|
||||
CompensationAction = apps.get_model('compensation', 'CompensationAction')
|
||||
actions = CompensationAction.objects.all()
|
||||
|
||||
for action in actions:
|
||||
action_type = action.action_type or []
|
||||
action.action_type_tmp.set([action_type])
|
||||
action.save()
|
||||
|
||||
if not action.action_type_tmp.count() > 0:
|
||||
raise ValueError("Migration of actions did not work! Stoped before data loss!")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('codelist', '0001_initial'),
|
||||
('compensation', '0003_auto_20220202_0846'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='compensationaction',
|
||||
name='action_type_tmp',
|
||||
field=models.ManyToManyField(blank=True, limit_choices_to={'code_lists__in': [1026], 'is_archived': False, 'is_selectable': True}, related_name='_compensationaction_action_type_+', to='codelist.KonovaCode'),
|
||||
),
|
||||
migrations.RunPython(migrate_actions),
|
||||
migrations.RemoveField(
|
||||
model_name='compensationaction',
|
||||
name='action_type',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='compensationaction',
|
||||
old_name='action_type_tmp',
|
||||
new_name='action_type',
|
||||
)
|
||||
]
|
||||
46
compensation/migrations/0005_auto_20220218_0917.py
Normal file
46
compensation/migrations/0005_auto_20220218_0917.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# Generated by Django 3.1.3 on 2022-02-18 08:17
|
||||
|
||||
from django.db import migrations, models, transaction
|
||||
import django.db.models.deletion
|
||||
|
||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_AFTER_STATE_BIOTOPES_ID
|
||||
|
||||
|
||||
def migrate_entries_974_to_654(apps, schema_editor):
|
||||
CompensationState = apps.get_model("compensation", "CompensationState")
|
||||
KonovaCode = apps.get_model("codelist", "KonovaCode")
|
||||
all_states = CompensationState.objects.all()
|
||||
|
||||
with transaction.atomic():
|
||||
for state in all_states:
|
||||
code_from_654 = KonovaCode.objects.get(
|
||||
short_name=state.biotope_type.short_name,
|
||||
code_lists__in=[CODELIST_BIOTOPES_ID],
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
)
|
||||
state.biotope_type = code_from_654
|
||||
state.save()
|
||||
|
||||
old_list_states = CompensationState.objects.filter(
|
||||
biotope_type__code_lists__in=[CODELIST_AFTER_STATE_BIOTOPES_ID]
|
||||
)
|
||||
if old_list_states.count() > 0:
|
||||
raise Exception("Still unmigrated values!")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('codelist', '0001_initial'),
|
||||
('compensation', '0004_auto_20220210_1402'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_entries_974_to_654),
|
||||
migrations.AlterField(
|
||||
model_name='compensationstate',
|
||||
name='biotope_type',
|
||||
field=models.ForeignKey(blank=True, limit_choices_to={'code_lists__in': [654], 'is_archived': False, 'is_selectable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='codelist.konovacode'),
|
||||
),
|
||||
]
|
||||
19
compensation/migrations/0006_ecoaccount_teams.py
Normal file
19
compensation/migrations/0006_ecoaccount_teams.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.1.3 on 2022-02-18 09:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0003_team'),
|
||||
('compensation', '0005_auto_20220218_0917'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ecoaccount',
|
||||
name='teams',
|
||||
field=models.ManyToManyField(help_text='Teams having access (data shared with)', to='user.Team'),
|
||||
),
|
||||
]
|
||||
23
compensation/migrations/0007_auto_20220531_1245.py
Normal file
23
compensation/migrations/0007_auto_20220531_1245.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.1.3 on 2022-05-31 10:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('compensation', '0006_ecoaccount_teams'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='compensation',
|
||||
name='is_pik',
|
||||
field=models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'", null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ecoaccount',
|
||||
name='is_pik',
|
||||
field=models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'", null=True),
|
||||
),
|
||||
]
|
||||
@@ -10,9 +10,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||
from compensation.managers import CompensationActionManager
|
||||
from konova.models import BaseResource
|
||||
from konova.utils.message_templates import COMPENSATION_ACTION_REMOVED
|
||||
|
||||
|
||||
class UnitChoices(models.TextChoices):
|
||||
@@ -31,10 +29,8 @@ class CompensationAction(BaseResource):
|
||||
"""
|
||||
Compensations include actions like planting trees, refreshing rivers and so on.
|
||||
"""
|
||||
action_type = models.ForeignKey(
|
||||
action_type = models.ManyToManyField(
|
||||
KonovaCode,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
limit_choices_to={
|
||||
"code_lists__in": [CODELIST_COMPENSATION_ACTION_ID],
|
||||
@@ -57,10 +53,8 @@ class CompensationAction(BaseResource):
|
||||
unit = models.CharField(max_length=100, null=True, blank=True, choices=UnitChoices.choices)
|
||||
comment = models.TextField(blank=True, null=True, help_text="Additional comment")
|
||||
|
||||
objects = CompensationActionManager()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.action_type} | {self.amount} {self.unit}"
|
||||
return f"{self.action_type.all()} | {self.amount} {self.unit}"
|
||||
|
||||
@property
|
||||
def unit_humanize(self):
|
||||
|
||||
@@ -8,7 +8,9 @@ Created on: 16.11.21
|
||||
import shutil
|
||||
|
||||
from django.contrib import messages
|
||||
from user.models import User
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from user.models import User, Team
|
||||
from django.db import models, transaction
|
||||
from django.db.models import QuerySet, Sum
|
||||
from django.http import HttpRequest
|
||||
@@ -104,12 +106,12 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
|
||||
with transaction.atomic():
|
||||
user_action = UserActionLogEntry.get_created_action(user)
|
||||
comp_action = CompensationAction.objects.create(
|
||||
action_type=form_data["action_type"],
|
||||
amount=form_data["amount"],
|
||||
unit=form_data["unit"],
|
||||
comment=form_data["comment"],
|
||||
created=user_action,
|
||||
)
|
||||
comp_action.action_type.set(form_data.get("action_type", []))
|
||||
comp_action_details = form_data["action_type_details"]
|
||||
comp_action.action_type_details.set(comp_action_details)
|
||||
self.actions.add(comp_action)
|
||||
@@ -142,8 +144,10 @@ class AbstractCompensation(BaseObject, GeoReferencedMixin):
|
||||
"""
|
||||
form_data = form.cleaned_data
|
||||
with transaction.atomic():
|
||||
biotope_type_id = form_data["biotope_type"]
|
||||
code = KonovaCode.objects.get(id=biotope_type_id)
|
||||
state = CompensationState.objects.create(
|
||||
biotope_type=form_data["biotope_type"],
|
||||
biotope_type=code,
|
||||
surface=form_data["surface"],
|
||||
)
|
||||
state_additional_types = form_data["biotope_extra"]
|
||||
@@ -253,7 +257,22 @@ class CoherenceMixin(models.Model):
|
||||
abstract = True
|
||||
|
||||
|
||||
class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
||||
class PikMixin(models.Model):
|
||||
""" Provides PIK flag as Mixin
|
||||
|
||||
"""
|
||||
is_pik = models.BooleanField(
|
||||
blank=True,
|
||||
null=True,
|
||||
default=False,
|
||||
help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin, PikMixin):
|
||||
"""
|
||||
Regular compensation, linked to an intervention
|
||||
"""
|
||||
@@ -299,7 +318,7 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
||||
# Compensations inherit their shared state from the interventions
|
||||
return self.intervention.is_shared_with(user)
|
||||
|
||||
def share_with(self, user: User):
|
||||
def share_with_user(self, user: User):
|
||||
""" Adds user to list of shared access users
|
||||
|
||||
Args:
|
||||
@@ -308,10 +327,9 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if not self.intervention.is_shared_with(user):
|
||||
self.intervention.users.add(user)
|
||||
self.intervention.users.add(user)
|
||||
|
||||
def share_with_list(self, user_list: list):
|
||||
def share_with_user_list(self, user_list: list):
|
||||
""" Sets the list of shared access users
|
||||
|
||||
Args:
|
||||
@@ -322,6 +340,28 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
||||
"""
|
||||
self.intervention.users.set(user_list)
|
||||
|
||||
def share_with_team(self, team: Team):
|
||||
""" Adds team to list of shared access teams
|
||||
|
||||
Args:
|
||||
team (Team): The team to be added to the object
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.intervention.teams.add(team)
|
||||
|
||||
def share_with_team_list(self, team_list: list):
|
||||
""" Sets the list of shared access teams
|
||||
|
||||
Args:
|
||||
team_list (list): The teams to be added to the object
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.intervention.teams.set(team_list)
|
||||
|
||||
@property
|
||||
def shared_users(self) -> QuerySet:
|
||||
""" Shortcut for fetching the users which have shared access on this object
|
||||
@@ -331,6 +371,15 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
||||
"""
|
||||
return self.intervention.users.all()
|
||||
|
||||
@property
|
||||
def shared_teams(self) -> QuerySet:
|
||||
""" Shortcut for fetching the teams which have shared access on this object
|
||||
|
||||
Returns:
|
||||
users (QuerySet)
|
||||
"""
|
||||
return self.intervention.teams.all()
|
||||
|
||||
def get_documents(self) -> QuerySet:
|
||||
""" Getter for all documents of a compensation
|
||||
|
||||
@@ -388,6 +437,18 @@ class Compensation(AbstractCompensation, CEFMixin, CoherenceMixin):
|
||||
super().set_status_messages(request)
|
||||
return request
|
||||
|
||||
@property
|
||||
def is_recorded(self):
|
||||
""" Getter for record status as property
|
||||
|
||||
Since compensations inherit their record status from their intervention, the intervention's status is being
|
||||
returned
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.intervention.is_recorded
|
||||
|
||||
|
||||
class CompensationDocument(AbstractDocument):
|
||||
"""
|
||||
|
||||
@@ -17,14 +17,13 @@ from django.db.models import Sum, QuerySet
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.managers import EcoAccountManager, EcoAccountDeductionManager
|
||||
from compensation.models.compensation import AbstractCompensation
|
||||
from compensation.models.compensation import AbstractCompensation, PikMixin
|
||||
from compensation.utils.quality import EcoAccountQualityChecker
|
||||
from konova.models import ShareableObjectMixin, RecordableObjectMixin, AbstractDocument, BaseResource, \
|
||||
generate_document_file_upload_path
|
||||
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
|
||||
|
||||
|
||||
class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
|
||||
class EcoAccount(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
|
||||
"""
|
||||
An eco account is a kind of 'prepaid' compensation. It can be compared to an account that already has been filled
|
||||
with some kind of currency. From this account one is able to deduct currency for current projects.
|
||||
|
||||
@@ -12,7 +12,6 @@ from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_BIOTOPES_EXTRA_CODES_ID
|
||||
from compensation.managers import CompensationStateManager
|
||||
from konova.models import UuidModel
|
||||
from konova.utils.message_templates import COMPENSATION_STATE_REMOVED
|
||||
|
||||
|
||||
class CompensationState(UuidModel):
|
||||
|
||||
@@ -5,17 +5,15 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 01.12.20
|
||||
|
||||
"""
|
||||
from user.models import User
|
||||
from konova.utils.message_templates import DATA_IS_UNCHECKED, DATA_CHECKED_ON_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from django.http import HttpRequest
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.timezone import localtime
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.filters import CompensationTableFilter, EcoAccountTableFilter
|
||||
from compensation.models import Compensation, EcoAccount
|
||||
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin
|
||||
import django_tables2 as tables
|
||||
|
||||
@@ -111,16 +109,21 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
"""
|
||||
html = ""
|
||||
checked = value is not None
|
||||
tooltip = _("Not checked yet")
|
||||
tooltip = DATA_IS_UNCHECKED
|
||||
previously_checked = record.intervention.get_last_checked_action()
|
||||
if checked:
|
||||
value = value.timestamp
|
||||
value = localtime(value)
|
||||
checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
|
||||
tooltip = _("Checked on {} by {}").format(checked_on, record.intervention.checked.user)
|
||||
checked_on = value.get_timestamp_str_formatted()
|
||||
tooltip = DATA_CHECKED_ON_TEMPLATE.format(checked_on, record.intervention.checked.user)
|
||||
html += self.render_checked_star(
|
||||
tooltip=tooltip,
|
||||
icn_filled=checked,
|
||||
)
|
||||
if previously_checked and not checked:
|
||||
checked_on = previously_checked.get_timestamp_str_formatted()
|
||||
tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(checked_on, previously_checked.user)
|
||||
html += self.render_previously_checked_star(
|
||||
tooltip=tooltip,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record: Compensation):
|
||||
@@ -134,7 +137,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"gmrkng",
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
@@ -159,9 +162,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
recorded = value is not None
|
||||
tooltip = _("Not recorded yet")
|
||||
if recorded:
|
||||
value = value.timestamp
|
||||
value = localtime(value)
|
||||
on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
|
||||
on = value.get_timestamp_str_formatted()
|
||||
tooltip = _("Recorded on {} by {}").format(on, record.intervention.recorded.user)
|
||||
html += self.render_bookmark(
|
||||
tooltip=tooltip,
|
||||
@@ -179,11 +180,7 @@ class CompensationTable(BaseTable, TableRenderMixin):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if value is None:
|
||||
value = User.objects.none()
|
||||
has_access = value.filter(
|
||||
id=self.user.id
|
||||
).exists()
|
||||
has_access = record.is_shared_with(self.user)
|
||||
|
||||
html = self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
@@ -295,7 +292,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"gmrkng",
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
@@ -320,9 +317,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
checked = value is not None
|
||||
tooltip = _("Not recorded yet. Can not be used for deductions, yet.")
|
||||
if checked:
|
||||
value = value.timestamp
|
||||
value = localtime(value)
|
||||
on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
|
||||
on = value.get_timestamp_str_formatted()
|
||||
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
|
||||
html += self.render_bookmark(
|
||||
tooltip=tooltip,
|
||||
@@ -343,7 +338,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin):
|
||||
html = ""
|
||||
# Do not use value in here, since value does use unprefetched 'users' manager, where record has already
|
||||
# prefetched users data
|
||||
has_access = self.user in record.users.all()
|
||||
has_access = record.is_shared_with(self.user)
|
||||
html += self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
icn_class="fas fa-edit rlp-r-inv" if has_access else "far fa-edit",
|
||||
|
||||
@@ -47,13 +47,15 @@
|
||||
{% for action in actions %}
|
||||
<tr>
|
||||
<td class="">
|
||||
<span>{{ action.action_type }}</span>
|
||||
{% if action.action_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in action.action_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for type in action.action_type.all %}
|
||||
<div> {{type.parent.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.long_name}} </div>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
{% for detail in action.action_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="">{{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }}</td>
|
||||
<td class="">
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
{% for state in after_states %}
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ state.biotope_type }}</span>
|
||||
{% if state.biotope_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||
<td class="align-middle float-right">
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
{% for state in before_states %}
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ state.biotope_type }}</span>
|
||||
{% if state.biotope_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||
<td class="align-middle float-right">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n l10n static fontawesome_5 humanize ksp_filters %}
|
||||
|
||||
{% block head %}
|
||||
|
||||
{% comment %}
|
||||
dal documentation (django-autocomplete-light) states using form.media for adding needed scripts.
|
||||
This does not work properly with modal forms, as the scripts are not loaded properly inside the modal.
|
||||
@@ -38,6 +39,16 @@
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Is PIK' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_pik %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Is CEF compensation' %}</th>
|
||||
<td class="align-middle">
|
||||
@@ -65,6 +76,11 @@
|
||||
<span>
|
||||
{% fa5_icon 'star' 'far' %}
|
||||
</span>
|
||||
{% if last_checked %}
|
||||
<span class="rlp-gd-inv" title="{{last_checked_tooltip}}">
|
||||
{% fa5_icon 'star' 'fas' %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="check-star" title="{% trans 'Checked on '%} {{obj.intervention.checked.timestamp}} {% trans 'by' %} {{obj.intervention.checked.user}}">
|
||||
{% fa5_icon 'star' %}
|
||||
@@ -89,14 +105,24 @@
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Last modified' %}</th>
|
||||
<td class="align-middle">
|
||||
{{obj.modified.timestamp|default_if_none:""|naturalday}}
|
||||
<br>
|
||||
{{obj.modified.user.username}}
|
||||
{% if obj.modified %}
|
||||
{{obj.modified.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.modified.user.username}}
|
||||
{% else %}
|
||||
{{obj.created.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.created.user.username}}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Shared with' %}</th>
|
||||
<td class="align-middle">
|
||||
{% for team in obj.intervention.shared_teams %}
|
||||
{% include 'user/includes/team_data_modal_button.html' %}
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% for user in obj.intervention.users.all %}
|
||||
{% include 'user/includes/contact_modal_button.html' %}
|
||||
{% endfor %}
|
||||
@@ -108,10 +134,12 @@
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/comment_card.html' %}
|
||||
|
||||
@@ -46,13 +46,15 @@
|
||||
{% for action in actions %}
|
||||
<tr>
|
||||
<td class="">
|
||||
<span>{{ action.action_type }}</span>
|
||||
{% if action.action_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in action.action_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for type in action.action_type.all %}
|
||||
<div> {{type.parent.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.long_name}} </div>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
{% for detail in action.action_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="">{{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }}</td>
|
||||
<td class="">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="d-flex justify-content-end">
|
||||
{% if is_default_member and has_access %}
|
||||
{% if is_default_member and obj.recorded %}
|
||||
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'compensation:acc:new-deduction' obj.id %}" title="{% trans 'Add new deduction' %}">
|
||||
{% fa5_icon 'plus' %}
|
||||
{% fa5_icon 'tree' %}
|
||||
@@ -61,7 +61,7 @@
|
||||
<td class="align-middle">{{ deduction.surface|floatformat:2|intcomma }} m²</td>
|
||||
<td class="align-middle">{{ deduction.created.timestamp|default_if_none:""|naturalday}}</td>
|
||||
<td class="align-middle float-right">
|
||||
{% if is_default_member and has_access %}
|
||||
{% if is_default_member and has_access or is_default_member and user in deduction.intervention.shared_users %}
|
||||
<button data-form-url="{% url 'compensation:acc:edit-deduction' deduction.account.id deduction.id %}" class="btn btn-default btn-modal" title="{% trans 'Edit Deduction' %}">
|
||||
{% fa5_icon 'edit' %}
|
||||
</button>
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
{% for state in after_states %}
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ state.biotope_type }}</span>
|
||||
{% if state.biotope_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||
<td class="align-middle float-right">
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
{% for state in before_states %}
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ state.biotope_type }}</span>
|
||||
{% if state.biotope_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||
<td class="align-middle float-right">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n l10n static fontawesome_5 humanize %}
|
||||
|
||||
{% block head %}
|
||||
|
||||
{% comment %}
|
||||
dal documentation (django-autocomplete-light) states using form.media for adding needed scripts.
|
||||
This does not work properly with modal forms, as the scripts are not loaded properly inside the modal.
|
||||
@@ -69,17 +70,37 @@
|
||||
<th scope="row">{% trans 'Action handler' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.handler|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Is PIK' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_pik %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Last modified' %}</th>
|
||||
<td class="align-middle">
|
||||
{{obj.modified.timestamp|default_if_none:""|naturalday}}
|
||||
<br>
|
||||
{{obj.modified.user.username}}
|
||||
{% if obj.modified %}
|
||||
{{obj.modified.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.modified.user.username}}
|
||||
{% else %}
|
||||
{{obj.created.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.created.user.username}}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Shared with' %}</th>
|
||||
<td class="align-middle">
|
||||
{% for team in obj.shared_teams %}
|
||||
{% include 'user/includes/team_data_modal_button.html' %}
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% for user in obj.users.all %}
|
||||
{% include 'user/includes/contact_modal_button.html' %}
|
||||
{% endfor %}
|
||||
@@ -90,10 +111,12 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/comment_card.html' %}
|
||||
|
||||
@@ -20,6 +20,36 @@
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Is PIK' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_pik %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Is CEF' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_cef %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Is coherence keeping' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_coherence_keeping %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Last modified' %}</th>
|
||||
<td class="align-middle">
|
||||
@@ -35,20 +65,15 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'Open in browser' %}</h4>
|
||||
{{ qrcode|safe }}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'View in LANIS' %}</h4>
|
||||
{{ qrcode_lanis|safe }}
|
||||
</div>
|
||||
{% include 'konova/includes/report/qrcodes.html' %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -21,8 +21,14 @@
|
||||
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Action handler' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.handler|default_if_none:""}}</td>
|
||||
<th scope="row">{% trans 'Is PIK' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_pik %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Deductions for' %}</th>
|
||||
@@ -52,20 +58,15 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'Open in browser' %}</h4>
|
||||
{{ qrcode|safe }}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'View in LANIS' %}</h4>
|
||||
{{ qrcode_lanis|safe }}
|
||||
</div>
|
||||
{% include 'konova/includes/report/qrcodes.html' %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -103,7 +103,7 @@ class CompensationViewTestCase(BaseViewTestCase):
|
||||
client = Client()
|
||||
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
self.superuser.groups.set([])
|
||||
self.intervention.share_with_list([self.superuser])
|
||||
self.intervention.share_with_user_list([self.superuser])
|
||||
|
||||
# Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
|
||||
# to a user without access, since the important permissions are missing
|
||||
@@ -143,7 +143,7 @@ class CompensationViewTestCase(BaseViewTestCase):
|
||||
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
self.superuser.groups.set([])
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.intervention.share_with_list([])
|
||||
self.intervention.share_with_user_list([])
|
||||
|
||||
# Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
|
||||
# to a user having shared access, since all important permissions are missing
|
||||
@@ -185,7 +185,7 @@ class CompensationViewTestCase(BaseViewTestCase):
|
||||
group = self.groups.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([group])
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.intervention.share_with_list([self.superuser])
|
||||
self.intervention.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -221,7 +221,7 @@ class CompensationViewTestCase(BaseViewTestCase):
|
||||
group = self.groups.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([group])
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.intervention.share_with_list([])
|
||||
self.intervention.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
|
||||
@@ -25,7 +25,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
||||
super().setUp()
|
||||
|
||||
# Give the user shared access to the dummy intervention -> inherits the access to the compensation
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
|
||||
# Make sure the intervention itself would be fine with valid data
|
||||
self.intervention = self.fill_out_intervention(self.intervention)
|
||||
@@ -50,18 +50,20 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
||||
test_id = self.create_dummy_string()
|
||||
test_title = self.create_dummy_string()
|
||||
test_geom = self.create_dummy_geometry()
|
||||
geom_json = self.create_geojson(test_geom)
|
||||
post_data = {
|
||||
"identifier": test_id,
|
||||
"title": test_title,
|
||||
"geom": test_geom.geojson,
|
||||
"geom": geom_json,
|
||||
"intervention": self.intervention.id,
|
||||
}
|
||||
pre_creation_intervention_log_count = self.intervention.log.count()
|
||||
|
||||
# Preserve the current number of intervention's compensations
|
||||
num_compensations = self.intervention.compensations.count()
|
||||
self.client_user.post(new_url, post_data)
|
||||
response = self.client_user.post(new_url, post_data)
|
||||
|
||||
self.assertEqual(302, response.status_code)
|
||||
self.intervention.refresh_from_db()
|
||||
self.assertEqual(num_compensations + 1, self.intervention.compensations.count())
|
||||
new_compensation = self.intervention.compensations.get(identifier=test_id)
|
||||
@@ -87,10 +89,11 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
||||
test_id = self.create_dummy_string()
|
||||
test_title = self.create_dummy_string()
|
||||
test_geom = self.create_dummy_geometry()
|
||||
geom_json = self.create_geojson(test_geom)
|
||||
post_data = {
|
||||
"identifier": test_id,
|
||||
"title": test_title,
|
||||
"geom": test_geom.geojson,
|
||||
"geom": geom_json,
|
||||
}
|
||||
pre_creation_intervention_log_count = self.intervention.log.count()
|
||||
|
||||
@@ -125,6 +128,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
||||
new_identifier = self.create_dummy_string()
|
||||
new_comment = self.create_dummy_string()
|
||||
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
|
||||
geojson = self.create_geojson(new_geometry)
|
||||
|
||||
check_on_elements = {
|
||||
self.compensation.title: new_title,
|
||||
@@ -139,7 +143,7 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
||||
"title": new_title,
|
||||
"intervention": self.intervention.id, # just keep the intervention as it is
|
||||
"comment": new_comment,
|
||||
"geom": new_geometry.geojson,
|
||||
"geom": geojson,
|
||||
}
|
||||
self.client_user.post(url, post_data)
|
||||
self.compensation.refresh_from_db()
|
||||
@@ -261,3 +265,26 @@ class CompensationWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertIn(recorded, self.compensation.log.all())
|
||||
self.assertEqual(pre_record_log_count + 1, self.compensation.log.count())
|
||||
|
||||
def test_non_editable_after_recording(self):
|
||||
""" Tests that the compensation can not be edited after being recorded
|
||||
|
||||
User must be redirected to another page
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.assertIsNotNone(self.compensation)
|
||||
self.assertFalse(self.compensation.is_recorded)
|
||||
edit_url = reverse("compensation:edit", args=(self.compensation.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertFalse(has_redirect)
|
||||
|
||||
self.compensation.intervention.set_recorded(self.user)
|
||||
self.assertTrue(self.compensation.is_recorded)
|
||||
|
||||
edit_url = reverse("compensation:edit", args=(self.compensation.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertTrue(has_redirect)
|
||||
self.compensation.intervention.set_unrecorded(self.user)
|
||||
|
||||
@@ -78,7 +78,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
|
||||
client = Client()
|
||||
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
self.superuser.groups.set([])
|
||||
self.eco_account.share_with_list([self.superuser])
|
||||
self.eco_account.share_with_user_list([self.superuser])
|
||||
|
||||
# Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
|
||||
# to a user without access, since the important permissions are missing
|
||||
@@ -119,7 +119,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
|
||||
client = Client()
|
||||
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
self.superuser.groups.set([])
|
||||
self.eco_account.share_with_list([])
|
||||
self.eco_account.share_with_user_list([])
|
||||
|
||||
# Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
|
||||
# to a user having shared access, since all important permissions are missing
|
||||
@@ -163,7 +163,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
|
||||
group = self.groups.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([group])
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.eco_account.share_with_list([self.superuser])
|
||||
self.eco_account.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -200,7 +200,7 @@ class EcoAccountViewTestCase(CompensationViewTestCase):
|
||||
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
group = self.groups.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([group])
|
||||
self.eco_account.share_with_list([])
|
||||
self.eco_account.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
|
||||
@@ -27,7 +27,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
# Add user to conservation office group and give shared access to the account
|
||||
self.superuser.groups.add(self.groups.get(name=DEFAULT_GROUP))
|
||||
self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
|
||||
self.eco_account.share_with_list([self.superuser])
|
||||
self.eco_account.share_with_user_list([self.superuser])
|
||||
|
||||
def test_new(self):
|
||||
""" Test the creation of an EcoAccount
|
||||
@@ -40,12 +40,13 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
test_id = self.create_dummy_string()
|
||||
test_title = self.create_dummy_string()
|
||||
test_geom = self.create_dummy_geometry()
|
||||
geom_json = self.create_geojson(test_geom)
|
||||
test_deductable_surface = 1000
|
||||
test_conservation_office = self.get_conservation_office_code()
|
||||
post_data = {
|
||||
"identifier": test_id,
|
||||
"title": test_title,
|
||||
"geom": test_geom.geojson,
|
||||
"geom": geom_json,
|
||||
"deductable_surface": test_deductable_surface,
|
||||
"conservation_office": test_conservation_office.id
|
||||
}
|
||||
@@ -73,7 +74,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.eco_account.share_with(self.superuser)
|
||||
self.eco_account.share_with_user(self.superuser)
|
||||
|
||||
url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
|
||||
pre_edit_log_count = self.eco_account.log.count()
|
||||
@@ -129,7 +130,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
|
||||
"""
|
||||
# Add proper privilege for the user
|
||||
self.eco_account.share_with(self.superuser)
|
||||
self.eco_account.share_with_user(self.superuser)
|
||||
pre_record_log_count = self.eco_account.log.count()
|
||||
|
||||
# Prepare url and form data
|
||||
@@ -178,7 +179,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
|
||||
"""
|
||||
# Give user shared access to the dummy intervention, which will be needed here
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
pre_deduction_acc_log_count = self.eco_account.log.count()
|
||||
pre_deduction_int_log_count = self.intervention.log.count()
|
||||
|
||||
@@ -231,7 +232,9 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
def test_edit_deduction(self):
|
||||
test_surface = self.eco_account.get_available_rest()[0]
|
||||
self.eco_account.set_recorded(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
self.eco_account.refresh_from_db()
|
||||
self.assertTrue(self.superuser, self.intervention.is_shared_with(self.superuser))
|
||||
|
||||
deduction = EcoAccountDeduction.objects.create(
|
||||
intervention=self.intervention,
|
||||
@@ -279,8 +282,8 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
"confirm": True,
|
||||
}
|
||||
|
||||
intervention.share_with(self.superuser)
|
||||
account.share_with(self.superuser)
|
||||
intervention.share_with_user(self.superuser)
|
||||
account.share_with_user(self.superuser)
|
||||
|
||||
pre_edit_intervention_log_count = intervention.log.count()
|
||||
pre_edit_account_log_count = account.log.count()
|
||||
@@ -300,3 +303,27 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertEqual(pre_edit_account_log_count + 1, account.log.count())
|
||||
self.assertEqual(intervention.log.first().action, UserAction.EDITED)
|
||||
self.assertEqual(account.log.first().action, UserAction.EDITED)
|
||||
|
||||
def test_non_editable_after_recording(self):
|
||||
""" Tests that the eco_account can not be edited after being recorded
|
||||
|
||||
User must be redirected to another page
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.assertIsNotNone(self.eco_account)
|
||||
self.assertFalse(self.eco_account.is_recorded)
|
||||
edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertFalse(has_redirect)
|
||||
|
||||
self.eco_account.set_recorded(self.user)
|
||||
self.assertTrue(self.eco_account.is_recorded)
|
||||
|
||||
edit_url = reverse("compensation:acc:edit", args=(self.eco_account.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertTrue(has_redirect)
|
||||
self.eco_account.set_unrecorded(self.user)
|
||||
|
||||
@@ -64,7 +64,7 @@ class PaymentViewTestCase(BaseViewTestCase):
|
||||
client = Client()
|
||||
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
self.superuser.groups.set([])
|
||||
self.intervention.share_with_list([self.superuser])
|
||||
self.intervention.share_with_user_list([self.superuser])
|
||||
|
||||
# Since the user has no groups, it does not matter that data has been shared. There SHOULD not be any difference
|
||||
# to a user without access, since the important permissions are missing
|
||||
@@ -91,7 +91,7 @@ class PaymentViewTestCase(BaseViewTestCase):
|
||||
client.login(username=self.superuser.username, password=self.superuser_pw)
|
||||
self.superuser.groups.set([])
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.intervention.share_with_list([])
|
||||
self.intervention.share_with_user_list([])
|
||||
|
||||
# Since the user has no groups, it does not matter that data is unshared. There SHOULD not be any difference
|
||||
# to a user having shared access, since all important permissions are missing
|
||||
@@ -120,7 +120,7 @@ class PaymentViewTestCase(BaseViewTestCase):
|
||||
group = self.groups.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([group])
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.intervention.share_with_list([self.superuser])
|
||||
self.intervention.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.new_url,
|
||||
@@ -143,7 +143,7 @@ class PaymentViewTestCase(BaseViewTestCase):
|
||||
group = self.groups.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([group])
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.intervention.share_with_list([])
|
||||
self.intervention.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
]
|
||||
|
||||
@@ -21,7 +21,7 @@ class PaymentWorkflowTestCase(BaseWorkflowTestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
# Give the user shared access to the dummy intervention
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
|
||||
self.payment = Payment.objects.get_or_create(
|
||||
intervention=self.intervention,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import Sum
|
||||
from django.http import HttpRequest, JsonResponse
|
||||
from django.shortcuts import render
|
||||
@@ -22,7 +23,7 @@ from konova.utils.message_templates import FORM_INVALID, IDENTIFIER_REPLACED, DA
|
||||
CHECKED_RECORDED_RESET, COMPENSATION_ADDED_TEMPLATE, COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, \
|
||||
COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, \
|
||||
DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, \
|
||||
DEADLINE_EDITED
|
||||
DEADLINE_EDITED, RECORDED_BLOCKS_EDIT, PARAMS_INVALID, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@@ -69,6 +70,19 @@ def new_view(request: HttpRequest, intervention_id: str = None):
|
||||
|
||||
"""
|
||||
template = "compensation/form/view.html"
|
||||
if intervention_id is not None:
|
||||
try:
|
||||
intervention = Intervention.objects.get(id=intervention_id)
|
||||
except ObjectDoesNotExist:
|
||||
messages.error(request, PARAMS_INVALID)
|
||||
return redirect("home")
|
||||
if intervention.is_recorded:
|
||||
messages.info(
|
||||
request,
|
||||
RECORDED_BLOCKS_EDIT
|
||||
)
|
||||
return redirect("intervention:detail", id=intervention_id)
|
||||
|
||||
data_form = NewCompensationForm(request.POST or None, intervention_id=intervention_id)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||
if request.method == "POST":
|
||||
@@ -134,6 +148,13 @@ def edit_view(request: HttpRequest, id: str):
|
||||
template = "compensation/form/view.html"
|
||||
# Get object from db
|
||||
comp = get_object_or_404(Compensation, id=id)
|
||||
if comp.is_recorded:
|
||||
messages.info(
|
||||
request,
|
||||
RECORDED_BLOCKS_EDIT
|
||||
)
|
||||
return redirect("compensation:detail", id=id)
|
||||
|
||||
# Create forms, initialize with values from db/from POST request
|
||||
data_form = EditCompensationForm(request.POST or None, instance=comp)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
|
||||
@@ -196,8 +217,15 @@ def detail_view(request: HttpRequest, id: str):
|
||||
|
||||
request = comp.set_status_messages(request)
|
||||
|
||||
last_checked = comp.intervention.get_last_checked_action()
|
||||
last_checked_tooltip = ""
|
||||
if last_checked:
|
||||
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user)
|
||||
|
||||
context = {
|
||||
"obj": comp,
|
||||
"last_checked": last_checked,
|
||||
"last_checked_tooltip": last_checked_tooltip,
|
||||
"geom_form": geom_form,
|
||||
"parcels": parcels,
|
||||
"has_access": is_data_shared,
|
||||
@@ -596,14 +624,12 @@ def report_view(request: HttpRequest, id: str):
|
||||
instance=comp
|
||||
)
|
||||
parcels = comp.get_underlying_parcels()
|
||||
qrcode_img = generate_qr_code(
|
||||
request.build_absolute_uri(reverse("compensation:report", args=(id,))),
|
||||
10
|
||||
)
|
||||
qrcode_img_lanis = generate_qr_code(
|
||||
comp.get_LANIS_link(),
|
||||
7
|
||||
)
|
||||
|
||||
qrcode_url = request.build_absolute_uri(reverse("compensation:report", args=(id,)))
|
||||
qrcode_img = generate_qr_code(qrcode_url, 10)
|
||||
qrcode_lanis_url = comp.get_LANIS_link()
|
||||
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
|
||||
|
||||
# Order states by surface
|
||||
before_states = comp.before_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||
after_states = comp.after_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||
@@ -611,8 +637,14 @@ def report_view(request: HttpRequest, id: str):
|
||||
|
||||
context = {
|
||||
"obj": comp,
|
||||
"qrcode": qrcode_img,
|
||||
"qrcode_lanis": qrcode_img_lanis,
|
||||
"qrcode": {
|
||||
"img": qrcode_img,
|
||||
"url": qrcode_url,
|
||||
},
|
||||
"qrcode_lanis": {
|
||||
"img": qrcode_img_lanis,
|
||||
"url": qrcode_lanis_url,
|
||||
},
|
||||
"has_access": False, # disables action buttons during rendering
|
||||
"before_states": before_states,
|
||||
"after_states": after_states,
|
||||
|
||||
@@ -35,7 +35,8 @@ from konova.utils.generators import generate_qr_code
|
||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
|
||||
CANCEL_ACC_RECORDED_OR_DEDUCTED, DEDUCTION_REMOVED, DEDUCTION_ADDED, DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, \
|
||||
COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, \
|
||||
DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
|
||||
DEDUCTION_EDITED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, \
|
||||
RECORDED_BLOCKS_EDIT
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@@ -145,6 +146,13 @@ def edit_view(request: HttpRequest, id: str):
|
||||
template = "compensation/form/view.html"
|
||||
# Get object from db
|
||||
acc = get_object_or_404(EcoAccount, id=id)
|
||||
if acc.is_recorded:
|
||||
messages.info(
|
||||
request,
|
||||
RECORDED_BLOCKS_EDIT
|
||||
)
|
||||
return redirect("compensation:acc:detail", id=id)
|
||||
|
||||
# Create forms, initialize with values from db/from POST request
|
||||
data_form = EditEcoAccountForm(request.POST or None, instance=acc)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=acc)
|
||||
@@ -272,7 +280,6 @@ def remove_view(request: HttpRequest, id: str):
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
@shared_access_required(EcoAccount, "id")
|
||||
def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
|
||||
""" Renders a modal view for removing deductions
|
||||
|
||||
@@ -287,6 +294,8 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
|
||||
acc = get_object_or_404(EcoAccount, id=id)
|
||||
try:
|
||||
eco_deduction = acc.deductions.get(id=deduction_id)
|
||||
if not eco_deduction.intervention.is_shared_with(request.user):
|
||||
raise ObjectDoesNotExist()
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404("Unknown deduction")
|
||||
|
||||
@@ -300,7 +309,6 @@ def deduction_remove_view(request: HttpRequest, id: str, deduction_id: str):
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
@shared_access_required(EcoAccount, "id")
|
||||
def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str):
|
||||
""" Renders a modal view for editing deductions
|
||||
|
||||
@@ -315,6 +323,8 @@ def deduction_edit_view(request: HttpRequest, id: str, deduction_id: str):
|
||||
acc = get_object_or_404(EcoAccount, id=id)
|
||||
try:
|
||||
eco_deduction = acc.deductions.get(id=deduction_id)
|
||||
if not eco_deduction.intervention.is_shared_with(request.user):
|
||||
raise ObjectDoesNotExist
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404("Unknown deduction")
|
||||
|
||||
@@ -679,7 +689,6 @@ def remove_document_view(request: HttpRequest, id: str, doc_id: str):
|
||||
|
||||
@login_required
|
||||
@default_group_required
|
||||
@shared_access_required(EcoAccount, "id")
|
||||
def new_deduction_view(request: HttpRequest, id: str):
|
||||
""" Renders a modal form view for creating deductions
|
||||
|
||||
@@ -691,6 +700,8 @@ def new_deduction_view(request: HttpRequest, id: str):
|
||||
|
||||
"""
|
||||
acc = get_object_or_404(EcoAccount, id=id)
|
||||
if not acc.recorded:
|
||||
raise Http404()
|
||||
form = NewDeductionModalForm(request.POST or None, instance=acc, request=request)
|
||||
return form.process_request(
|
||||
request,
|
||||
@@ -728,18 +739,16 @@ def report_view(request:HttpRequest, id: str):
|
||||
instance=acc
|
||||
)
|
||||
parcels = acc.get_underlying_parcels()
|
||||
qrcode_img = generate_qr_code(
|
||||
request.build_absolute_uri(reverse("ema:report", args=(id,))),
|
||||
10
|
||||
)
|
||||
qrcode_img_lanis = generate_qr_code(
|
||||
acc.get_LANIS_link(),
|
||||
7
|
||||
)
|
||||
|
||||
qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
|
||||
qrcode_img = generate_qr_code(qrcode_url, 10)
|
||||
qrcode_lanis_url = acc.get_LANIS_link()
|
||||
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
|
||||
|
||||
# Order states by surface
|
||||
before_states = acc.before_states.all().order_by("-surface").select_related("biotope_type__parent")
|
||||
after_states = acc.after_states.all().order_by("-surface").select_related("biotope_type__parent")
|
||||
actions = acc.actions.all().select_related("action_type__parent")
|
||||
actions = acc.actions.all().prefetch_related("action_type__parent")
|
||||
|
||||
# Reduce amount of db fetched data to the bare minimum we need in the template (deduction's intervention id and identifier)
|
||||
deductions = acc.deductions.all()\
|
||||
@@ -749,8 +758,14 @@ def report_view(request:HttpRequest, id: str):
|
||||
|
||||
context = {
|
||||
"obj": acc,
|
||||
"qrcode": qrcode_img,
|
||||
"qrcode_lanis": qrcode_img_lanis,
|
||||
"qrcode": {
|
||||
"img": qrcode_img,
|
||||
"url": qrcode_url,
|
||||
},
|
||||
"qrcode_lanis": {
|
||||
"img": qrcode_img_lanis,
|
||||
"url": qrcode_lanis_url,
|
||||
},
|
||||
"has_access": False, # disables action buttons during rendering
|
||||
"before_states": before_states,
|
||||
"after_states": after_states,
|
||||
@@ -793,7 +808,7 @@ def share_view(request: HttpRequest, id: str, token: str):
|
||||
request,
|
||||
_("{} has been shared with you").format(obj.identifier)
|
||||
)
|
||||
obj.share_with(user)
|
||||
obj.share_with_user(user)
|
||||
return redirect("compensation:acc:detail", id=id)
|
||||
else:
|
||||
messages.error(
|
||||
|
||||
35
ema/forms.py
35
ema/forms.py
@@ -12,14 +12,15 @@ from django.db import transaction
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin
|
||||
from compensation.forms.forms import AbstractCompensationForm, CompensationResponsibleFormMixin, \
|
||||
PikCompensationFormMixin
|
||||
from ema.models import Ema, EmaDocument
|
||||
from intervention.models import Responsibility
|
||||
from intervention.models import Responsibility, Handler
|
||||
from konova.forms import SimpleGeomForm, NewDocumentModalForm
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
|
||||
class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin, PikCompensationFormMixin):
|
||||
""" Form for creating new EMA objects.
|
||||
|
||||
Inherits basic form fields from AbstractCompensationForm and additional from CompensationResponsibleFormMixin.
|
||||
@@ -31,7 +32,9 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
"title",
|
||||
"conservation_office",
|
||||
"conservation_file_number",
|
||||
"handler",
|
||||
"is_pik",
|
||||
"handler_type",
|
||||
"handler_detail",
|
||||
"comment",
|
||||
]
|
||||
|
||||
@@ -53,9 +56,11 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
handler_type = self.cleaned_data.get("handler_type", None)
|
||||
handler_detail = self.cleaned_data.get("handler_detail", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
is_pik = self.cleaned_data.get("is_pik", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
|
||||
# Create log entry
|
||||
@@ -63,6 +68,10 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
# Process the geometry form
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
handler = Handler.objects.create(
|
||||
type=handler_type,
|
||||
detail=handler_detail
|
||||
)
|
||||
responsible = Responsibility.objects.create(
|
||||
handler=handler,
|
||||
conservation_file_number=conservation_file_number,
|
||||
@@ -77,10 +86,11 @@ class NewEmaForm(AbstractCompensationForm, CompensationResponsibleFormMixin):
|
||||
created=action,
|
||||
geometry=geometry,
|
||||
comment=comment,
|
||||
is_pik=is_pik,
|
||||
)
|
||||
|
||||
# Add the creating user to the list of shared users
|
||||
acc.share_with(user)
|
||||
acc.share_with_user(user)
|
||||
|
||||
# Add the log entry to the main objects log list
|
||||
acc.log.add(action)
|
||||
@@ -105,10 +115,12 @@ class EditEmaForm(NewEmaForm):
|
||||
form_data = {
|
||||
"identifier": self.instance.identifier,
|
||||
"title": self.instance.title,
|
||||
"handler": self.instance.responsible.handler,
|
||||
"handler_type": self.instance.responsible.handler.type,
|
||||
"handler_detail": self.instance.responsible.handler.detail,
|
||||
"conservation_office": self.instance.responsible.conservation_office,
|
||||
"conservation_file_number": self.instance.responsible.conservation_file_number,
|
||||
"comment": self.instance.comment,
|
||||
"is_pik": self.instance.is_pik,
|
||||
}
|
||||
disabled_fields = []
|
||||
self.load_initial_data(
|
||||
@@ -121,10 +133,12 @@ class EditEmaForm(NewEmaForm):
|
||||
# Fetch data from cleaned POST values
|
||||
identifier = self.cleaned_data.get("identifier", None)
|
||||
title = self.cleaned_data.get("title", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
handler_type = self.cleaned_data.get("handler_type", None)
|
||||
handler_detail = self.cleaned_data.get("handler_detail", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
comment = self.cleaned_data.get("comment", None)
|
||||
is_pik = self.cleaned_data.get("is_pik", None)
|
||||
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_edited_action(user)
|
||||
@@ -132,7 +146,9 @@ class EditEmaForm(NewEmaForm):
|
||||
geometry = geom_form.save(action)
|
||||
|
||||
# Update responsible data
|
||||
self.instance.responsible.handler = handler
|
||||
self.instance.responsible.handler.type = handler_type
|
||||
self.instance.responsible.handler.detail = handler_detail
|
||||
self.instance.responsible.handler.save()
|
||||
self.instance.responsible.conservation_office = conservation_office
|
||||
self.instance.responsible.conservation_file_number = conservation_file_number
|
||||
self.instance.responsible.save()
|
||||
@@ -142,6 +158,7 @@ class EditEmaForm(NewEmaForm):
|
||||
self.instance.title = title
|
||||
self.instance.geometry = geometry
|
||||
self.instance.comment = comment
|
||||
self.instance.is_pik = is_pik
|
||||
self.instance.modified = action
|
||||
self.instance.save()
|
||||
|
||||
|
||||
19
ema/migrations/0003_ema_teams.py
Normal file
19
ema/migrations/0003_ema_teams.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.1.3 on 2022-02-18 09:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0003_team'),
|
||||
('ema', '0002_auto_20220114_0936'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ema',
|
||||
name='teams',
|
||||
field=models.ManyToManyField(help_text='Teams having access (data shared with)', to='user.Team'),
|
||||
),
|
||||
]
|
||||
18
ema/migrations/0004_ema_is_pik.py
Normal file
18
ema/migrations/0004_ema_is_pik.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.1.3 on 2022-05-31 10:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ema', '0003_ema_teams'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ema',
|
||||
name='is_pik',
|
||||
field=models.BooleanField(blank=True, default=False, help_text="Flag if compensation is a 'Produktonsintegrierte Kompensation'", null=True),
|
||||
),
|
||||
]
|
||||
@@ -13,15 +13,14 @@ from django.db.models import QuerySet
|
||||
from django.http import HttpRequest
|
||||
from django.urls import reverse
|
||||
|
||||
from compensation.models import AbstractCompensation
|
||||
from compensation.models import AbstractCompensation, PikMixin
|
||||
from ema.managers import EmaManager
|
||||
from ema.utils.quality import EmaQualityChecker
|
||||
from konova.models import AbstractDocument, generate_document_file_upload_path, RecordableObjectMixin, ShareableObjectMixin
|
||||
from konova.settings import DEFAULT_SRID_RLP, LANIS_LINK_TEMPLATE
|
||||
from konova.utils.message_templates import DATA_UNSHARED_EXPLANATION, DOCUMENT_REMOVED_TEMPLATE
|
||||
|
||||
|
||||
class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin):
|
||||
class Ema(AbstractCompensation, ShareableObjectMixin, RecordableObjectMixin, PikMixin):
|
||||
"""
|
||||
EMA = Ersatzzahlungsmaßnahme
|
||||
(compensation actions from payments)
|
||||
|
||||
@@ -104,7 +104,7 @@ class EmaTable(BaseTable, TableRenderMixin):
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"gmrkng",
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
@@ -115,7 +115,6 @@ class EmaTable(BaseTable, TableRenderMixin):
|
||||
)
|
||||
return html
|
||||
|
||||
|
||||
def render_r(self, value, record: Ema):
|
||||
""" Renders the registered column for a EMA
|
||||
|
||||
@@ -130,9 +129,7 @@ class EmaTable(BaseTable, TableRenderMixin):
|
||||
recorded = value is not None
|
||||
tooltip = _("Not recorded yet")
|
||||
if recorded:
|
||||
value = value.timestamp
|
||||
value = localtime(value)
|
||||
on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
|
||||
on = value.get_timestamp_str_formatted()
|
||||
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
|
||||
html += self.render_bookmark(
|
||||
tooltip=tooltip,
|
||||
@@ -151,9 +148,7 @@ class EmaTable(BaseTable, TableRenderMixin):
|
||||
|
||||
"""
|
||||
html = ""
|
||||
has_access = value.filter(
|
||||
id=self.user.id
|
||||
).exists()
|
||||
has_access = record.is_shared_with(self.user)
|
||||
|
||||
html += self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
|
||||
@@ -44,13 +44,15 @@
|
||||
{% for action in obj.actions.all %}
|
||||
<tr>
|
||||
<td class="">
|
||||
<span>{{ action.action_type }}</span>
|
||||
{% if action.action_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in action.action_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for type in action.action_type.all %}
|
||||
<div> {{type.parent.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.parent.long_name}} {% fa5_icon 'angle-right' %} {{type.long_name}} </div>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
{% for detail in action.action_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No action type details' %}">{% trans 'No action type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="">{{ action.amount|floatformat:2|intcomma }} {{ action.unit_humanize }}</td>
|
||||
<td class="">
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
{% for state in after_states %}
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ state.biotope_type }}</span>
|
||||
{% if state.biotope_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||
<td class="align-middle float-right">
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
{% for state in before_states %}
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ state.biotope_type }}</span>
|
||||
{% if state.biotope_type_details.count > 0 %}
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<span>{{ state.biotope_type.parent.long_name }} {% fa5_icon 'angle-right' %} {{ state.biotope_type.long_name }} ({{state.biotope_type.short_name}})</span>
|
||||
<br>
|
||||
{% for detail in state.biotope_type_details.all %}
|
||||
<span class="badge badge-pill rlp-r" title="{{detail}}">{{detail.long_name}}</span>
|
||||
{% empty %}
|
||||
<span class="badge badge-pill rlp-r-outline" title="{% trans 'No biotope type details' %}">{% trans 'No biotope type details' %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ state.surface|floatformat:2 }} m²</td>
|
||||
<td class="align-middle float-right">
|
||||
|
||||
@@ -56,25 +56,38 @@
|
||||
<th scope="row">{% trans 'Action handler' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.handler|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Is PIK' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_pik %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Last modified' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.modified %}
|
||||
{{obj.modified.timestamp|default_if_none:""|naturalday}}
|
||||
{{obj.modified.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.modified.user.username}}
|
||||
{% else %}
|
||||
{{obj.created.timestamp|default_if_none:""|naturalday}}
|
||||
{{obj.created.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.created.user.username}}
|
||||
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Shared with' %}</th>
|
||||
<td class="align-middle">
|
||||
{% for user in obj.users.all %}
|
||||
{% for team in obj.shared_teams %}
|
||||
{% include 'user/includes/team_data_modal_button.html' %}
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% for user in obj.user.all %}
|
||||
{% include 'user/includes/contact_modal_button.html' %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
@@ -84,10 +97,12 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/comment_card.html' %}
|
||||
|
||||
@@ -21,8 +21,14 @@
|
||||
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Action handler' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.handler|default_if_none:""}}</td>
|
||||
<th scope="row">{% trans 'Is PIK' %}</th>
|
||||
<td class="align-middle">
|
||||
{% if obj.is_pik %}
|
||||
{% trans 'Yes' %}
|
||||
{% else %}
|
||||
{% trans 'No' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Last modified' %}</th>
|
||||
@@ -39,20 +45,15 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'Open in browser' %}</h4>
|
||||
{{ qrcode|safe }}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'View in LANIS' %}</h4>
|
||||
{{ qrcode_lanis|safe }}
|
||||
</div>
|
||||
{% include 'konova/includes/report/qrcodes.html' %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -79,7 +79,9 @@ class EmaViewTestCase(CompensationViewTestCase):
|
||||
# Create log entry
|
||||
action = UserActionLogEntry.get_created_action(self.superuser)
|
||||
# Create responsible data object
|
||||
responsibility_data = Responsibility.objects.create()
|
||||
responsibility_data = Responsibility.objects.create(
|
||||
handler=self.handler
|
||||
)
|
||||
geometry = Geometry.objects.create()
|
||||
self.ema = Ema.objects.create(
|
||||
identifier="TEST",
|
||||
@@ -110,7 +112,7 @@ class EmaViewTestCase(CompensationViewTestCase):
|
||||
|
||||
# Sharing does not have any effect in here, since the default group will prohibit further functionality access
|
||||
# to this user
|
||||
self.ema.share_with_list([self.superuser])
|
||||
self.ema.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -160,7 +162,7 @@ class EmaViewTestCase(CompensationViewTestCase):
|
||||
|
||||
# Sharing does not have any effect in here, since the default group will prohibit further functionality access
|
||||
# to this user
|
||||
self.ema.share_with_list([])
|
||||
self.ema.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -203,7 +205,7 @@ class EmaViewTestCase(CompensationViewTestCase):
|
||||
groups = self.groups.filter(Q(name=ETS_GROUP)|Q(name=DEFAULT_GROUP))
|
||||
self.superuser.groups.set(groups)
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.ema.share_with_list([self.superuser])
|
||||
self.ema.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -243,7 +245,7 @@ class EmaViewTestCase(CompensationViewTestCase):
|
||||
groups = self.groups.filter(Q(name=ETS_GROUP)|Q(name=DEFAULT_GROUP))
|
||||
self.superuser.groups.set(groups)
|
||||
# Sharing is inherited by base intervention for compensation. Therefore configure the interventions share state
|
||||
self.ema.share_with_list([])
|
||||
self.ema.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
|
||||
@@ -41,11 +41,12 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
||||
test_id = self.create_dummy_string()
|
||||
test_title = self.create_dummy_string()
|
||||
test_geom = self.create_dummy_geometry()
|
||||
geom_json = self.create_geojson(test_geom)
|
||||
test_conservation_office = self.get_conservation_office_code()
|
||||
post_data = {
|
||||
"identifier": test_id,
|
||||
"title": test_title,
|
||||
"geom": test_geom.geojson,
|
||||
"geom": geom_json,
|
||||
"conservation_office": test_conservation_office.id
|
||||
}
|
||||
self.client_user.post(new_url, post_data)
|
||||
@@ -117,6 +118,32 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertEqual(pre_edit_log_count + 1, self.ema.log.count())
|
||||
self.assertEqual(self.ema.log.first().action, UserAction.EDITED)
|
||||
|
||||
def test_non_editable_after_recording(self):
|
||||
""" Tests that the EMA can not be edited after being recorded
|
||||
|
||||
User must be redirected to another page
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.superuser.groups.add(self.groups.get(name=ETS_GROUP))
|
||||
self.assertIsNotNone(self.ema)
|
||||
self.ema.share_with_user(self.superuser)
|
||||
self.assertFalse(self.ema.is_recorded)
|
||||
edit_url = reverse("ema:edit", args=(self.ema.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertFalse(has_redirect)
|
||||
|
||||
self.ema.set_recorded(self.superuser)
|
||||
self.assertTrue(self.ema.is_recorded)
|
||||
|
||||
edit_url = reverse("ema:edit", args=(self.ema.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertTrue(has_redirect)
|
||||
self.ema.set_unrecorded(self.superuser)
|
||||
|
||||
def test_recordability(self):
|
||||
"""
|
||||
This tests if the recordability of the Ema is triggered by the quality of it's data (e.g. not all fields filled)
|
||||
|
||||
35
ema/views.py
35
ema/views.py
@@ -26,7 +26,7 @@ from konova.utils.generators import generate_qr_code
|
||||
from konova.utils.message_templates import IDENTIFIER_REPLACED, FORM_INVALID, DATA_UNSHARED, DATA_UNSHARED_EXPLANATION, \
|
||||
DOCUMENT_ADDED, COMPENSATION_STATE_REMOVED, COMPENSATION_STATE_ADDED, COMPENSATION_ACTION_REMOVED, \
|
||||
COMPENSATION_ACTION_ADDED, DEADLINE_ADDED, DEADLINE_REMOVED, DOCUMENT_EDITED, COMPENSATION_STATE_EDITED, \
|
||||
COMPENSATION_ACTION_EDITED, DEADLINE_EDITED
|
||||
COMPENSATION_ACTION_EDITED, DEADLINE_EDITED, RECORDED_BLOCKS_EDIT
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@@ -213,6 +213,13 @@ def edit_view(request: HttpRequest, id: str):
|
||||
template = "compensation/form/view.html"
|
||||
# Get object from db
|
||||
ema = get_object_or_404(Ema, id=id)
|
||||
if ema.is_recorded:
|
||||
messages.info(
|
||||
request,
|
||||
RECORDED_BLOCKS_EDIT
|
||||
)
|
||||
return redirect("ema:detail", id=id)
|
||||
|
||||
# Create forms, initialize with values from db/from POST request
|
||||
data_form = EditEmaForm(request.POST or None, instance=ema)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
|
||||
@@ -563,14 +570,12 @@ def report_view(request:HttpRequest, id: str):
|
||||
instance=ema,
|
||||
)
|
||||
parcels = ema.get_underlying_parcels()
|
||||
qrcode_img = generate_qr_code(
|
||||
request.build_absolute_uri(reverse("ema:report", args=(id,))),
|
||||
10
|
||||
)
|
||||
qrcode_img_lanis = generate_qr_code(
|
||||
ema.get_LANIS_link(),
|
||||
7
|
||||
)
|
||||
|
||||
qrcode_url = request.build_absolute_uri(reverse("ema:report", args=(id,)))
|
||||
qrcode_img = generate_qr_code(qrcode_url, 10)
|
||||
qrcode_lanis_url = ema.get_LANIS_link()
|
||||
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
|
||||
|
||||
# Order states by surface
|
||||
before_states = ema.before_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||
after_states = ema.after_states.all().order_by("-surface").prefetch_related("biotope_type")
|
||||
@@ -578,8 +583,14 @@ def report_view(request:HttpRequest, id: str):
|
||||
|
||||
context = {
|
||||
"obj": ema,
|
||||
"qrcode": qrcode_img,
|
||||
"qrcode_lanis": qrcode_img_lanis,
|
||||
"qrcode": {
|
||||
"img": qrcode_img,
|
||||
"url": qrcode_url
|
||||
},
|
||||
"qrcode_lanis": {
|
||||
"img": qrcode_img_lanis,
|
||||
"url": qrcode_lanis_url
|
||||
},
|
||||
"has_access": False, # disables action buttons during rendering
|
||||
"before_states": before_states,
|
||||
"after_states": after_states,
|
||||
@@ -621,7 +632,7 @@ def share_view(request: HttpRequest, id: str, token: str):
|
||||
request,
|
||||
_("{} has been shared with you").format(obj.identifier)
|
||||
)
|
||||
obj.share_with(user)
|
||||
obj.share_with_user(user)
|
||||
return redirect("ema:detail", id=id)
|
||||
else:
|
||||
messages.error(
|
||||
|
||||
@@ -25,12 +25,14 @@ class InterventionAdmin(BaseObjectAdmin):
|
||||
"checked",
|
||||
"recorded",
|
||||
"users",
|
||||
"geometry",
|
||||
]
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
return super().get_readonly_fields(request, obj) + [
|
||||
"checked",
|
||||
"recorded",
|
||||
"geometry",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_PROCESS_TYPE_ID, CODELIST_LAW_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_HANDLER_ID
|
||||
from intervention.inputs import GenerateInput
|
||||
from intervention.models import Intervention, Legal, Responsibility
|
||||
from intervention.models import Intervention, Legal, Responsibility, Handler
|
||||
from konova.forms import BaseForm, SimpleGeomForm
|
||||
from user.models import UserActionLogEntry
|
||||
|
||||
@@ -138,12 +138,29 @@ class NewInterventionForm(BaseForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
handler = forms.CharField(
|
||||
label=_("Intervention handler"),
|
||||
handler_type = forms.ModelChoiceField(
|
||||
label=_("Intervention handler type"),
|
||||
label_suffix="",
|
||||
help_text=_("What type of handler is responsible for the intervention?"),
|
||||
required=False,
|
||||
queryset=KonovaCode.objects.filter(
|
||||
is_archived=False,
|
||||
is_leaf=True,
|
||||
code_lists__in=[CODELIST_HANDLER_ID],
|
||||
),
|
||||
widget=autocomplete.ModelSelect2(
|
||||
url="codes-handler-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
}
|
||||
),
|
||||
)
|
||||
handler_detail = forms.CharField(
|
||||
label=_("Intervention handler detail"),
|
||||
label_suffix="",
|
||||
max_length=255,
|
||||
required=False,
|
||||
help_text=_("Who performs the intervention"),
|
||||
help_text=_("Detail input on the handler"),
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
"placeholder": _("Company Mustermann"),
|
||||
@@ -151,6 +168,7 @@ class NewInterventionForm(BaseForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
registration_date = forms.DateField(
|
||||
label=_("Registration date"),
|
||||
label_suffix=_(""),
|
||||
@@ -198,6 +216,10 @@ class NewInterventionForm(BaseForm):
|
||||
identifier = tmp_intervention.generate_new_identifier()
|
||||
self.initialize_form_field("identifier", identifier)
|
||||
|
||||
def is_valid(self):
|
||||
super_valid_result = super().is_valid()
|
||||
return super_valid_result
|
||||
|
||||
def save(self, user: User, geom_form: SimpleGeomForm):
|
||||
with transaction.atomic():
|
||||
# Fetch data from cleaned POST values
|
||||
@@ -205,7 +227,8 @@ class NewInterventionForm(BaseForm):
|
||||
title = self.cleaned_data.get("title", None)
|
||||
_type = self.cleaned_data.get("type", None)
|
||||
laws = self.cleaned_data.get("laws", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
handler_type = self.cleaned_data.get("handler_type", None)
|
||||
handler_detail = self.cleaned_data.get("handler_detail", None)
|
||||
registration_office = self.cleaned_data.get("registration_office", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
conservation_file_number = self.cleaned_data.get("conservation_file_number", None)
|
||||
@@ -226,6 +249,10 @@ class NewInterventionForm(BaseForm):
|
||||
# Then add the M2M laws to the object
|
||||
legal_data.laws.set(laws)
|
||||
|
||||
handler = Handler.objects.create(
|
||||
type=handler_type,
|
||||
detail=handler_detail
|
||||
)
|
||||
# Create responsible data object
|
||||
responsibility_data = Responsibility.objects.create(
|
||||
registration_office=registration_office,
|
||||
@@ -253,7 +280,7 @@ class NewInterventionForm(BaseForm):
|
||||
intervention.log.add(action)
|
||||
|
||||
# Add the performing user as the first user having access to the data
|
||||
intervention.share_with(user)
|
||||
intervention.share_with_user(user)
|
||||
return intervention
|
||||
|
||||
|
||||
@@ -284,7 +311,8 @@ class EditInterventionForm(NewInterventionForm):
|
||||
"title": self.instance.title,
|
||||
"type": self.instance.legal.process_type,
|
||||
"laws": list(self.instance.legal.laws.values_list("id", flat=True)),
|
||||
"handler": self.instance.responsible.handler,
|
||||
"handler_type": self.instance.responsible.handler.type,
|
||||
"handler_detail": self.instance.responsible.handler.detail,
|
||||
"registration_office": self.instance.responsible.registration_office,
|
||||
"registration_file_number": self.instance.responsible.registration_file_number,
|
||||
"conservation_office": self.instance.responsible.conservation_office,
|
||||
@@ -313,7 +341,8 @@ class EditInterventionForm(NewInterventionForm):
|
||||
title = self.cleaned_data.get("title", None)
|
||||
process_type = self.cleaned_data.get("type", None)
|
||||
laws = self.cleaned_data.get("laws", None)
|
||||
handler = self.cleaned_data.get("handler", None)
|
||||
handler_type = self.cleaned_data.get("handler_type", None)
|
||||
handler_detail = self.cleaned_data.get("handler_detail", None)
|
||||
registration_office = self.cleaned_data.get("registration_office", None)
|
||||
registration_file_number = self.cleaned_data.get("registration_file_number", None)
|
||||
conservation_office = self.cleaned_data.get("conservation_office", None)
|
||||
@@ -328,7 +357,10 @@ class EditInterventionForm(NewInterventionForm):
|
||||
self.instance.legal.laws.set(laws)
|
||||
self.instance.legal.save()
|
||||
|
||||
self.instance.responsible.handler = handler
|
||||
self.instance.responsible.handler.type = handler_type
|
||||
self.instance.responsible.handler.detail = handler_detail
|
||||
self.instance.responsible.handler.save()
|
||||
|
||||
self.instance.responsible.registration_office = registration_office
|
||||
self.instance.responsible.registration_file_number = registration_file_number
|
||||
self.instance.responsible.conservation_office = conservation_office
|
||||
|
||||
@@ -7,11 +7,11 @@ Created on: 27.09.21
|
||||
"""
|
||||
from dal import autocomplete
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models.fields.files import FieldFile
|
||||
|
||||
from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
|
||||
REVOCATION_EDITED, FILE_TYPE_UNSUPPORTED, FILE_SIZE_TOO_LARGE
|
||||
from user.models import User, UserActionLogEntry
|
||||
REVOCATION_EDITED, ENTRY_REMOVE_MISSING_PERMISSION
|
||||
from user.models import User, Team
|
||||
from user.models import UserActionLogEntry
|
||||
from django.db import transaction
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -37,7 +37,21 @@ class ShareModalForm(BaseModalForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
user_select = forms.ModelMultipleChoiceField(
|
||||
teams = forms.ModelMultipleChoiceField(
|
||||
label=_("Add team to share with"),
|
||||
label_suffix="",
|
||||
help_text=_("Multiple selection possible - You can only select teams which do not already have access."),
|
||||
required=False,
|
||||
queryset=Team.objects.all(),
|
||||
widget=autocomplete.ModelSelect2Multiple(
|
||||
url="share-team-autocomplete",
|
||||
attrs={
|
||||
"data-placeholder": _("Click for selection"),
|
||||
"data-minimum-input-length": 3,
|
||||
},
|
||||
),
|
||||
)
|
||||
users = forms.ModelMultipleChoiceField(
|
||||
label=_("Add user to share with"),
|
||||
label_suffix="",
|
||||
help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."),
|
||||
@@ -49,21 +63,8 @@ class ShareModalForm(BaseModalForm):
|
||||
"data-placeholder": _("Click for selection"),
|
||||
"data-minimum-input-length": 3,
|
||||
},
|
||||
forward=["users"]
|
||||
),
|
||||
)
|
||||
users = forms.MultipleChoiceField(
|
||||
label=_("Shared with"),
|
||||
label_suffix="",
|
||||
required=True,
|
||||
help_text=_("Remove check to remove access for this user"),
|
||||
widget=forms.CheckboxSelectMultiple(
|
||||
attrs={
|
||||
"class": "list-unstyled",
|
||||
}
|
||||
),
|
||||
choices=[]
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -77,6 +78,48 @@ class ShareModalForm(BaseModalForm):
|
||||
|
||||
self._init_fields()
|
||||
|
||||
def _user_team_valid(self):
|
||||
""" Checks whether users and teams have been removed by the user and if the user is allowed to do so or not
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
users = self.cleaned_data.get("users", User.objects.none())
|
||||
teams = self.cleaned_data.get("teams", Team.objects.none())
|
||||
|
||||
_is_valid = True
|
||||
if is_default_group_only(self.user):
|
||||
shared_users = self.instance.shared_users
|
||||
shared_teams = self.instance.shared_teams
|
||||
|
||||
shared_users_are_removed = not set(shared_users).issubset(users)
|
||||
shared_teams_are_removed = not set(shared_teams).issubset(teams)
|
||||
|
||||
if shared_users_are_removed:
|
||||
self.add_error(
|
||||
"users",
|
||||
ENTRY_REMOVE_MISSING_PERMISSION
|
||||
)
|
||||
_is_valid = False
|
||||
if shared_teams_are_removed:
|
||||
self.add_error(
|
||||
"teams",
|
||||
ENTRY_REMOVE_MISSING_PERMISSION
|
||||
)
|
||||
_is_valid = False
|
||||
return _is_valid
|
||||
|
||||
def is_valid(self):
|
||||
""" Extended validity check
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
super_valid = super().is_valid()
|
||||
user_team_valid = self._user_team_valid()
|
||||
_is_valid = super_valid and user_team_valid
|
||||
return _is_valid
|
||||
|
||||
def _init_fields(self):
|
||||
""" Wraps initializing of fields
|
||||
|
||||
@@ -91,34 +134,14 @@ class ShareModalForm(BaseModalForm):
|
||||
self.share_link
|
||||
)
|
||||
|
||||
# Initialize users field
|
||||
# Disable field if user is not in registration or conservation group
|
||||
if is_default_group_only(self.request.user):
|
||||
self.disable_form_field("users")
|
||||
|
||||
self._add_user_choices_to_field()
|
||||
|
||||
def _add_user_choices_to_field(self):
|
||||
""" Transforms the instance's sharing users into a list for the form field
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
users = self.instance.users.all()
|
||||
choices = []
|
||||
for n in users:
|
||||
choices.append(
|
||||
(n.id, n.username)
|
||||
)
|
||||
self.fields["users"].choices = choices
|
||||
u_ids = list(users.values_list("id", flat=True))
|
||||
self.initialize_form_field(
|
||||
"users",
|
||||
u_ids
|
||||
)
|
||||
form_data = {
|
||||
"teams": self.instance.teams.all(),
|
||||
"users": self.instance.users.all(),
|
||||
}
|
||||
self.load_initial_data(form_data)
|
||||
|
||||
def save(self):
|
||||
self.instance.update_sharing_user(self)
|
||||
self.instance.update_shared_access(self)
|
||||
|
||||
|
||||
class NewRevocationModalForm(BaseModalForm):
|
||||
@@ -182,6 +205,7 @@ class EditRevocationModalForm(NewRevocationModalForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.revocation = kwargs.pop("revocation", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit revocation")
|
||||
try:
|
||||
doc = self.revocation.document.file
|
||||
except ObjectDoesNotExist:
|
||||
@@ -267,7 +291,9 @@ class CheckModalForm(BaseModalForm):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
comps = self.instance.compensations.all()
|
||||
comps = self.instance.compensations.filter(
|
||||
deleted=None,
|
||||
)
|
||||
comps_valid = True
|
||||
for comp in comps:
|
||||
checker = comp.quality_check()
|
||||
@@ -401,13 +427,22 @@ class NewDeductionModalForm(BaseModalForm):
|
||||
"""
|
||||
super_result = super().is_valid()
|
||||
acc = self.cleaned_data["account"]
|
||||
intervention = self.cleaned_data["intervention"]
|
||||
objects_valid = True
|
||||
|
||||
if not acc.recorded:
|
||||
self.add_error(
|
||||
"account",
|
||||
_("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
|
||||
)
|
||||
return False
|
||||
objects_valid = False
|
||||
|
||||
if intervention.is_recorded:
|
||||
self.add_error(
|
||||
"intervention",
|
||||
_("Intervention {} is currently recorded. To change any data on it, the entry must be unrecorded.").format(intervention.identifier)
|
||||
)
|
||||
objects_valid = False
|
||||
|
||||
rest_surface = self._get_available_surface(acc)
|
||||
form_surface = float(self.cleaned_data["surface"])
|
||||
@@ -421,7 +456,7 @@ class NewDeductionModalForm(BaseModalForm):
|
||||
format_german_float(rest_surface),
|
||||
),
|
||||
)
|
||||
return is_valid_surface and super_result
|
||||
return is_valid_surface and objects_valid and super_result
|
||||
|
||||
def __create_deduction(self):
|
||||
""" Creates the deduction
|
||||
@@ -452,6 +487,7 @@ class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.deduction = kwargs.pop("deduction", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit Deduction")
|
||||
form_data = {
|
||||
"account": self.deduction.account,
|
||||
"intervention": self.deduction.intervention,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from django import forms
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID
|
||||
|
||||
|
||||
class DummyFilterInput(forms.HiddenInput):
|
||||
@@ -30,3 +32,112 @@ class GenerateInput(forms.TextInput):
|
||||
|
||||
"""
|
||||
template_name = "konova/widgets/generate-content-input.html"
|
||||
|
||||
|
||||
class TreeCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
|
||||
""" Provides multiple selection of parent-child data
|
||||
|
||||
"""
|
||||
template_name = "konova/widgets/tree/checkbox/checkbox-tree-select.html"
|
||||
|
||||
class meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class TreeRadioSelect(forms.RadioSelect):
|
||||
""" Provides single selection of parent-child data
|
||||
|
||||
"""
|
||||
template_name = "konova/widgets/tree/radio/radio-tree-select.html"
|
||||
|
||||
class meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class KonovaCodeTreeCheckboxSelectMultiple(TreeCheckboxSelectMultiple):
|
||||
""" Provides multiple selection of KonovaCodes
|
||||
|
||||
"""
|
||||
filter = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.code_list = kwargs.pop("code_list", None)
|
||||
self.filter = kwargs.pop("filter", {})
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
context = super().get_context(name, value, attrs)
|
||||
codes = KonovaCode.objects.filter(
|
||||
**self.filter,
|
||||
)
|
||||
codes = [
|
||||
parent_code.add_children()
|
||||
for parent_code in codes
|
||||
]
|
||||
context["codes"] = codes
|
||||
return context
|
||||
|
||||
|
||||
class KonovaCodeTreeRadioSelect(TreeRadioSelect):
|
||||
""" Provides single selection of KonovaCode
|
||||
|
||||
"""
|
||||
filter = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.code_list = kwargs.pop("code_list", None)
|
||||
self.filter = kwargs.pop("filter", {})
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
context = super().get_context(name, value, attrs)
|
||||
codes = KonovaCode.objects.filter(
|
||||
**self.filter,
|
||||
)
|
||||
codes = [
|
||||
parent_code.add_children()
|
||||
for parent_code in codes
|
||||
]
|
||||
context["codes"] = codes
|
||||
return context
|
||||
|
||||
|
||||
class CompensationActionTreeCheckboxSelectMultiple(KonovaCodeTreeCheckboxSelectMultiple):
|
||||
""" Provides multiple selection of CompensationActions
|
||||
|
||||
"""
|
||||
filter = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.filter = {
|
||||
"code_lists__in": [CODELIST_COMPENSATION_ACTION_ID],
|
||||
"parent": None,
|
||||
}
|
||||
|
||||
|
||||
class CompensationStateTreeRadioSelect(KonovaCodeTreeRadioSelect):
|
||||
""" Provides single selection of CompensationState
|
||||
|
||||
"""
|
||||
filter = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.filter = {
|
||||
"code_lists__in": [CODELIST_BIOTOPES_ID],
|
||||
"parent": None,
|
||||
"is_archived": False,
|
||||
}
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
context = super().get_context(name, value, attrs)
|
||||
codes = KonovaCode.objects.filter(
|
||||
**self.filter,
|
||||
)
|
||||
codes = [
|
||||
parent_code.add_children("short_name")
|
||||
for parent_code in codes
|
||||
]
|
||||
context["codes"] = codes
|
||||
return context
|
||||
19
intervention/migrations/0003_intervention_teams.py
Normal file
19
intervention/migrations/0003_intervention_teams.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 3.1.3 on 2022-02-18 09:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('user', '0003_team'),
|
||||
('intervention', '0002_auto_20220114_0936'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='intervention',
|
||||
name='teams',
|
||||
field=models.ManyToManyField(help_text='Teams having access (data shared with)', to='user.Team'),
|
||||
),
|
||||
]
|
||||
64
intervention/migrations/0004_auto_20220303_0956.py
Normal file
64
intervention/migrations/0004_auto_20220303_0956.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# Generated by Django 3.1.3 on 2022-03-03 08:56
|
||||
from django.db import migrations, models, transaction
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
def migrate_handler(apps, schema_editor):
|
||||
KonovaCode = apps.get_model('codelist', 'KonovaCode')
|
||||
|
||||
Responsibility = apps.get_model('intervention', 'Responsibility')
|
||||
Handler = apps.get_model('intervention', 'Handler')
|
||||
all_responsibs = Responsibility.objects.all()
|
||||
|
||||
if all_responsibs.exists():
|
||||
handler_tmp_code = KonovaCode.objects.get(
|
||||
atom_id=710185,
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
for resp in all_responsibs:
|
||||
handler_old = resp.handler_old
|
||||
handler = Handler.objects.create(
|
||||
type=handler_tmp_code,
|
||||
detail=handler_old
|
||||
)
|
||||
resp.handler = handler
|
||||
resp.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('codelist', '0001_initial'),
|
||||
('intervention', '0003_intervention_teams'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Handler',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('detail', models.CharField(blank=True, max_length=500, null=True)),
|
||||
('type', models.ForeignKey(blank=True, limit_choices_to={'code_lists__in': [1052], 'is_archived': False, 'is_selectable': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='codelist.konovacode')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='responsibility',
|
||||
old_name='handler',
|
||||
new_name='handler_old',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='responsibility',
|
||||
name='handler',
|
||||
field=models.ForeignKey(blank=True, help_text="Refers to 'Eingriffsverursacher' or 'Maßnahmenträger'", null=True, on_delete=django.db.models.deletion.SET_NULL, to='intervention.handler'),
|
||||
),
|
||||
migrations.RunPython(migrate_handler),
|
||||
migrations.RemoveField(
|
||||
model_name='responsibility',
|
||||
name='handler_old'
|
||||
),
|
||||
]
|
||||
@@ -13,6 +13,7 @@ from django.db.models.fields.files import FieldFile
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from intervention.tasks import celery_export_to_egon
|
||||
from user.models import User
|
||||
from django.db import models, transaction
|
||||
from django.db.models import QuerySet
|
||||
@@ -131,6 +132,16 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
|
||||
self.add_log_entry_to_compensations(log_entry)
|
||||
return log_entry
|
||||
|
||||
def send_data_to_egon(self):
|
||||
""" Performs the export to rabbitmq of this intervention's data
|
||||
|
||||
FOLLOWING BACKWARDS COMPATIBILITY LOGIC
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
celery_export_to_egon.delay(self.id)
|
||||
|
||||
def set_recorded(self, user: User) -> UserActionLogEntry:
|
||||
log_entry = super().set_recorded(user)
|
||||
self.add_log_entry_to_compensations(log_entry)
|
||||
@@ -171,6 +182,8 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
|
||||
intervention=self,
|
||||
)
|
||||
self.mark_as_edited(user, form.request, edit_comment=PAYMENT_ADDED)
|
||||
|
||||
self.send_data_to_egon()
|
||||
return pay
|
||||
|
||||
def add_revocation(self, form):
|
||||
@@ -335,6 +348,7 @@ class Intervention(BaseObject, ShareableObjectMixin, RecordableObjectMixin, Chec
|
||||
with transaction.atomic():
|
||||
payment.delete()
|
||||
self.mark_as_edited(user, request=form.request, edit_comment=PAYMENT_REMOVED)
|
||||
self.send_data_to_egon()
|
||||
|
||||
|
||||
class InterventionDocument(AbstractDocument):
|
||||
|
||||
@@ -6,10 +6,42 @@ Created on: 15.11.21
|
||||
|
||||
"""
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID
|
||||
from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID, CODELIST_REGISTRATION_OFFICE_ID, \
|
||||
CODELIST_HANDLER_ID
|
||||
from konova.models import UuidModel
|
||||
from konova.utils.message_templates import UNKNOWN, NO_DETAILS
|
||||
|
||||
|
||||
class Handler(UuidModel):
|
||||
""" The handler of an entry
|
||||
|
||||
Refers to 'Eingriffsverursacher' or 'Maßnahmenträger'
|
||||
|
||||
"""
|
||||
type = models.ForeignKey(
|
||||
KonovaCode,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
limit_choices_to={
|
||||
"code_lists__in": [CODELIST_HANDLER_ID],
|
||||
"is_selectable": True,
|
||||
"is_archived": False,
|
||||
}
|
||||
)
|
||||
detail = models.CharField(
|
||||
max_length=500,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
detail = self.detail or NO_DETAILS
|
||||
_type = self.type.long_name if self.type is not None else UNKNOWN
|
||||
return f'{_type}, {detail}'
|
||||
|
||||
|
||||
class Responsibility(UuidModel):
|
||||
@@ -43,11 +75,17 @@ class Responsibility(UuidModel):
|
||||
}
|
||||
)
|
||||
conservation_file_number = models.CharField(max_length=1000, blank=True, null=True)
|
||||
handler = models.CharField(max_length=500, null=True, blank=True, help_text="Refers to 'Eingriffsverursacher' or 'Maßnahmenträger'")
|
||||
handler = models.ForeignKey(
|
||||
Handler,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Refers to 'Eingriffsverursacher' or 'Maßnahmenträger'",
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "ZB: {} | ETS: {} | Handler: {}".format(
|
||||
self.registration_office,
|
||||
self.conservation_office,
|
||||
self.handler
|
||||
str(self.handler)
|
||||
)
|
||||
|
||||
@@ -6,4 +6,11 @@ Created on: 30.11.20
|
||||
|
||||
"""
|
||||
INTERVENTION_IDENTIFIER_LENGTH = 6
|
||||
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
|
||||
INTERVENTION_IDENTIFIER_TEMPLATE = "EIV-{}"
|
||||
|
||||
# EGON connection settings via rabbitmq
|
||||
# NEEDED FOR BACKWARDS COMPATIBILITY
|
||||
EGON_RABBITMQ_HOST = "CHANGE_ME"
|
||||
EGON_RABBITMQ_PORT = "CHANGE_ME"
|
||||
EGON_RABBITMQ_USER = "CHANGE_ME"
|
||||
EGON_RABBITMQ_PW = "CHANGE_ME"
|
||||
|
||||
@@ -9,12 +9,11 @@ from django.http import HttpRequest
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.timezone import localtime
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from intervention.filters import InterventionTableFilter
|
||||
from intervention.models import Intervention
|
||||
from konova.sub_settings.django_settings import DEFAULT_DATE_TIME_FORMAT, DEFAULT_DATE_FORMAT
|
||||
from konova.utils.message_templates import DATA_CHECKED_ON_TEMPLATE, DATA_IS_UNCHECKED, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from konova.utils.tables import BaseTable, TableRenderMixin
|
||||
import django_tables2 as tables
|
||||
|
||||
@@ -108,16 +107,21 @@ class InterventionTable(BaseTable, TableRenderMixin):
|
||||
"""
|
||||
html = ""
|
||||
checked = value is not None
|
||||
tooltip = _("Not checked yet")
|
||||
previously_checked = record.get_last_checked_action()
|
||||
tooltip = DATA_IS_UNCHECKED
|
||||
if checked:
|
||||
value = value.timestamp
|
||||
value = localtime(value)
|
||||
checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
|
||||
tooltip = _("Checked on {} by {}").format(checked_on, record.checked.user)
|
||||
checked_on = value.get_timestamp_str_formatted()
|
||||
tooltip = DATA_CHECKED_ON_TEMPLATE.format(checked_on, record.checked.user)
|
||||
html += self.render_checked_star(
|
||||
tooltip=tooltip,
|
||||
icn_filled=checked,
|
||||
)
|
||||
if previously_checked and not checked:
|
||||
checked_on = previously_checked.get_timestamp_str_formatted()
|
||||
tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(checked_on, previously_checked.user)
|
||||
html += self.render_previously_checked_star(
|
||||
tooltip=tooltip,
|
||||
)
|
||||
return format_html(html)
|
||||
|
||||
def render_d(self, value, record: Intervention):
|
||||
@@ -131,7 +135,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
|
||||
|
||||
"""
|
||||
parcels = value.get_underlying_parcels().values_list(
|
||||
"gmrkng",
|
||||
"parcel_group__name",
|
||||
flat=True
|
||||
).distinct()
|
||||
html = render_to_string(
|
||||
@@ -156,9 +160,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
|
||||
checked = value is not None
|
||||
tooltip = _("Not recorded yet")
|
||||
if checked:
|
||||
value = value.timestamp
|
||||
value = localtime(value)
|
||||
on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
|
||||
on = value.get_timestamp_str_formatted()
|
||||
tooltip = _("Recorded on {} by {}").format(on, record.recorded.user)
|
||||
html += self.render_bookmark(
|
||||
tooltip=tooltip,
|
||||
@@ -177,9 +179,7 @@ class InterventionTable(BaseTable, TableRenderMixin):
|
||||
|
||||
"""
|
||||
html = ""
|
||||
has_access = value.filter(
|
||||
id=self.user.id
|
||||
).exists()
|
||||
has_access = record.is_shared_with(self.user)
|
||||
|
||||
html += self.render_icn(
|
||||
tooltip=_("Full access granted") if has_access else _("Access not granted"),
|
||||
|
||||
18
intervention/tasks.py
Normal file
18
intervention/tasks.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 21.03.22
|
||||
|
||||
"""
|
||||
from celery import shared_task
|
||||
|
||||
from intervention.utils.egon_export import EgonExporter
|
||||
|
||||
|
||||
@shared_task
|
||||
def celery_export_to_egon(intervention_id: str):
|
||||
from intervention.models import Intervention
|
||||
intervention = Intervention.objects.get(id=intervention_id)
|
||||
egon_exporter = EgonExporter(intervention)
|
||||
egon_exporter.export_to_rabbitmq()
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n l10n static fontawesome_5 humanize %}
|
||||
{% load i18n l10n static fontawesome_5 %}
|
||||
|
||||
{% block head %}
|
||||
{% comment %}
|
||||
@@ -70,6 +70,11 @@
|
||||
<span>
|
||||
{% fa5_icon 'star' 'far' %}
|
||||
</span>
|
||||
{% if last_checked %}
|
||||
<span class="rlp-gd-inv" title="{{last_checked_tooltip}}">
|
||||
{% fa5_icon 'star' 'fas' %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="check-star" title="{% trans 'Checked on '%} {{obj.checked.timestamp}} {% trans 'by' %} {{obj.checked.user}}">
|
||||
{% fa5_icon 'star' %}
|
||||
@@ -106,14 +111,24 @@
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Last modified' %}</th>
|
||||
<td class="align-middle">
|
||||
{{obj.created.timestamp|default_if_none:""|naturalday}}
|
||||
<br>
|
||||
{{obj.created.user.username}}
|
||||
{% if obj.modified %}
|
||||
{{obj.modified.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.modified.user.username}}
|
||||
{% else %}
|
||||
{{obj.created.timestamp|default_if_none:""}}
|
||||
<br>
|
||||
{{obj.created.user.username}}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Shared with' %}</th>
|
||||
<td class="align-middle">
|
||||
{% for team in obj.shared_teams %}
|
||||
{% include 'user/includes/team_data_modal_button.html' %}
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% for user in obj.users.all %}
|
||||
{% include 'user/includes/contact_modal_button.html' %}
|
||||
{% endfor %}
|
||||
@@ -124,10 +139,12 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/comment_card.html' %}
|
||||
|
||||
@@ -41,10 +41,6 @@
|
||||
<th scope="row">{% trans 'Conservation office file number' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.conservation_file_number|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Intervention handler' %}</th>
|
||||
<td class="align-middle">{{obj.responsible.handler|default_if_none:""}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'Compensations' %}</th>
|
||||
<td class="align-middle">
|
||||
@@ -98,20 +94,15 @@
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-6">
|
||||
<div class="row">
|
||||
{% include 'map/geom_form.html' %}
|
||||
<div class="col-sm-12">
|
||||
{% include 'map/geom_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% include 'konova/includes/parcels.html' %}
|
||||
{% include 'konova/includes/parcels/parcels.html' %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'Open in browser' %}</h4>
|
||||
{{ qrcode|safe }}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-6 col-lg-6">
|
||||
<h4>{% trans 'View in LANIS' %}</h4>
|
||||
{{ qrcode_lanis|safe }}
|
||||
</div>
|
||||
{% include 'konova/includes/report/qrcodes.html' %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -144,7 +144,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
# Add user to default group
|
||||
default_group = Group.objects.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([default_group])
|
||||
self.intervention.share_with_list([self.superuser])
|
||||
self.intervention.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -190,7 +190,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
# Add user to default group
|
||||
default_group = Group.objects.get(name=DEFAULT_GROUP)
|
||||
self.superuser.groups.set([default_group])
|
||||
self.intervention.share_with_list([])
|
||||
self.intervention.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -236,7 +236,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
# Add user to zb group
|
||||
zb_group = self.groups.get(name=ZB_GROUP)
|
||||
self.superuser.groups.set([zb_group])
|
||||
self.intervention.share_with_list([self.superuser])
|
||||
self.intervention.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -282,7 +282,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
# Add user to zb group
|
||||
zb_group = self.groups.get(name=ZB_GROUP)
|
||||
self.superuser.groups.set([zb_group])
|
||||
self.intervention.share_with_list([])
|
||||
self.intervention.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -328,7 +328,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
# Add user to ets group
|
||||
ets_group = Group.objects.get(name=ETS_GROUP)
|
||||
self.superuser.groups.set([ets_group])
|
||||
self.intervention.share_with_list([self.superuser])
|
||||
self.intervention.share_with_user_list([self.superuser])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
@@ -374,7 +374,7 @@ class InterventionViewTestCase(BaseViewTestCase):
|
||||
# Add user to default group
|
||||
ets_group = Group.objects.get(name=ETS_GROUP)
|
||||
self.superuser.groups.set([ets_group])
|
||||
self.intervention.share_with_list([])
|
||||
self.intervention.share_with_user_list([])
|
||||
|
||||
success_urls = [
|
||||
self.index_url,
|
||||
|
||||
@@ -30,7 +30,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
super().setUp()
|
||||
# Recreate a new (bare minimum) intervention before each test
|
||||
self.intervention = self.create_dummy_intervention()
|
||||
self.intervention.share_with(self.superuser)
|
||||
self.intervention.share_with_user(self.superuser)
|
||||
|
||||
def test_new(self):
|
||||
"""
|
||||
@@ -46,6 +46,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
test_id = self.create_dummy_string()
|
||||
test_title = self.create_dummy_string()
|
||||
test_geom = self.create_dummy_geometry()
|
||||
geom_json = self.create_geojson(test_geom)
|
||||
|
||||
new_url = reverse("intervention:new", args=())
|
||||
|
||||
@@ -59,7 +60,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
post_data = {
|
||||
"identifier": test_id,
|
||||
"title": test_title,
|
||||
"geom": test_geom.geojson,
|
||||
"geom": geom_json,
|
||||
}
|
||||
response = self.client_user.post(
|
||||
new_url,
|
||||
@@ -89,6 +90,30 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertIn(self.superuser, obj.users.all())
|
||||
self.assertEqual(1, obj.users.count())
|
||||
|
||||
def test_non_editable_after_recording(self):
|
||||
""" Tests that the intervention can not be edited after being recorded
|
||||
|
||||
User must be redirected to another page
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self.assertIsNotNone(self.intervention)
|
||||
self.assertFalse(self.intervention.is_recorded)
|
||||
edit_url = reverse("intervention:edit", args=(self.intervention.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertFalse(has_redirect)
|
||||
|
||||
self.intervention.set_recorded(self.user)
|
||||
self.assertTrue(self.intervention.is_recorded)
|
||||
|
||||
edit_url = reverse("intervention:edit", args=(self.intervention.id,))
|
||||
response = self.client_user.get(edit_url)
|
||||
has_redirect = response.status_code == 302
|
||||
self.assertTrue(has_redirect)
|
||||
self.intervention.set_unrecorded(self.user)
|
||||
|
||||
def test_checkability(self):
|
||||
""" Tests that the intervention can only be checked if all required data has been added
|
||||
|
||||
@@ -303,7 +328,6 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
Reasons for failing are:
|
||||
* EcoAccount does not provide enough 'deductable_surface'
|
||||
* EcoAccount is not recorded (not "approved"), yet
|
||||
* EcoAccount is not shared with performing user
|
||||
|
||||
Args:
|
||||
new_url (str): The url to send the post data to
|
||||
@@ -315,7 +339,6 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
# Before running fail positive tests, we need to have an account in a (normally) fine working state
|
||||
self.assertIsNotNone(self.eco_account.recorded) # -> is recorded
|
||||
self.assertGreater(self.eco_account.deductable_surface, test_surface) # -> has more deductable surface than we need
|
||||
self.assertIn(self.superuser, self.eco_account.users.all()) # -> is shared with the performing user
|
||||
|
||||
# Count the number of already existing deductions in total and for the account for later comparison
|
||||
num_deductions = self.eco_account.deductions.count()
|
||||
@@ -333,20 +356,11 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
self.assertEqual(num_deductions, self.eco_account.deductions.count())
|
||||
self.assertEqual(num_deductions_total, EcoAccountDeduction.objects.count())
|
||||
|
||||
# Now restore the deductable surface to a valid size back again but remove the user from the shared list
|
||||
# Now restore the deductable surface to a valid size back again
|
||||
self.eco_account.deductable_surface = test_surface + 100.00
|
||||
self.eco_account.share_with_list([])
|
||||
self.eco_account.save()
|
||||
|
||||
# Now perform the (expected) failing request (again)
|
||||
self.client_user.post(new_url, post_data)
|
||||
|
||||
# Expect no changes at all, since the account is not shared
|
||||
self.assertEqual(num_deductions, self.eco_account.deductions.count())
|
||||
self.assertEqual(num_deductions_total, EcoAccountDeduction.objects.count())
|
||||
|
||||
# Restore the sharing but remove the recording state
|
||||
self.eco_account.share_with_list([self.superuser])
|
||||
# Remove the recording state
|
||||
self.eco_account.recorded.delete()
|
||||
self.eco_account.refresh_from_db()
|
||||
self.eco_account.save()
|
||||
@@ -376,7 +390,7 @@ class InterventionWorkflowTestCase(BaseWorkflowTestCase):
|
||||
if self.eco_account.recorded is None:
|
||||
rec_action = UserActionLogEntry.get_recorded_action(self.superuser)
|
||||
self.eco_account.recorded = rec_action
|
||||
self.eco_account.share_with_list([self.superuser])
|
||||
self.eco_account.share_with_user_list([self.superuser])
|
||||
self.eco_account.save()
|
||||
num_all_deducs = EcoAccountDeduction.objects.count()
|
||||
|
||||
|
||||
250
intervention/utils/egon_export.py
Normal file
250
intervention/utils/egon_export.py
Normal file
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
Author: Michel Peltriaux
|
||||
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.03.22
|
||||
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
|
||||
import pika
|
||||
import xmltodict
|
||||
from django.db.models import Sum
|
||||
|
||||
from intervention.settings import EGON_RABBITMQ_HOST, EGON_RABBITMQ_USER, EGON_RABBITMQ_PW, EGON_RABBITMQ_PORT
|
||||
from konova.sub_settings.django_settings import DEFAULT_DATE_FORMAT
|
||||
|
||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||
|
||||
|
||||
class EgonExporter:
|
||||
"""
|
||||
EGON is the payment management system of SNU RLP. Due to compatibility reasons we need to provide the old style
|
||||
of data transmission between KSP and EGON:
|
||||
1. Create GML from intervention object
|
||||
2. Send created GML to the appropriate RabbitMQ channel
|
||||
"""
|
||||
intervention = None
|
||||
gml_builder = None
|
||||
|
||||
def __init__(self, intervention):
|
||||
self.intervention = intervention
|
||||
self.gml_builder = EgonGmlBuilder(intervention)
|
||||
|
||||
def export_to_rabbitmq(self):
|
||||
""" Sends the exporter gml to message broker rabbitmq to be fetched by EGON application from there
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
msg = {
|
||||
"nachricht": self.gml_builder.gml,
|
||||
}
|
||||
msg = json.dumps(msg)
|
||||
print(msg)
|
||||
credentials = pika.PlainCredentials(EGON_RABBITMQ_USER, EGON_RABBITMQ_PW)
|
||||
params = pika.ConnectionParameters(
|
||||
EGON_RABBITMQ_HOST,
|
||||
EGON_RABBITMQ_PORT,
|
||||
"/",
|
||||
credentials
|
||||
)
|
||||
conn = pika.BlockingConnection(params)
|
||||
channel = conn.channel()
|
||||
channel.basic_publish(
|
||||
exchange="",
|
||||
routing_key="KSP_EGON",
|
||||
body=msg.encode("utf-8"),
|
||||
)
|
||||
conn.close()
|
||||
|
||||
|
||||
class EgonGmlBuilder:
|
||||
"""
|
||||
Creates the GML for EGON export
|
||||
"""
|
||||
intervention = None
|
||||
gml = None
|
||||
|
||||
def __init__(self, intervention):
|
||||
self.intervention = intervention
|
||||
self.gml = self.build_gml()
|
||||
|
||||
def _gen_flurstuecksKennzeichen(self, parcel):
|
||||
""" Generates oneo:flurstuecksKennzeichen to provide backwards compatibility
|
||||
|
||||
Args:
|
||||
parcel (Parcel): The requested parcel
|
||||
|
||||
Returns:
|
||||
str
|
||||
"""
|
||||
gmrkng_code = "{0:06d}".format(int(parcel.parcel_group.key) or 0)
|
||||
flr_code = "{0:03d}".format(int(parcel.flr or 0))
|
||||
flrstckzhlr_code = "{0:05d}".format(int(parcel.flrstck_zhlr or 0))
|
||||
flrstcknnr_code = "{0:06d}".format(int(parcel.flrstck_nnr or 0))
|
||||
return gmrkng_code + flr_code + flrstckzhlr_code + flrstcknnr_code
|
||||
|
||||
def _sum_all_payments(self):
|
||||
all_payments = self.intervention.payments.aggregate(
|
||||
summed=Sum("amount")
|
||||
)["summed"]
|
||||
return all_payments
|
||||
|
||||
def _gen_kompensationsArt(self) -> (str, int):
|
||||
comp_type = "Ersatzzahlung"
|
||||
comp_type_code = 774898901
|
||||
if self.intervention.compensations.exists():
|
||||
comp_type += " und Kompensation"
|
||||
comp_type_code = 771655351
|
||||
return comp_type, comp_type_code
|
||||
|
||||
def _gen_geometry_list(self):
|
||||
geom = self.intervention.geometry.geom
|
||||
geom.transform(DEFAULT_SRID_RLP)
|
||||
geoms_list = [
|
||||
{
|
||||
"gml:Polygon": {
|
||||
"gml:exterior": {
|
||||
"gml:LinearRing": {
|
||||
"gml:posList": " ".join([f"{str(coord[0])} {str(coord[1])}" for coord in coords[0]])
|
||||
}
|
||||
}
|
||||
}
|
||||
} for coords in geom.coords
|
||||
]
|
||||
return geoms_list
|
||||
|
||||
def _gen_raumreferenz(self):
|
||||
parcels = self.intervention.get_underlying_parcels()
|
||||
spatial_reference_list = [
|
||||
{
|
||||
"oneo:datumAbgleich": None,
|
||||
"oneo:ortsangabe": {
|
||||
"oneo:Ortsangaben": {
|
||||
"oneo:kreisSchluessel": {
|
||||
"xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/588/{parcel.district.key}",
|
||||
},
|
||||
"oneo:gemeindeSchluessel": {
|
||||
"xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/910/{parcel.municipal.key}",
|
||||
},
|
||||
"oneo:verbandsgemeindeSchluessel": {
|
||||
"xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/589/{None}",
|
||||
},
|
||||
"oneo:flurstuecksKennzeichen": self._gen_flurstuecksKennzeichen(parcel),
|
||||
}
|
||||
},
|
||||
} for parcel in parcels
|
||||
]
|
||||
return spatial_reference_list
|
||||
|
||||
def _gen_foto(self):
|
||||
revoc_docs, regular_docs = self.intervention.get_documents()
|
||||
docs_list = [
|
||||
{
|
||||
"oneo:Foto": {
|
||||
"oneo:aufnahmezeitpunkt": doc.date_of_creation.strftime(DEFAULT_DATE_FORMAT),
|
||||
"oneo:bemerkung": doc.comment,
|
||||
"oneo:fotoverweis": base64.b64encode(doc.file.read()).decode("utf-8"),
|
||||
"oneo:dateiname": doc.title,
|
||||
"oneo:hauptfoto": False,
|
||||
}
|
||||
} for doc in regular_docs
|
||||
]
|
||||
return docs_list
|
||||
|
||||
def build_gml(self):
|
||||
comp_type, comp_type_code = self._gen_kompensationsArt()
|
||||
payment = self.intervention.payments.first()
|
||||
payment_date = None
|
||||
if payment is not None:
|
||||
payment_date = payment.due_on
|
||||
payment_date = payment_date.strftime(DEFAULT_DATE_FORMAT)
|
||||
|
||||
cons_office = self.intervention.responsible.conservation_office
|
||||
reg_office = self.intervention.responsible.registration_office
|
||||
law = self.intervention.legal.laws.first()
|
||||
process_type = self.intervention.legal.process_type
|
||||
handler = self.intervention.responsible.handler
|
||||
reg_date = self.intervention.legal.registration_date
|
||||
bind_date = self.intervention.legal.binding_date
|
||||
|
||||
xml_dict = {
|
||||
"wfs:FeatureCollection": {
|
||||
"@xmlns:wfs": "http://www.opengis.net/wfs",
|
||||
"@xmlns:xlink": "http://www.w3.org/1999/xlink",
|
||||
"@xmlns:oneo": "http://www.osiris-projekt.rlp.de/oneo",
|
||||
"@xmlns:gmlexr": "http://www.opengis.net/gml/3.3/exr",
|
||||
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
||||
"@xmlns:gml": "http://www.opengis.net/gml/3.2",
|
||||
"oneo:Eingriffsverfahren": {
|
||||
"@gml:id": self.intervention.identifier,
|
||||
"oneo:azEintragungsstelle": self.intervention.responsible.conservation_file_number,
|
||||
"oneo:azZulassungsstelle": self.intervention.responsible.registration_file_number,
|
||||
"oneo:bemerkungZulassungsstelle": None,
|
||||
"oneo:eintragungsstelle": {
|
||||
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/907/{cons_office.atom_id if cons_office else None}",
|
||||
"#text": cons_office.long_name if cons_office else None
|
||||
},
|
||||
"oneo:zulassungsstelle": {
|
||||
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{reg_office.atom_id if reg_office else None}",
|
||||
"#text": reg_office.long_name if reg_office else None
|
||||
},
|
||||
"oneo:ersatzzahlung": self._sum_all_payments(),
|
||||
"oneo:kompensationsart": {
|
||||
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/88140/{comp_type_code}",
|
||||
"#text": comp_type
|
||||
},
|
||||
"oneo:verfahrensrecht": {
|
||||
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1048/{law.atom_id if law else None}",
|
||||
"#text": law.short_name if law else None
|
||||
},
|
||||
"oneo:verfahrenstyp": {
|
||||
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/44382/{process_type.atom_id if process_type else None}",
|
||||
"#text": process_type.long_name if process_type else None,
|
||||
},
|
||||
"oneo:eingreifer": {
|
||||
"oneo:Eingreifer": {
|
||||
"oneo:art": {
|
||||
"@xlink:href": f"http://register.naturschutz.rlp.de/repository/services/referenzliste/1053/{handler.type.atom_id if handler.type else None}",
|
||||
"#text": handler.type.long_name if handler.type else None,
|
||||
},
|
||||
"oneo:bemerkung": handler.detail if handler else None,
|
||||
}
|
||||
},
|
||||
"oneo:erfasser": {
|
||||
"oneo:Erfasser": {
|
||||
"oneo:name": None,
|
||||
"oneo:bemerkung": None,
|
||||
}
|
||||
},
|
||||
"oneo:zulassung": {
|
||||
"oneo:Zulassungstermin": {
|
||||
"oneo:bauBeginn": payment_date,
|
||||
"oneo:erlass": reg_date.strftime(DEFAULT_DATE_FORMAT) if reg_date else None,
|
||||
"oneo:rechtsKraft": bind_date.strftime(DEFAULT_DATE_FORMAT) if bind_date else None,
|
||||
}
|
||||
},
|
||||
"oneo:geometrie": {
|
||||
"gml:multiSurfaceProperty": {
|
||||
"gml:MultiPolygon": {
|
||||
"@srsName": f"http://www.opengis.net/gml/srs/epsg.xml#{DEFAULT_SRID_RLP}",
|
||||
"gml:polygonMember": self._gen_geometry_list(),
|
||||
}
|
||||
},
|
||||
},
|
||||
"oneo:kennung": self.intervention.identifier,
|
||||
"oneo:bezeichnung": self.intervention.title,
|
||||
"oneo:bemerkung": self.intervention.comment,
|
||||
"oneo:verantwortlicheStelle": None,
|
||||
"oneo:veroffentlichtAm": None,
|
||||
"oneo:raumreferenz": {
|
||||
"oneo:Raumreferenz": self._gen_raumreferenz(),
|
||||
},
|
||||
"oneo:foto": self._gen_foto(),
|
||||
}
|
||||
},
|
||||
}
|
||||
gml = xmltodict.unparse(xml_dict)
|
||||
return gml
|
||||
@@ -18,7 +18,8 @@ from konova.utils.documents import remove_document, get_document
|
||||
from konova.utils.generators import generate_qr_code
|
||||
from konova.utils.message_templates import INTERVENTION_INVALID, FORM_INVALID, IDENTIFIER_REPLACED, \
|
||||
CHECKED_RECORDED_RESET, DEDUCTION_REMOVED, DEDUCTION_ADDED, REVOCATION_ADDED, REVOCATION_REMOVED, \
|
||||
COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED
|
||||
COMPENSATION_REMOVED_TEMPLATE, DOCUMENT_ADDED, DEDUCTION_EDITED, REVOCATION_EDITED, DOCUMENT_EDITED, \
|
||||
RECORDED_BLOCKS_EDIT, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
||||
from konova.utils.user_checks import in_group
|
||||
|
||||
|
||||
@@ -264,15 +265,18 @@ def detail_view(request: HttpRequest, id: str):
|
||||
geom_form = SimpleGeomForm(
|
||||
instance=intervention,
|
||||
)
|
||||
|
||||
parcels = intervention.get_underlying_parcels()
|
||||
last_checked = intervention.get_last_checked_action()
|
||||
last_checked_tooltip = ""
|
||||
if last_checked:
|
||||
last_checked_tooltip = DATA_CHECKED_PREVIOUSLY_TEMPLATE.format(last_checked.get_timestamp_str_formatted(), last_checked.user)
|
||||
|
||||
context = {
|
||||
"obj": intervention,
|
||||
"last_checked": last_checked,
|
||||
"last_checked_tooltip": last_checked_tooltip,
|
||||
"compensations": compensations,
|
||||
"has_access": is_data_shared,
|
||||
"geom_form": geom_form,
|
||||
"parcels": parcels,
|
||||
"is_default_member": in_group(_user, DEFAULT_GROUP),
|
||||
"is_zb_member": in_group(_user, ZB_GROUP),
|
||||
"is_ets_member": in_group(_user, ETS_GROUP),
|
||||
@@ -302,6 +306,13 @@ def edit_view(request: HttpRequest, id: str):
|
||||
template = "intervention/form/view.html"
|
||||
# Get object from db
|
||||
intervention = get_object_or_404(Intervention, id=id)
|
||||
if intervention.is_recorded:
|
||||
messages.info(
|
||||
request,
|
||||
RECORDED_BLOCKS_EDIT
|
||||
)
|
||||
return redirect("intervention:detail", id=id)
|
||||
|
||||
# Create forms, initialize with values from db/from POST request
|
||||
data_form = EditInterventionForm(request.POST or None, instance=intervention)
|
||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
|
||||
@@ -432,7 +443,7 @@ def share_view(request: HttpRequest, id: str, token: str):
|
||||
request,
|
||||
_("{} has been shared with you").format(intervention.identifier)
|
||||
)
|
||||
intervention.share_with(user)
|
||||
intervention.share_with_user(user)
|
||||
return redirect("intervention:detail", id=id)
|
||||
else:
|
||||
messages.error(
|
||||
@@ -693,19 +704,22 @@ def report_view(request:HttpRequest, id: str):
|
||||
distinct_deductions = intervention.deductions.all().distinct(
|
||||
"account"
|
||||
)
|
||||
qrcode_img = generate_qr_code(
|
||||
request.build_absolute_uri(reverse("intervention:report", args=(id,))),
|
||||
10
|
||||
)
|
||||
qrcode_img_lanis = generate_qr_code(
|
||||
intervention.get_LANIS_link(),
|
||||
7
|
||||
)
|
||||
qrcode_url = request.build_absolute_uri(reverse("intervention:report", args=(id,)))
|
||||
qrcode_img = generate_qr_code(qrcode_url, 10)
|
||||
qrcode_lanis_url = intervention.get_LANIS_link()
|
||||
qrcode_img_lanis = generate_qr_code(qrcode_lanis_url, 7)
|
||||
|
||||
context = {
|
||||
"obj": intervention,
|
||||
"deductions": distinct_deductions,
|
||||
"qrcode": qrcode_img,
|
||||
"qrcode_lanis": qrcode_img_lanis,
|
||||
"qrcode": {
|
||||
"img": qrcode_img,
|
||||
"url": qrcode_url,
|
||||
},
|
||||
"qrcode_lanis": {
|
||||
"img": qrcode_img_lanis,
|
||||
"url": qrcode_lanis_url,
|
||||
},
|
||||
"geom_form": geom_form,
|
||||
"parcels": parcels,
|
||||
TAB_TITLE_IDENTIFIER: tab_title,
|
||||
|
||||
@@ -7,7 +7,8 @@ Created on: 22.07.21
|
||||
"""
|
||||
from django.contrib import admin
|
||||
|
||||
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District
|
||||
from konova.models import Geometry, Deadline, GeometryConflict, Parcel, District, Municipal, ParcelGroup
|
||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
||||
from user.models import UserAction
|
||||
|
||||
@@ -16,13 +17,28 @@ class GeometryAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"created",
|
||||
"st_area",
|
||||
]
|
||||
readonly_fields = [
|
||||
"st_area",
|
||||
"created",
|
||||
"modified",
|
||||
]
|
||||
|
||||
def st_area(self, obj):
|
||||
val = None
|
||||
geom = obj.geom
|
||||
if geom is not None:
|
||||
geom.transform(ct=DEFAULT_SRID_RLP)
|
||||
val = geom.area
|
||||
return val
|
||||
st_area.short_description = f"Area (srid={DEFAULT_SRID_RLP})"
|
||||
|
||||
|
||||
class ParcelAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"id",
|
||||
"gmrkng",
|
||||
"parcel_group",
|
||||
"flr",
|
||||
"flrstck_nnr",
|
||||
"flrstck_zhlr",
|
||||
@@ -32,9 +48,27 @@ class ParcelAdmin(admin.ModelAdmin):
|
||||
|
||||
class DistrictAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"name",
|
||||
"key",
|
||||
"id",
|
||||
]
|
||||
|
||||
|
||||
class MunicipalAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"name",
|
||||
"key",
|
||||
"district",
|
||||
"id",
|
||||
]
|
||||
|
||||
|
||||
class ParcelGroupAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
"name",
|
||||
"key",
|
||||
"municipal",
|
||||
"id",
|
||||
"gmnd",
|
||||
"krs",
|
||||
]
|
||||
|
||||
|
||||
@@ -64,6 +98,18 @@ class DeadlineAdmin(admin.ModelAdmin):
|
||||
]
|
||||
|
||||
|
||||
class DeletableObjectMixinAdmin(admin.ModelAdmin):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def restore_deleted_data(self, request, queryset):
|
||||
queryset = queryset.filter(
|
||||
deleted__isnull=False
|
||||
)
|
||||
for entry in queryset:
|
||||
entry.deleted.delete()
|
||||
|
||||
|
||||
class BaseResourceAdmin(admin.ModelAdmin):
|
||||
fields = [
|
||||
"created",
|
||||
@@ -75,7 +121,7 @@ class BaseResourceAdmin(admin.ModelAdmin):
|
||||
]
|
||||
|
||||
|
||||
class BaseObjectAdmin(BaseResourceAdmin):
|
||||
class BaseObjectAdmin(BaseResourceAdmin, DeletableObjectMixinAdmin):
|
||||
search_fields = [
|
||||
"identifier",
|
||||
"title",
|
||||
@@ -92,18 +138,13 @@ class BaseObjectAdmin(BaseResourceAdmin):
|
||||
"deleted",
|
||||
]
|
||||
|
||||
def restore_deleted_data(self, request, queryset):
|
||||
queryset = queryset.filter(
|
||||
deleted__isnull=False
|
||||
)
|
||||
for entry in queryset:
|
||||
entry.deleted.delete()
|
||||
|
||||
|
||||
|
||||
# Outcommented for a cleaner admin backend on production
|
||||
#admin.site.register(Geometry, GeometryAdmin)
|
||||
#admin.site.register(Parcel, ParcelAdmin)
|
||||
#admin.site.register(District, DistrictAdmin)
|
||||
#admin.site.register(Municipal, MunicipalAdmin)
|
||||
#admin.site.register(ParcelGroup, ParcelGroupAdmin)
|
||||
#admin.site.register(GeometryConflict, GeometryConflictAdmin)
|
||||
#admin.site.register(Deadline, DeadlineAdmin)
|
||||
|
||||
@@ -5,14 +5,19 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 07.12.20
|
||||
|
||||
"""
|
||||
import collections
|
||||
|
||||
from dal_select2.views import Select2QuerySetView, Select2GroupQuerySetView
|
||||
from user.models import User
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from konova.utils.message_templates import UNGROUPED
|
||||
from user.models import User, Team
|
||||
from django.db.models import Q
|
||||
|
||||
from codelist.models import KonovaCode
|
||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, \
|
||||
CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID, \
|
||||
CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID
|
||||
CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_HANDLER_ID
|
||||
from compensation.models import EcoAccount
|
||||
from intervention.models import Intervention
|
||||
|
||||
@@ -20,7 +25,7 @@ from intervention.models import Intervention
|
||||
class EcoAccountAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for ecoAccount entries
|
||||
|
||||
Only returns entries that are accessible for the requesting user and already are recorded
|
||||
Only returns entries that are already recorded and not deleted
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
@@ -29,7 +34,6 @@ class EcoAccountAutocomplete(Select2QuerySetView):
|
||||
qs = EcoAccount.objects.filter(
|
||||
deleted=None,
|
||||
recorded__isnull=False,
|
||||
users__in=[self.request.user],
|
||||
).order_by(
|
||||
"identifier"
|
||||
)
|
||||
@@ -48,14 +52,16 @@ class InterventionAutocomplete(Select2QuerySetView):
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
user = self.request.user
|
||||
if user.is_anonymous:
|
||||
return Intervention.objects.none()
|
||||
qs = Intervention.objects.filter(
|
||||
deleted=None,
|
||||
users__in=[self.request.user],
|
||||
Q(deleted=None) &
|
||||
Q(users__in=[user]) |
|
||||
Q(teams__in=user.teams.all())
|
||||
).order_by(
|
||||
"identifier"
|
||||
)
|
||||
).distinct()
|
||||
if self.q:
|
||||
qs = qs.filter(
|
||||
Q(identifier__icontains=self.q) |
|
||||
@@ -65,27 +71,65 @@ class InterventionAutocomplete(Select2QuerySetView):
|
||||
|
||||
|
||||
class ShareUserAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for intervention entries
|
||||
""" Autocomplete for share with single users
|
||||
|
||||
Only returns entries that are accessible for the requesting user
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return User.objects.none()
|
||||
exclude_user_ids = self.forwarded.get("users", [])
|
||||
_exclude = {"id__in": exclude_user_ids}
|
||||
qs = User.objects.all().exclude(
|
||||
**_exclude
|
||||
).order_by(
|
||||
"username"
|
||||
)
|
||||
qs = User.objects.all()
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
Q(username=self.q) |
|
||||
Q(email=self.q)
|
||||
).distinct()
|
||||
qs = qs.order_by("username")
|
||||
return qs
|
||||
|
||||
|
||||
class ShareTeamAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with teams
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return Team.objects.none()
|
||||
qs = Team.objects.filter(
|
||||
deleted__isnull=True
|
||||
)
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
name__icontains=self.q
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"name"
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class TeamAdminAutocomplete(Select2QuerySetView):
|
||||
""" Autocomplete for share with teams
|
||||
|
||||
"""
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_anonymous:
|
||||
return User.objects.none()
|
||||
qs = User.objects.filter(
|
||||
id__in=self.forwarded.get("members", [])
|
||||
).exclude(
|
||||
id__in=self.forwarded.get("admins", [])
|
||||
)
|
||||
if self.q:
|
||||
# Due to privacy concerns only a full username match will return the proper user entry
|
||||
qs = qs.filter(
|
||||
name__icontains=self.q
|
||||
)
|
||||
qs = qs.order_by(
|
||||
"username"
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
@@ -139,6 +183,8 @@ class KonovaCodeAutocomplete(Select2GroupQuerySetView):
|
||||
q_or |= Q(short_name__icontains=keyword)
|
||||
q_or |= Q(parent__long_name__icontains=keyword)
|
||||
q_or |= Q(parent__short_name__icontains=keyword)
|
||||
q_or |= Q(parent__parent__long_name__icontains=keyword)
|
||||
q_or |= Q(parent__parent__short_name__icontains=keyword)
|
||||
_filter.add(q_or, Q.AND)
|
||||
qs = qs.filter(_filter).distinct()
|
||||
return qs
|
||||
@@ -181,7 +227,7 @@ class CompensationActionDetailCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"parent__long_name"
|
||||
"long_name"
|
||||
)
|
||||
|
||||
|
||||
@@ -214,6 +260,41 @@ class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
|
||||
def get_results(self, context):
|
||||
"""Return the options grouped by a common related model.
|
||||
|
||||
Raises ImproperlyConfigured if self.group_by_name is not configured
|
||||
"""
|
||||
if not self.group_by_related:
|
||||
raise ImproperlyConfigured("Missing group_by_related.")
|
||||
|
||||
super_groups = collections.OrderedDict()
|
||||
|
||||
object_list = context['object_list']
|
||||
|
||||
for result in object_list:
|
||||
group = result.parent if result.parent else None
|
||||
group_name = f"{group.long_name} ({group.short_name})" if group else UNGROUPED
|
||||
super_group = result.parent.parent if result.parent else None
|
||||
super_group_name = f"{super_group.long_name} ({super_group.short_name})" if super_group else UNGROUPED
|
||||
super_groups.setdefault(super_group_name, {})
|
||||
super_groups[super_group_name].setdefault(group_name, [])
|
||||
super_groups[super_group_name][group_name].append(result)
|
||||
|
||||
return [{
|
||||
'id': None,
|
||||
'text': super_group,
|
||||
'children': [{
|
||||
"id": None,
|
||||
"text": group,
|
||||
"children": [{
|
||||
'id': self.get_result_value(result),
|
||||
'text': self.get_result_label(result),
|
||||
'selected_text': self.get_selected_result_label(result),
|
||||
} for result in results]
|
||||
} for group, results in groups.items()]
|
||||
} for super_group, groups in super_groups.items()]
|
||||
|
||||
|
||||
class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
@@ -239,7 +320,7 @@ class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
qs (QuerySet): The ordered queryset
|
||||
"""
|
||||
return qs.order_by(
|
||||
"parent__long_name",
|
||||
"long_name",
|
||||
)
|
||||
|
||||
def get_result_label(self, result):
|
||||
@@ -284,6 +365,11 @@ class RegistrationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
self.c = CODELIST_REGISTRATION_OFFICE_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def order_by(self, qs):
|
||||
return qs.order_by(
|
||||
"parent__long_name"
|
||||
)
|
||||
|
||||
|
||||
class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
@@ -297,4 +383,19 @@ class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
return f"{result.long_name} ({result.short_name})"
|
||||
|
||||
|
||||
class HandlerCodeAutocomplete(KonovaCodeAutocomplete):
|
||||
"""
|
||||
Due to limitations of the django dal package, we need to subclass for each code list
|
||||
"""
|
||||
group_by_related = "parent"
|
||||
related_field_name = "long_name"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.c = CODELIST_HANDLER_ID
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_result_label(self, result):
|
||||
return result.long_name
|
||||
|
||||
@@ -145,26 +145,20 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def _filter_parcel_reference(self, queryset, name, value, filter_value) -> QuerySet:
|
||||
""" Filters the parcel entries by a given filter_value.
|
||||
|
||||
filter_value may already include further filter annotations like 'xy__icontains'
|
||||
def _filter_parcel_reference(self, queryset, filter_q) -> QuerySet:
|
||||
""" Filters the parcel entries by a given filter_q
|
||||
|
||||
Args:
|
||||
queryset ():
|
||||
name ():
|
||||
value ():
|
||||
filter_value ():
|
||||
queryset (QuerySet): The queryset
|
||||
filter_q (Q): The Q-style filter expression
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
_filter = {
|
||||
filter_value: value
|
||||
}
|
||||
matching_parcels = Parcel.objects.filter(
|
||||
**_filter
|
||||
filter_q
|
||||
)
|
||||
|
||||
related_geoms = matching_parcels.values(
|
||||
"geometries"
|
||||
).distinct()
|
||||
@@ -185,8 +179,9 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
|
||||
"""
|
||||
matching_districts = District.objects.filter(
|
||||
krs=value
|
||||
)
|
||||
Q(name__icontains=value) |
|
||||
Q(key__icontains=value)
|
||||
).distinct()
|
||||
matching_parcels = Parcel.objects.filter(
|
||||
district__in=matching_districts
|
||||
)
|
||||
@@ -209,7 +204,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
Returns:
|
||||
|
||||
"""
|
||||
queryset = self._filter_parcel_reference(queryset, name, value, "gmrkng__istartswith")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(parcel_group__name__icontains=value) | Q(parcel_group__key__icontains=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel(self, queryset, name, value) -> QuerySet:
|
||||
@@ -224,7 +222,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(queryset, name, value, "flr")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flr=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel_counter(self, queryset, name, value) -> QuerySet:
|
||||
@@ -239,7 +240,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(queryset, name, value, "flrstck_zhlr")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_zhlr=value)
|
||||
)
|
||||
return queryset
|
||||
|
||||
def filter_parcel_number(self, queryset, name, value) -> QuerySet:
|
||||
@@ -254,7 +258,10 @@ class GeoReferencedTableFilterMixin(django_filters.FilterSet):
|
||||
|
||||
"""
|
||||
value = value.replace("-", "")
|
||||
queryset = self._filter_parcel_reference(queryset, name, value, "flrstck_nnr")
|
||||
queryset = self._filter_parcel_reference(
|
||||
queryset,
|
||||
Q(flrstck_nnr=value),
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
||||
@@ -297,8 +304,9 @@ class ShareableTableFilterMixin(django_filters.FilterSet):
|
||||
"""
|
||||
if not value:
|
||||
return queryset.filter(
|
||||
users__in=[self.user], # requesting user has access
|
||||
)
|
||||
Q(users__in=[self.user]) | # requesting user has access
|
||||
Q(teams__in=self.user.shared_teams)
|
||||
).distinct()
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
128
konova/forms.py
128
konova/forms.py
@@ -5,18 +5,20 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 16.11.20
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
from abc import abstractmethod
|
||||
|
||||
from bootstrap_modal_forms.forms import BSModalForm
|
||||
from bootstrap_modal_forms.utils import is_ajax
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.contrib.gis import gdal
|
||||
from django.db.models.fields.files import FieldFile
|
||||
|
||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||
from user.models import User
|
||||
from django.contrib.gis.forms import OSMWidget, MultiPolygonField
|
||||
from django.contrib.gis.geos import MultiPolygon
|
||||
from django.contrib.gis.forms import MultiPolygonField
|
||||
from django.contrib.gis.geos import MultiPolygon, Polygon
|
||||
from django.db import transaction
|
||||
from django.http import HttpRequest, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
@@ -57,6 +59,8 @@ class BaseForm(forms.Form):
|
||||
self.has_required_fields = True
|
||||
break
|
||||
|
||||
self.check_for_recorded_instance()
|
||||
|
||||
@abstractmethod
|
||||
def save(self):
|
||||
# To be implemented in subclasses!
|
||||
@@ -136,6 +140,38 @@ class BaseForm(forms.Form):
|
||||
set_class = set_class.replace(cls, "")
|
||||
self.fields[field].widget.attrs["class"] = set_class
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
""" Checks if the instance is recorded and runs some special logic if yes
|
||||
|
||||
If the instance is recorded, the form shall not display any possibility to
|
||||
edit any data. Instead, the users should get some information about why they can not edit anything.
|
||||
|
||||
There are situations where the form should be rendered regularly,
|
||||
e.g deduction forms for (recorded) eco accounts.
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
from intervention.forms.modalForms import NewDeductionModalForm, EditEcoAccountDeductionModalForm, \
|
||||
RemoveEcoAccountDeductionModalForm
|
||||
is_none = self.instance is None
|
||||
is_other_data_type = not isinstance(self.instance, BaseObject)
|
||||
is_deduction_form = isinstance(
|
||||
self,
|
||||
(
|
||||
NewDeductionModalForm,
|
||||
EditEcoAccountDeductionModalForm,
|
||||
RemoveEcoAccountDeductionModalForm,
|
||||
)
|
||||
)
|
||||
|
||||
if is_none or is_other_data_type or is_deduction_form:
|
||||
# Do nothing
|
||||
return
|
||||
|
||||
if self.instance.is_recorded:
|
||||
self.template = "form/recorded_no_edit.html"
|
||||
|
||||
|
||||
class RemoveForm(BaseForm):
|
||||
check = forms.BooleanField(
|
||||
@@ -238,41 +274,85 @@ class SimpleGeomForm(BaseForm):
|
||||
""" A geometry form for rendering geometry read-only using a widget
|
||||
|
||||
"""
|
||||
read_only = True
|
||||
geom = MultiPolygonField(
|
||||
srid=DEFAULT_SRID,
|
||||
srid=DEFAULT_SRID_RLP,
|
||||
label=_("Geometry"),
|
||||
help_text=_(""),
|
||||
label_suffix="",
|
||||
required=False,
|
||||
disabled=False,
|
||||
widget=OSMWidget(
|
||||
attrs={
|
||||
"map_width": 600,
|
||||
"map_height": 400,
|
||||
# default_zoom defines the nearest possible zoom level from which the JS automatically
|
||||
# zooms out if geometry requires a larger view port. So define a larger range for smaller geometries
|
||||
"default_zoom": 25,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
read_only = kwargs.pop("read_only", True)
|
||||
self.read_only = kwargs.pop("read_only", True)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Initialize geometry
|
||||
try:
|
||||
geom = self.instance.geometry.geom
|
||||
self.empty = geom.empty
|
||||
|
||||
if self.empty:
|
||||
raise AttributeError
|
||||
|
||||
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
||||
geom = json.dumps(geojson)
|
||||
except AttributeError:
|
||||
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||
geom = None
|
||||
geom = ""
|
||||
self.empty = True
|
||||
self.fields["geom"].widget.attrs["default_zoom"] = 1
|
||||
|
||||
self.initialize_form_field("geom", geom)
|
||||
if read_only:
|
||||
self.fields["geom"].disabled = True
|
||||
|
||||
def is_valid(self):
|
||||
super().is_valid()
|
||||
is_valid = True
|
||||
|
||||
# Get geojson from form
|
||||
geom = self.data["geom"]
|
||||
if geom is None or len(geom) == 0:
|
||||
# empty geometry is a valid geometry
|
||||
return is_valid
|
||||
geom = json.loads(geom)
|
||||
|
||||
# Write submitted data back into form field to make sure invalid geometry
|
||||
# will be rendered again on failed submit
|
||||
self.initialize_form_field("geom", self.data["geom"])
|
||||
|
||||
# Read geojson into gdal geometry
|
||||
# HINT: This can be simplified if the geojson format holds data in epsg:4326 (GDAL provides direct creation for
|
||||
# this case)
|
||||
features = []
|
||||
features_json = geom.get("features", [])
|
||||
for feature in features_json:
|
||||
g = gdal.OGRGeometry(json.dumps(feature.get("geometry", feature)), srs=DEFAULT_SRID_RLP)
|
||||
if g.geom_type not in ["Polygon", "MultiPolygon"]:
|
||||
self.add_error("geom", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||
is_valid = False
|
||||
return is_valid
|
||||
|
||||
polygon = Polygon.from_ewkt(g.ewkt)
|
||||
is_valid = polygon.valid
|
||||
if not is_valid:
|
||||
self.add_error("geom", polygon.valid_reason)
|
||||
return is_valid
|
||||
|
||||
features.append(polygon)
|
||||
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
||||
for feature in features:
|
||||
form_geom = form_geom.union(feature)
|
||||
|
||||
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
||||
if form_geom.geom_type != "MultiPolygon":
|
||||
form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
|
||||
|
||||
# Write unioned Multipolygon into cleaned data
|
||||
if self.cleaned_data is None:
|
||||
self.cleaned_data = {}
|
||||
self.cleaned_data["geom"] = form_geom.ewkt
|
||||
|
||||
return is_valid
|
||||
|
||||
def save(self, action: UserActionLogEntry):
|
||||
""" Saves the form's geometry
|
||||
@@ -410,7 +490,6 @@ class NewDocumentModalForm(BaseModalForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Add new document")
|
||||
self.form_caption = _("")
|
||||
self.template = "modal/modal_form.html"
|
||||
self.form_attrs = {
|
||||
"enctype": "multipart/form-data", # important for file upload
|
||||
}
|
||||
@@ -471,6 +550,7 @@ class EditDocumentModalForm(NewDocumentModalForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.document = kwargs.pop("document", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.form_title = _("Edit document")
|
||||
form_data = {
|
||||
"title": self.document.title,
|
||||
"comment": self.document.comment,
|
||||
@@ -596,4 +676,12 @@ class RecordModalForm(BaseModalForm):
|
||||
self.instance.set_unrecorded(self.user)
|
||||
else:
|
||||
self.instance.set_recorded(self.user)
|
||||
return self.instance
|
||||
return self.instance
|
||||
|
||||
def check_for_recorded_instance(self):
|
||||
""" Overwrite the check method for doing nothing on the RecordModalForm
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -9,7 +9,7 @@ from compensation.models import CompensationState, Compensation, EcoAccount, Com
|
||||
from ema.models import Ema
|
||||
from intervention.models import Intervention
|
||||
from konova.management.commands.setup import BaseKonovaCommand
|
||||
from konova.models import Deadline, Geometry, Parcel, District
|
||||
from konova.models import Deadline, Geometry, Parcel, District, Municipal, ParcelGroup
|
||||
from user.models import UserActionLogEntry, UserAction
|
||||
|
||||
|
||||
@@ -271,13 +271,26 @@ class Command(BaseKonovaCommand):
|
||||
self._write_success("No unused states found.")
|
||||
self._break_line()
|
||||
|
||||
def __sanitize_parcel_sub_type(self, cls):
|
||||
unrelated_entries = cls.objects.filter(
|
||||
parcels=None,
|
||||
)
|
||||
num_unrelated_entries = unrelated_entries.count()
|
||||
cls_name = cls.__name__
|
||||
if num_unrelated_entries > 0:
|
||||
self._write_error(f"Found {num_unrelated_entries} unrelated {cls_name} entries. Delete now...")
|
||||
unrelated_entries.delete()
|
||||
self._write_success(f"Unrelated {cls_name} deleted.")
|
||||
else:
|
||||
self._write_success(f"No unrelated {cls_name} found.")
|
||||
|
||||
def sanitize_parcels_and_districts(self):
|
||||
""" Removes unattached parcels and districts
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
self._write_warning("=== Sanitize parcels and districts ===")
|
||||
self._write_warning("=== Sanitize administrative spatial references ===")
|
||||
unrelated_parcels = Parcel.objects.filter(
|
||||
geometries=None,
|
||||
)
|
||||
@@ -289,16 +302,12 @@ class Command(BaseKonovaCommand):
|
||||
else:
|
||||
self._write_success("No unrelated parcels found.")
|
||||
|
||||
unrelated_districts = District.objects.filter(
|
||||
parcels=None,
|
||||
)
|
||||
num_unrelated_districts = unrelated_districts.count()
|
||||
if num_unrelated_districts > 0:
|
||||
self._write_error(f"Found {num_unrelated_districts} unrelated district entries. Delete now...")
|
||||
unrelated_districts.delete()
|
||||
self._write_success("Unrelated districts deleted.")
|
||||
else:
|
||||
self._write_success("No unrelated districts found.")
|
||||
|
||||
self._break_line()
|
||||
sub_types = [
|
||||
District,
|
||||
Municipal,
|
||||
ParcelGroup
|
||||
]
|
||||
for sub_type in sub_types:
|
||||
self.__sanitize_parcel_sub_type(sub_type)
|
||||
|
||||
self._break_line()
|
||||
@@ -5,6 +5,10 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
||||
Created on: 04.01.22
|
||||
|
||||
"""
|
||||
import datetime
|
||||
|
||||
from django.contrib.gis.db.models.functions import Area
|
||||
|
||||
from konova.management.commands.setup import BaseKonovaCommand
|
||||
from konova.models import Geometry, Parcel, District
|
||||
|
||||
@@ -23,12 +27,21 @@ class Command(BaseKonovaCommand):
|
||||
num_parcels_before = Parcel.objects.count()
|
||||
num_districts_before = District.objects.count()
|
||||
self._write_warning("=== Update parcels and districts ===")
|
||||
# Order geometries by size to process smaller once at first
|
||||
geometries = Geometry.objects.all().exclude(
|
||||
geom=None
|
||||
).annotate(area=Area("geom")).order_by(
|
||||
'area'
|
||||
)
|
||||
self._write_warning(f"Process parcels for {geometries.count()} geometry entries now ...")
|
||||
i = 0
|
||||
num_geoms = geometries.count()
|
||||
for geometry in geometries:
|
||||
self._write_warning(f"--- {datetime.datetime.now()} Process {geometry.id} now ...")
|
||||
geometry.update_parcels()
|
||||
self._write_warning(f"--- Processed {geometry.get_underlying_parcels().count()} underlying parcels")
|
||||
i += 1
|
||||
self._write_warning(f"--- {i}/{num_geoms} processed")
|
||||
|
||||
num_parcels_after = Parcel.objects.count()
|
||||
num_districts_after = District.objects.count()
|
||||
|
||||
40
konova/migrations/0005_auto_20220216_0856.py
Normal file
40
konova/migrations/0005_auto_20220216_0856.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Generated by Django 3.1.3 on 2022-02-16 07:56
|
||||
|
||||
from django.db import migrations, transaction
|
||||
|
||||
from codelist.settings import CODELIST_BIOTOPES_ID, CODELIST_AFTER_STATE_BIOTOPES_ID
|
||||
|
||||
|
||||
def migrate_biotopes_from_974_to_654(apps, schema_editor):
|
||||
KonovaCode = apps.get_model("codelist", "KonovaCode")
|
||||
CompensationState = apps.get_model("compensation", "CompensationState")
|
||||
all_states = CompensationState.objects.all()
|
||||
|
||||
with transaction.atomic():
|
||||
for state in all_states:
|
||||
new_biotope_code = KonovaCode.objects.get(
|
||||
code_lists__in=[CODELIST_BIOTOPES_ID],
|
||||
is_selectable=True,
|
||||
is_archived=False,
|
||||
short_name=state.biotope_type.short_name,
|
||||
)
|
||||
state.biotope_type = new_biotope_code
|
||||
state.save()
|
||||
|
||||
all_states = CompensationState.objects.all()
|
||||
after_state_list_elements = all_states.filter(
|
||||
biotope_type__code_lists__in=[CODELIST_AFTER_STATE_BIOTOPES_ID]
|
||||
)
|
||||
if after_state_list_elements.count() > 0:
|
||||
raise Exception("Still states with wrong codelist entries!")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('konova', '0004_auto_20220209_0839'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate_biotopes_from_974_to_654),
|
||||
]
|
||||
71
konova/migrations/0006_auto_20220411_0835.py
Normal file
71
konova/migrations/0006_auto_20220411_0835.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# Generated by Django 3.1.3 on 2022-04-11 06:35
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('konova', '0005_auto_20220216_0856'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Municipal',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('key', models.IntegerField(blank=True, help_text='Represents Gemeindeschlüssel', null=True)),
|
||||
('name', models.CharField(blank=True, help_text='Gemeinde', max_length=1000, null=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='district',
|
||||
old_name='krs',
|
||||
new_name='name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='district',
|
||||
name='gmnd',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='parcel',
|
||||
name='gmrkng',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='district',
|
||||
name='key',
|
||||
field=models.IntegerField(blank=True, help_text='Represents Kreisschlüssel', null=True),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ParcelGroup',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('key', models.IntegerField(blank=True, help_text='Represents Gemarkungsschlüssel', null=True)),
|
||||
('name', models.CharField(blank=True, help_text='Gemarkung', max_length=1000, null=True)),
|
||||
('municipal', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.municipal')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='municipal',
|
||||
name='district',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.district'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='parcel',
|
||||
name='municipal',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parcels', to='konova.municipal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='parcel',
|
||||
name='parcel_group',
|
||||
field=models.ForeignKey(blank=True, help_text='Gemarkung', null=True, on_delete=django.db.models.deletion.SET_NULL, to='konova.parcelgroup'),
|
||||
),
|
||||
]
|
||||
28
konova/migrations/0007_auto_20220411_0848.py
Normal file
28
konova/migrations/0007_auto_20220411_0848.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 3.1.3 on 2022-04-11 06:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('konova', '0006_auto_20220411_0835'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='district',
|
||||
name='key',
|
||||
field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='municipal',
|
||||
name='key',
|
||||
field=models.CharField(blank=True, help_text='Represents Gemeindeschlüssel', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parcelgroup',
|
||||
name='key',
|
||||
field=models.CharField(blank=True, help_text='Represents Gemarkungsschlüssel', max_length=255, null=True),
|
||||
),
|
||||
]
|
||||
48
konova/migrations/0008_auto_20220411_0914.py
Normal file
48
konova/migrations/0008_auto_20220411_0914.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Generated by Django 3.1.3 on 2022-04-11 07:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('konova', '0007_auto_20220411_0848'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='municipal',
|
||||
name='key',
|
||||
field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='municipal',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, help_text='Kreis', max_length=1000, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parcel',
|
||||
name='flr',
|
||||
field=models.IntegerField(blank=True, help_text='Flur', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parcel',
|
||||
name='flrstck_nnr',
|
||||
field=models.IntegerField(blank=True, help_text='Flurstücksnenner', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parcel',
|
||||
name='flrstck_zhlr',
|
||||
field=models.IntegerField(blank=True, help_text='Flurstückszähler', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parcelgroup',
|
||||
name='key',
|
||||
field=models.CharField(blank=True, help_text='Represents Kreisschlüssel', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parcelgroup',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, help_text='Kreis', max_length=1000, null=True),
|
||||
),
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user