Compare commits
956
Commits
1.22
..
d830b7f88b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d830b7f88b | ||
|
|
9a5ebb88a9 | ||
|
|
274f6460de | ||
|
|
a3a616dffe | ||
|
|
86698bd289 | ||
|
|
5059b763d7 | ||
|
|
5fb9afa7bd | ||
|
|
439a211e01 | ||
|
|
eb528de48b | ||
|
|
f39d99b2fd | ||
|
|
85235386ec | ||
|
|
7820dec3d1 | ||
|
|
de93092a52 | ||
|
|
8ef11c681e | ||
|
|
e6eb9abd36 | ||
|
|
9de9d5007c | ||
|
|
45441cc44d | ||
|
|
3ac9081205 | ||
|
|
b265aab4cf | ||
|
|
e47eda385d | ||
|
|
fd7ed40c91 | ||
|
|
c004e1c6da | ||
|
|
e9f9420f0f | ||
|
|
4e56f47b47 | ||
|
|
b622f986b1 | ||
|
|
dc718e911b | ||
|
|
ac6d133797 | ||
|
|
67fe06315b | ||
|
|
e5284cd660 | ||
|
|
81663db65c | ||
|
|
762d25a87e | ||
|
|
2761d2a19c | ||
|
|
a8eef6e793 | ||
|
|
2cb77e89fe | ||
|
|
ff266d5c54 | ||
|
|
730d29bcef | ||
|
|
e57e3ed598 | ||
|
|
f88f9d06fa | ||
|
|
7eb440f841 | ||
|
|
134afae038 | ||
|
|
dc754c0363 | ||
|
|
801c9078e9 | ||
|
|
c282443a6d | ||
|
|
af0afec77d | ||
|
|
44c11fd89b | ||
|
|
d86bbb0ab0 | ||
|
|
ec4b5f76fe | ||
|
|
eaea0eb512 | ||
|
|
8a67033e0d | ||
|
|
6e35dc7de6 | ||
|
|
9b29474e46 | ||
|
|
da545cc8ec | ||
|
|
2365ede672 | ||
|
|
898a56db8a | ||
|
|
8b1cf24bdd | ||
|
|
5ef840f172 | ||
|
|
9e762584dc | ||
|
|
d1790feda8 | ||
|
|
decf0dc522 | ||
|
|
67de6be975 | ||
|
|
32bfec3683 | ||
|
|
d31f997a58 | ||
|
|
d0e812ae70 | ||
|
|
8feb24e70f | ||
|
|
5bd0a161ff | ||
|
|
be373e2b14 | ||
|
|
3b38f227ec | ||
|
|
b00dd5bcc8 | ||
|
|
9d4a9bd122 | ||
|
|
34d53b0bc6 | ||
|
|
d4edb00a12 | ||
|
|
5c8b2ac1f8 | ||
|
|
cf050a92ef | ||
|
|
03edb74023 | ||
|
|
feee8b74ed | ||
|
|
e542872fef | ||
|
|
61046a27c5 | ||
|
|
52b1750281 | ||
|
|
aa97a663b4 | ||
|
|
5a71ad7187 | ||
|
|
a57d609420 | ||
|
|
a71fc435e0 | ||
|
|
7681a7a05f | ||
|
|
ba83993abf | ||
|
|
5b157254b4 | ||
|
|
69a8139601 | ||
|
|
590a510880 | ||
|
|
689a2d6acb | ||
|
|
4f02bf1da2 | ||
|
|
6945b17117 | ||
|
|
d639a4e530 | ||
|
|
523e338b1b | ||
|
|
5922d5ce06 | ||
|
|
4d215288b3 | ||
|
|
425bbd5f0a | ||
|
|
1746382aef | ||
|
|
e231bc6dbf | ||
|
|
5d052fee41 | ||
|
|
547aa30763 | ||
|
|
96d2b5aa6c | ||
|
|
c4c560d11c | ||
|
|
8cdea88756 | ||
|
|
11bbf7d9ec | ||
|
|
08467e5619 | ||
|
|
fd7a82e569 | ||
|
|
d94767a05a | ||
|
|
7a4c1ca530 | ||
|
|
0b131ec15a | ||
|
|
9870c28967 | ||
|
|
29a55c9427 | ||
|
|
cf1f42551d | ||
|
|
b9c9310f21 | ||
|
|
a0823814c5 | ||
|
|
fb67e42078 | ||
|
|
691292c325 | ||
|
|
2bcac86249 | ||
|
|
98a6f19a16 | ||
|
|
ebe5ced710 | ||
|
|
e4a94c6635 | ||
|
|
daee3e71b3 | ||
|
|
0dd60f8b40 | ||
|
|
c7958d1ebf | ||
|
|
bdc1c226e9 | ||
|
|
0f173c4802 | ||
|
|
599764ef38 | ||
|
|
11f2ad5617 | ||
|
|
274d7b8b6f | ||
|
|
02e8cfc76d | ||
|
|
2a9e57e633 | ||
|
|
ffae18ebc4 | ||
|
|
5f45db6716 | ||
|
|
d9046eb2b9 | ||
|
|
21db8227f8 | ||
|
|
1773463e86 | ||
|
|
e443c5f8be | ||
|
|
42cb138276 | ||
|
|
b09ad302b5 | ||
|
|
e22c9132e1 | ||
|
|
963dd57cb2 | ||
|
|
2fa8783fd9 | ||
|
|
3fa76063d0 | ||
|
|
5684b9b6d9 | ||
|
|
e9086cbbcd | ||
|
|
1437d16092 | ||
|
|
44db3ea03e | ||
|
|
9615497a61 | ||
|
|
9035b07801 | ||
|
|
ad2f4c12f8 | ||
|
|
a2b44a9c45 | ||
|
|
21c7889551 | ||
|
|
0f757a5de1 | ||
|
|
1d3b576b51 | ||
|
|
643abcf841 | ||
|
|
564ae4d5db | ||
|
|
9476713911 | ||
|
|
d9cf9669f0 | ||
|
|
345b266422 | ||
|
|
60b6968436 | ||
|
|
b11217e41f | ||
|
|
d361767471 | ||
|
|
a571c28027 | ||
|
|
4d96afae1d | ||
|
|
9544e27baf | ||
|
|
c604de0f32 | ||
|
|
204cbe2a78 | ||
|
|
4987d0b90d | ||
|
|
a49c44ab44 | ||
|
|
57b734f1c2 | ||
|
|
56ddf7ddfa | ||
|
|
028efb3023 | ||
|
|
8a84df0fcd | ||
|
|
3eff97b6dd | ||
|
|
a2a7b00db5 | ||
|
|
6ce6834acd | ||
|
|
0a3a9b2480 | ||
|
|
16e72c3372 | ||
|
|
e4a2a0f64e | ||
|
|
76a90e655a | ||
|
|
5664213959 | ||
|
|
7356c10f34 | ||
|
|
7e847cf8cb | ||
|
|
c421399788 | ||
|
|
b0d068e8a6 | ||
|
|
5fb4f61683 | ||
|
|
5c23f7a3de | ||
|
|
457244d11b | ||
|
|
9673886f93 | ||
|
|
e2b0120f93 | ||
|
|
d6af6ebf45 | ||
|
|
5551a513e9 | ||
|
|
e600b78c74 | ||
|
|
a90106b390 | ||
|
|
bc9c22949d | ||
|
|
ede2591cb0 | ||
|
|
f3075412eb | ||
|
|
8e95e980a3 | ||
|
|
073c39a970 | ||
|
|
acee9daab8 | ||
|
|
35c7836b5c | ||
|
|
89c83f8a55 | ||
|
|
1438cf4e89 | ||
|
|
676b8e1e54 | ||
|
|
78868be772 | ||
|
|
4bd2a4c1c5 | ||
|
|
b5160ed918 | ||
|
|
c5d073b06b | ||
|
|
4052105e1d | ||
|
|
51db1b2f5d | ||
|
|
b9d1c18511 | ||
|
|
9b3dba9eab | ||
|
|
c13216761e | ||
|
|
6f2b45b62d | ||
|
|
3390593bff | ||
|
|
f005e5ccca | ||
|
|
6d0ccda792 | ||
|
|
8c6f394f5b | ||
|
|
2e5345e522 | ||
|
|
2950487f9c | ||
|
|
af21fa3daa | ||
|
|
9c8286a5d1 | ||
|
|
02acceb34e | ||
|
|
a786023e75 | ||
|
|
5008bdaaf0 | ||
|
|
235063c8c3 | ||
|
|
87b2e79389 | ||
|
|
aba91db152 | ||
|
|
260aa55b09 | ||
|
|
c3c1e472e8 | ||
|
|
66f7341cb5 | ||
|
|
f26b26d1a1 | ||
|
|
7585551beb | ||
|
|
c4174b5b36 | ||
|
|
e6be063e9e | ||
|
|
a06b960435 | ||
|
|
0901bb8d76 | ||
|
|
ad20f5a73c | ||
|
|
e2e91a4df9 | ||
|
|
cdda43615a | ||
|
|
52ee8690eb | ||
|
|
e4305ad9e7 | ||
|
|
821141f2a8 | ||
|
|
6e85c6ea91 | ||
|
|
e26fc7fae1 | ||
|
|
f7b01f4fc4 | ||
|
|
c3350bb9bc | ||
|
|
2689e73d3a | ||
|
|
7fda62cbf3 | ||
|
|
40df3a30d8 | ||
|
|
1798765af5 | ||
|
|
c3b277e6a5 | ||
|
|
913c42e7e8 | ||
|
|
f58da45a11 | ||
|
|
415089084e | ||
|
|
e30f625497 | ||
|
|
aa13e60bc0 | ||
|
|
bfbe1085b2 | ||
|
|
7438cb34d1 | ||
|
|
79fd3ad29d | ||
|
|
2ef643f4e0 | ||
|
|
67e79701cf | ||
|
|
a6a5bd5450 | ||
|
|
5594250d59 | ||
|
|
d6e76f7a2a | ||
|
|
6833fc14cb | ||
|
|
0bc486502f | ||
|
|
689f3b6d28 | ||
|
|
4138b056df | ||
|
|
c2fc210868 | ||
|
|
2c493d33dc | ||
|
|
0f08071ca6 | ||
|
|
a157891f9d | ||
|
|
0cefc0e0b8 | ||
|
|
8197615546 | ||
|
|
d7b2e933ec | ||
|
|
4e0ee7dc91 | ||
|
|
83137f0e58 | ||
|
|
266f2fcdf6 | ||
|
|
daae2609d2 | ||
|
|
5c4b4dded3 | ||
|
|
829c145749 | ||
|
|
1f7f9c8964 | ||
|
|
9d0677e170 | ||
|
|
c9485bc8e0 | ||
|
|
048af01795 | ||
|
|
48aee04955 | ||
|
|
d95827770d | ||
|
|
b57d52dc9f | ||
|
|
90da686de3 | ||
|
|
68e8a0201f | ||
|
|
7ccb362968 | ||
|
|
67df63af0f | ||
|
|
7235110b7d | ||
|
|
587b828574 | ||
|
|
96ee94583f | ||
|
|
2129855068 | ||
|
|
8f2f3f348f | ||
|
|
68dfef18c5 | ||
|
|
73f3887941 | ||
|
|
974c4dbce5 | ||
|
|
d7655be7dc | ||
|
|
f386fd5b4d | ||
|
|
4ae3758d20 | ||
|
|
fd6143b304 | ||
|
|
109a45c564 | ||
|
|
cd914c880f | ||
|
|
ab534556a2 | ||
|
|
71811ae156 | ||
|
|
477b4508ba | ||
|
|
981b526e99 | ||
|
|
167be0c24e | ||
|
|
63ff4b372b | ||
|
|
2957a6aa60 | ||
|
|
4b20802a6a | ||
|
|
60001089f4 | ||
|
|
c91caa72d6 | ||
|
|
61cd1a697b | ||
|
|
a8b7cebc09 | ||
|
|
8f5a90dede | ||
|
|
f49bb74c38 | ||
|
|
07fb021049 | ||
|
|
fc19a4de52 | ||
|
|
09402611dc | ||
|
|
e66c5369a7 | ||
|
|
75802c5f66 | ||
|
|
59b77fe567 | ||
|
|
9e6a9f4902 | ||
|
|
a73046aa02 | ||
|
|
7b35591f5d | ||
|
|
ef3507f058 | ||
|
|
3c416fa264 | ||
|
|
d168ec47ce | ||
|
|
8c3e8b596a | ||
|
|
79840550cb | ||
|
|
55a69d45c3 | ||
|
|
75777d65c6 | ||
|
|
87b1da8fdd | ||
|
|
c07933a9bf | ||
|
|
ca6770ec54 | ||
|
|
05c1bf677c | ||
|
|
de8d79983d | ||
|
|
1367fd2b5f | ||
|
|
180ee293ca | ||
|
|
0bf2051bdf | ||
|
|
8a44681803 | ||
|
|
60867fdf39 | ||
|
|
7e05d05d97 | ||
|
|
b2f29a3651 | ||
|
|
45e9ff9539 | ||
|
|
b5333baa86 | ||
|
|
049305c32e | ||
|
|
ff26019b6e | ||
|
|
4138481a1b | ||
|
|
00336c24b9 | ||
|
|
538d8d1ed2 | ||
|
|
f717e650e6 | ||
|
|
ac49f983ee | ||
|
|
a955e9f564 | ||
|
|
8c47c8ae35 | ||
|
|
2206565673 | ||
|
|
72e5599395 | ||
|
|
85a4563123 | ||
|
|
fc3c0c4beb | ||
|
|
053612a924 | ||
|
|
10c86af314 | ||
|
|
72b779de98 | ||
|
|
e4528dc5ef | ||
|
|
83039e63ce | ||
|
|
2d3299cffa | ||
|
|
30d8a3c6e5 | ||
|
|
8245cf5bbb | ||
|
|
3fe80680a0 | ||
|
|
6c99a5b4e6 | ||
|
|
d7c9c77b7b | ||
|
|
b273afdea3 | ||
|
|
e513db9da5 | ||
|
|
b0a15d9d1c | ||
|
|
5cf4dbf2a4 | ||
|
|
b0f6d83277 | ||
|
|
2e68d7bbcb | ||
|
|
8b6c8dc1aa | ||
|
|
c911276cb4 | ||
|
|
4ec8e1ae07 | ||
|
|
5de3f4c24e | ||
|
|
170e5798ec | ||
|
|
b790921e42 | ||
|
|
c1aa14f6ba | ||
|
|
528f2145a4 | ||
|
|
5c21932511 | ||
|
|
56f58284f9 | ||
|
|
c0595760ab | ||
|
|
714d888326 | ||
|
|
42592c5bdf | ||
|
|
1f998ebf76 | ||
|
|
526178ce56 | ||
|
|
8b6dbdff8a | ||
|
|
6fd3c4c179 | ||
|
|
0b4b664265 | ||
|
|
3535aba30f | ||
|
|
21d1fabeec | ||
|
|
eb763a94fb | ||
|
|
5fe27e02ec | ||
|
|
a5d24e6db5 | ||
|
|
18789c231d | ||
|
|
bec9c967b9 | ||
|
|
a7b8752119 | ||
|
|
2c2075fe4a | ||
|
|
c9c918bae1 | ||
|
|
7027e0c02b | ||
|
|
80de57a087 | ||
|
|
7d29dddd22 | ||
|
|
f93e2b8463 | ||
|
|
377428a699 | ||
|
|
db05dbaf82 | ||
|
|
854382dc9a | ||
|
|
428f534c0d | ||
|
|
376a32182b | ||
|
|
2dff3cfce3 | ||
|
|
d332be2971 | ||
|
|
5e65156b54 | ||
|
|
8d34580090 | ||
|
|
eeccba3968 | ||
|
|
d9ec0226fe | ||
|
|
3075ca8fec | ||
|
|
1ea4cb7297 | ||
|
|
5f1f2a933e | ||
|
|
a9555f7bda | ||
|
|
a1c9966d02 | ||
|
|
12df2e7110 | ||
|
|
dd0b3c0680 | ||
|
|
e029f8c61e | ||
|
|
25d2b806ab | ||
|
|
23b6130907 | ||
|
|
8282509e2d | ||
|
|
d3d4fac7d3 | ||
|
|
4fc1ca9eed | ||
|
|
75bae95db7 | ||
|
|
e85a2294d9 | ||
|
|
157655a085 | ||
|
|
250189d895 | ||
|
|
a22f52ddd4 | ||
|
|
e93ea72019 | ||
|
|
6e76a3fdce | ||
|
|
8af0e41e2d | ||
|
|
bdb9f74aca | ||
|
|
eb3d58ed19 | ||
|
|
57e3287821 | ||
|
|
a052283319 | ||
|
|
bc53649ea8 | ||
|
|
f50f17d593 | ||
|
|
0269dfb392 | ||
|
|
18c2513863 | ||
|
|
05c8d6e520 | ||
|
|
caa840849e | ||
|
|
f4d0485019 | ||
|
|
f441ed94f5 | ||
|
|
a80fa4c700 | ||
|
|
e715be3ca1 | ||
|
|
ab361ec2fd | ||
|
|
95255ff3ea | ||
|
|
6e3a6d53d1 | ||
|
|
ae1e65768e | ||
|
|
d679acd863 | ||
|
|
c34011d45f | ||
|
|
65179eaaab | ||
|
|
f75acd318f | ||
|
|
fdfb724109 | ||
|
|
87f1ca19fb | ||
|
|
963854652e | ||
|
|
50def040f2 | ||
|
|
87a93b9a7f | ||
|
|
edcf7b3c78 | ||
|
|
e152dfd4d7 | ||
|
|
071bdea099 | ||
|
|
31f4369236 | ||
|
|
3878b5dbdb | ||
|
|
9fec85b688 | ||
|
|
75c70ff8dc | ||
|
|
2339fcebd1 | ||
|
|
5ccb63d27b | ||
|
|
85f777f683 | ||
|
|
c7382f1e54 | ||
|
|
0677cec709 | ||
|
|
3c9eed894a | ||
|
|
8224f4c8c5 | ||
|
|
aaea58217b | ||
|
|
b528c8ecce | ||
|
|
91185ef847 | ||
|
|
ed27d8d589 | ||
|
|
a160f3fe6c | ||
|
|
1ea5b4fc39 | ||
|
|
6d5e2b8d15 | ||
|
|
4be26fbc22 | ||
|
|
c0de1ae28d | ||
|
|
95856c9ee9 | ||
|
|
d04d02380f | ||
|
|
cc4e4d650b | ||
|
|
62e452c625 | ||
|
|
afb78fa670 | ||
|
|
2b66189590 | ||
|
|
e7454e558d | ||
|
|
1028c19f87 | ||
|
|
a26ab9cc3a | ||
|
|
6fbdc3c1fb | ||
|
|
afb0cabec4 | ||
|
|
8d396c3e2b | ||
|
|
c88bbdabbc | ||
|
|
792b4a4632 | ||
|
|
b581d1c4ad | ||
|
|
aa242e040a | ||
|
|
0c261196a4 | ||
|
|
fe842cb9bc | ||
|
|
3ded54b80f | ||
|
|
4a777e4b01 | ||
|
|
c5534bcd55 | ||
|
|
1b5cda648e | ||
|
|
d106977c34 | ||
|
|
ce9143e4b2 | ||
|
|
c5e3800c34 | ||
|
|
aa338e5519 | ||
|
|
a096b2a413 | ||
|
|
6ea66180d1 | ||
|
|
402bc2d6f3 | ||
|
|
5e79f16e1e | ||
|
|
23c04c8883 | ||
|
|
d5a3c70788 | ||
|
|
02ccb78080 | ||
|
|
07c6f19d5c | ||
|
|
5ebb3f833a | ||
|
|
43bc3517ff | ||
|
|
5eebd42c3c | ||
|
|
98df0f93c3 | ||
|
|
7d3c3f030b | ||
|
|
7535f008b7 | ||
|
|
e5153ddf77 | ||
|
|
ee959809ad | ||
|
|
3fcc438558 | ||
|
|
b453256916 | ||
|
|
49aba49406 | ||
|
|
746daf14f6 | ||
|
|
bd92b8e895 | ||
|
|
37ed627025 | ||
|
|
c7665a1aff | ||
|
|
298a632cac | ||
|
|
0a6d5cf19b | ||
|
|
0e48ae6e4a | ||
|
|
05b2f1b2cc | ||
|
|
b17b0b5144 | ||
|
|
4563c222e9 | ||
|
|
4bff5f2548 | ||
|
|
edad33e662 | ||
|
|
1eecc5fa48 | ||
|
|
0248075479 | ||
|
|
6dbf87a8de | ||
|
|
0fb27f979f | ||
|
|
9013508df1 | ||
|
|
0bd2e5ffef | ||
|
|
de3f96d4a8 | ||
|
|
b0f3505972 | ||
|
|
c4c36d27a2 | ||
|
|
f9751d38db | ||
|
|
ff5d209895 | ||
|
|
c09ed3bb09 | ||
|
|
f6304d0b33 | ||
|
|
73cf3fc503 | ||
|
|
e1865c7267 | ||
|
|
95a17ed04f | ||
|
|
aade8ffb32 | ||
|
|
e7bab1cbbb | ||
|
|
1786bd965b | ||
|
|
5c7aefb8a5 | ||
|
|
37382fbee8 | ||
|
|
9c9c9b7717 | ||
|
|
5b7351e331 | ||
|
|
14a38b43de | ||
|
|
e0f7de37b6 | ||
|
|
1b3adc396f | ||
|
|
c32911755a | ||
|
|
4c98949ba3 | ||
|
|
a22a926199 | ||
|
|
bfefc43522 | ||
|
|
27c1de2c53 | ||
|
|
3d446883c6 | ||
|
|
e7dbea49cd | ||
|
|
5a7ea0b6c2 | ||
|
|
07418a3ac3 | ||
|
|
8f40162974 | ||
|
|
b86202ba98 | ||
|
|
b13e67e061 | ||
|
|
25cccee5d6 | ||
|
|
26f402fd3b | ||
|
|
617d969a10 | ||
|
|
89fb867ab2 | ||
|
|
f461a8e38d | ||
|
|
d58ca3f324 | ||
|
|
07331078c4 | ||
|
|
02e72e015f | ||
|
|
79acf63dbf | ||
|
|
2fa2876090 | ||
|
|
314879a1fe | ||
|
|
b87389e07b | ||
|
|
c8dfa7e21f | ||
|
|
d0f3fb9f61 | ||
|
|
45ac5b68b9 | ||
|
|
8d400b4ffe | ||
|
|
870cc96a1a | ||
|
|
4f6964b04a | ||
|
|
897520f906 | ||
|
|
0c35e79d04 | ||
|
|
3938db1893 | ||
|
|
881da38538 | ||
|
|
60fb22b6c7 | ||
|
|
b9d532aa81 | ||
|
|
ee6ac9e7c8 | ||
|
|
fd3f9b95b9 | ||
|
|
e73b42db7b | ||
|
|
b72426e20f | ||
|
|
cf984e4abe | ||
|
|
1670bf37ab | ||
|
|
79b9be5637 | ||
|
|
7a8cafcd77 | ||
|
|
b9bec82876 | ||
|
|
014155b0a4 | ||
|
|
dc42278943 | ||
|
|
951bbef480 | ||
|
|
df84f7f660 | ||
|
|
028f8fd8f0 | ||
|
|
14c42217c2 | ||
|
|
a464ed9e3f | ||
|
|
b41b3af690 | ||
|
|
db9881d05f | ||
|
|
d982c5c835 | ||
|
|
a179827a35 | ||
|
|
6c9d47cad0 | ||
|
|
9ea47fcc83 | ||
|
|
44033c18fe | ||
|
|
4027629996 | ||
|
|
d0640a4098 | ||
|
|
5dae6a7b91 | ||
|
|
850a3914fd | ||
|
|
a2d5c4ddaa | ||
|
|
2118b42ecb | ||
|
|
0c2d39ba96 | ||
|
|
9cb9308f85 | ||
|
|
d9577fe6a1 | ||
|
|
1fc0fbbf0a | ||
|
|
022d9d15a7 | ||
|
|
be7934f34a | ||
|
|
b34853089e | ||
|
|
d08260a519 | ||
|
|
6f76b3a53d | ||
|
|
86aafcc5d4 | ||
|
|
795f64f20b | ||
|
|
5ca4c7c777 | ||
|
|
7c5454a2c9 | ||
|
|
157f05ead6 | ||
|
|
17dc3f7537 | ||
|
|
dbf32f3138 | ||
|
|
9c4bab3800 | ||
|
|
02970b19b4 | ||
|
|
37fffd639f | ||
|
|
96f2e77972 | ||
|
|
4c5e170b85 | ||
|
|
d57478ee09 | ||
|
|
834fa8dbd4 | ||
|
|
5bca8fc8f7 | ||
|
|
5d5d8b1243 | ||
|
|
8a19d7239d | ||
|
|
87f11a9914 | ||
|
|
524b5c340f | ||
|
|
2fdf6606b3 | ||
|
|
7075d7074c | ||
|
|
c9b54cd4c5 | ||
|
|
552559c118 | ||
|
|
e2fe85a4fb | ||
|
|
9fe054c681 | ||
|
|
d4d792754f | ||
|
|
f503abf5e6 | ||
|
|
d6d15185ea | ||
|
|
e618d454e2 | ||
|
|
527077cef3 | ||
|
|
393f9a4d47 | ||
|
|
bf0de00b72 | ||
|
|
f0911b5eb6 | ||
|
|
6563e5e438 | ||
|
|
2494ecc493 | ||
|
|
62030c4dcc | ||
|
|
440a4b04d5 | ||
|
|
71f88f7218 | ||
|
|
44dbee0008 | ||
|
|
5df84bb7a1 | ||
|
|
286ed609da | ||
|
|
2bd94a7618 | ||
|
|
0ec1744158 | ||
|
|
6b018e5495 | ||
|
|
6b81e3efd8 | ||
|
|
22da2d4bbe | ||
|
|
59d8608830 | ||
|
|
c93f16a598 | ||
|
|
c43b45a0e4 | ||
|
|
3e606c2a49 | ||
|
|
6cb1a31aff | ||
|
|
bd36ab5b6f | ||
|
|
80e72c2146 | ||
|
|
22a97de195 | ||
|
|
8b73d03e7c | ||
|
|
3db2a156bb | ||
|
|
8e1c4446dd | ||
|
|
779065ec91 | ||
|
|
71e4aa9a2f | ||
|
|
6ad387daa3 | ||
|
|
d8f0db6bd6 | ||
|
|
9b531bc09e | ||
|
|
60a50b304a | ||
|
|
fd8ce4b2ec | ||
|
|
899bd28247 | ||
|
|
78945d648f | ||
|
|
6f107ac7a1 | ||
|
|
d78afbfb5c | ||
|
|
bcffae4a95 | ||
|
|
0778277de4 | ||
|
|
ab8d5c9724 | ||
|
|
2ca0ceade4 | ||
|
|
7ed114a858 | ||
|
|
36470f819c | ||
|
|
e922b983dd | ||
|
|
fdcae500b1 | ||
|
|
d72c29bd56 | ||
|
|
8d7947bbda | ||
|
|
c1a251abc9 | ||
|
|
e59af1e437 | ||
|
|
037d3a8fee | ||
|
|
ea14a01f89 | ||
|
|
ce7033943f | ||
|
|
50e134ba91 | ||
|
|
667f378b74 | ||
|
|
7d611a60d8 | ||
|
|
bcee58700a | ||
|
|
08d023092f | ||
|
|
bbbaeab4c7 | ||
|
|
6992484b7f | ||
|
|
2957035b5a | ||
|
|
0fac17ad68 | ||
|
|
c221d00c28 | ||
|
|
107cbeadee | ||
|
|
b5fc96c1cf | ||
|
|
5213c717d9 | ||
|
|
bfe541f83e | ||
|
|
2553eb2825 | ||
|
|
6e31dfc00d | ||
|
|
e2d2e0317c | ||
|
|
da23761f88 | ||
|
|
4a4c9ad049 | ||
|
|
1c3ab898cc | ||
|
|
26c1cee27e | ||
|
|
619f2110e4 | ||
|
|
4c7efdb800 | ||
|
|
a5b1f68f62 | ||
|
|
e2409fdbde | ||
|
|
af70303b32 | ||
|
|
d47e840dbd | ||
|
|
6ca5b32945 | ||
|
|
b69b9a607e | ||
|
|
37a684c7ab | ||
|
|
4e89fd9678 | ||
|
|
419a48cff1 | ||
|
|
e22e390040 | ||
|
|
6b839b7f60 | ||
|
|
d5b6c7f098 | ||
|
|
9e7e5ee726 | ||
|
|
b90a07ca2a | ||
|
|
0e9a169d38 | ||
|
|
88c7f64901 | ||
|
|
33de71d554 | ||
|
|
92155fb742 | ||
|
|
2b06a439cf | ||
|
|
7631adc88f | ||
|
|
85759a636a | ||
|
|
f5f08b979b | ||
|
|
ce74d011f6 | ||
|
|
193ba4c658 | ||
|
|
c4a205908a | ||
|
|
ac17d953c6 | ||
|
|
af0fe655b3 | ||
|
|
83378f27c1 | ||
|
|
30f36060f3 | ||
|
|
f50dccb30b | ||
|
|
0e7c378d4a | ||
|
|
7d46ebd2f4 | ||
|
|
0e839f5e44 | ||
|
|
3842bcf0b1 | ||
|
|
1971cf5942 | ||
|
|
60f03591ef | ||
|
|
d84fe68120 | ||
|
|
46e1a9af86 | ||
|
|
d0aa125248 | ||
|
|
3d31a13508 | ||
|
|
9cfb400bad | ||
|
|
61b75be1a1 | ||
|
|
7841599650 | ||
|
|
49a9d03471 | ||
|
|
ea0a07890c | ||
|
|
78ef1b79af | ||
|
|
877a1ca335 | ||
|
|
e94dbbd3c8 | ||
|
|
7ee28e526e | ||
|
|
72ed82ecd6 | ||
|
|
8b1223e780 | ||
|
|
9b728e5d10 | ||
|
|
951477c58f | ||
|
|
ce204fff2e | ||
|
|
89c75d471b | ||
|
|
8a2bbb1fc2 | ||
|
|
43075088de | ||
|
|
251f88526e | ||
|
|
a7970a1221 | ||
|
|
4c045618e6 | ||
|
|
a828a1bba2 | ||
|
|
4f8d6e7036 | ||
|
|
416d11d1ee | ||
|
|
1edc1edc98 | ||
|
|
e7b6b4dd8d | ||
|
|
799ce8d72c | ||
|
|
9dbea71af5 | ||
|
|
cd3eb8099d | ||
|
|
270f9ab7d2 | ||
|
|
3c9d73533f | ||
|
|
8eb9d4f9ee | ||
|
|
d569be80b3 | ||
|
|
cd5fb9cad6 | ||
|
|
8a62f6c46b | ||
|
|
81cea4f045 | ||
|
|
6b70ee8ba9 | ||
|
|
c1aea16fba | ||
|
|
280510d729 | ||
|
|
c7fd0037bd | ||
|
|
415fe267d2 | ||
|
|
1f8440de57 | ||
|
|
6ce17dd480 | ||
|
|
c2fdad1ab6 | ||
|
|
97571d4363 | ||
|
|
d33cf5ad4e | ||
|
|
24a6b7fd37 | ||
|
|
2469bde7ee | ||
|
|
e9aea7bcc4 | ||
|
|
41c9ed106c | ||
|
|
631b5d0ea6 | ||
|
|
5137572127 | ||
|
|
2c3f6bbcd1 | ||
|
|
fd38ab8170 | ||
|
|
615b7bf5ea | ||
|
|
d1f43f8c64 | ||
|
|
03fe293cd8 | ||
|
|
198462b051 | ||
|
|
7dbf4d5b91 | ||
|
|
c82ee8afbc | ||
|
|
833483b810 | ||
|
|
1a4a4a16c6 | ||
|
|
74f71cfd1c | ||
|
|
ddd6669bbf | ||
|
|
244e300715 | ||
|
|
c8b6d001ef | ||
|
|
5f85f49636 | ||
|
|
bd2413d63c | ||
|
|
09d7cad8c8 | ||
|
|
766d38bcbf | ||
|
|
f8b3e5c8fd | ||
|
|
58510eee50 | ||
|
|
abdc574ea2 | ||
|
|
93c02a72ca | ||
|
|
a7aeecde2e | ||
|
|
5efa755188 | ||
|
|
f69d98460f | ||
|
|
135f25d88b | ||
|
|
685f425e29 | ||
|
|
8e1f679c2a | ||
|
|
fd526b80b7 | ||
|
|
2e3fdaad97 | ||
|
|
f666c3afa3 | ||
|
|
d773c3b3d4 | ||
|
|
cd5b2e264b | ||
|
|
816600535a | ||
|
|
19f50baae4 | ||
|
|
715cf65538 | ||
|
|
f58650effa | ||
|
|
63b2d3ef66 | ||
|
|
d203046666 | ||
|
|
c72ff615ba | ||
|
|
6e2ee15b93 | ||
|
|
f113bad733 | ||
|
|
92cacd7aaa | ||
|
|
6a650d2021 | ||
|
|
52600c9a16 | ||
|
|
923f09d9f4 | ||
|
|
5c16406873 | ||
|
|
2f33e5fba9 | ||
|
|
b7ab9f6f55 | ||
|
|
e9f25e50ac | ||
|
|
e327f03893 | ||
|
|
5858a5fdf9 | ||
|
|
4647be958b | ||
|
|
287e0b4068 | ||
|
|
c76a061740 | ||
|
|
1d77eb293e | ||
|
|
d919eee3b4 | ||
|
|
09fad6a2de | ||
|
|
1ff02c30a6 | ||
|
|
1d4be22da5 | ||
|
|
2889b10e0d | ||
|
|
23afe2654e | ||
|
|
8885f81770 | ||
|
|
47ec30919c | ||
|
|
a0c0a0f074 | ||
|
|
13a15e4f56 | ||
|
|
b962e22c04 | ||
|
|
eaa9c43afe | ||
|
|
a304f8d42e | ||
|
|
4d651aec24 | ||
|
|
0c1fa72556 | ||
|
|
f88b206756 | ||
|
|
67f415c2e3 | ||
|
|
8f0db2ae3e | ||
|
|
ee6984de00 | ||
|
|
c07a0d730d | ||
|
|
90262d663f | ||
|
|
fc6f9cfbcd | ||
|
|
bfdd5665b7 | ||
|
|
6ac0556601 | ||
|
|
596d61f667 | ||
|
|
c72a7faa3d | ||
|
|
0d18dfd609 | ||
|
|
044085bf55 | ||
|
|
39c1d9eb12 | ||
|
|
50d98b2a83 | ||
|
|
903a22973f | ||
|
|
f0a33b360d | ||
|
|
b1ea9aa1e2 | ||
|
|
7cbcdc62ae | ||
|
|
3277896ff1 | ||
|
|
7968d7d355 | ||
|
|
f654826003 | ||
|
|
c3e1e3e669 | ||
|
|
905e29c8f8 | ||
|
|
8fd09abda6 | ||
|
|
b63cb3c493 | ||
|
|
629bf4230e | ||
|
|
cd4327deba | ||
|
|
d086bb9465 | ||
|
|
c2a066fc71 | ||
|
|
fa5387e814 | ||
|
|
980d57958b | ||
|
|
32370a0a7e | ||
|
|
206d34afbb | ||
|
|
2757f1b9c1 | ||
|
|
75f983f7a7 | ||
|
|
4084373e2b | ||
|
|
a5e8bcfa8c |
No files matched your search
@@ -24,7 +24,6 @@ DEFAULT_FROM_EMAIL=service@ksp.de
|
|||||||
|
|
||||||
# Proxy
|
# Proxy
|
||||||
PROXY=CHANGE_ME
|
PROXY=CHANGE_ME
|
||||||
MAP_PROXY_HOST_WHITELIST=CHANGE_ME_1,CHANGE_ME_2
|
|
||||||
GEOPORTAL_RLP_USER=CHANGE_ME
|
GEOPORTAL_RLP_USER=CHANGE_ME
|
||||||
GEOPORTAL_RLP_PASSWORD=CHANGE_ME
|
GEOPORTAL_RLP_PASSWORD=CHANGE_ME
|
||||||
|
|
||||||
@@ -38,7 +37,6 @@ SSO_SERVER_BASE_URL=https://login.naturschutz.rlp.de
|
|||||||
OAUTH_CODE_VERIFIER=CHANGE_ME
|
OAUTH_CODE_VERIFIER=CHANGE_ME
|
||||||
OAUTH_CLIENT_ID=CHANGE_ME
|
OAUTH_CLIENT_ID=CHANGE_ME
|
||||||
OAUTH_CLIENT_SECRET=CHANGE_ME
|
OAUTH_CLIENT_SECRET=CHANGE_ME
|
||||||
PROPAGATION_SECRET=CHANGE_ME
|
|
||||||
|
|
||||||
# RabbitMQ
|
# RabbitMQ
|
||||||
## For connections to EGON
|
## For connections to EGON
|
||||||
|
|||||||
+2
-2
@@ -10,6 +10,6 @@ from analysis.views import *
|
|||||||
|
|
||||||
app_name = "analysis"
|
app_name = "analysis"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("reports/", ReportIndexView.as_view(), name="reports"),
|
path("reports/", index_reports_view, name="reports"),
|
||||||
path("reports/<id>", ReportDetailView.as_view(), name="report-detail"),
|
path("reports/<id>", detail_report_view, name="report-detail"),
|
||||||
]
|
]
|
||||||
+35
-94
@@ -1,12 +1,8 @@
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
|
||||||
from django.views.generic import DetailView
|
|
||||||
|
|
||||||
from analysis.forms import TimespanReportForm
|
from analysis.forms import TimespanReportForm
|
||||||
from analysis.utils.excel.excel import TempExcelFile
|
from analysis.utils.excel.excel import TempExcelFile
|
||||||
@@ -46,112 +42,57 @@ def index_reports_view(request: HttpRequest):
|
|||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
class ReportIndexView(LoginRequiredMixin, View):
|
|
||||||
@method_decorator(conservation_office_group_required)
|
|
||||||
def get(self, request: HttpRequest) -> HttpResponse:
|
|
||||||
|
|
||||||
"""
|
@login_required
|
||||||
|
@conservation_office_group_required
|
||||||
|
def detail_report_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders the detailed report for a conservation office
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The conservation_office KonovaCode id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
template = "analysis/reports/index.html"
|
# Try to resolve the requested office id
|
||||||
form = TimespanReportForm(None)
|
cons_office = get_object_or_404(
|
||||||
context = {
|
KonovaCode,
|
||||||
"form": form
|
id=id
|
||||||
}
|
)
|
||||||
context = BaseContext(request, context).context
|
# Try to resolve the date parameters into Date objects -> redirect if this fails
|
||||||
return render(request, template, context)
|
try:
|
||||||
|
df = request.GET.get("df", None)
|
||||||
@method_decorator(conservation_office_group_required)
|
dt = request.GET.get("dt", None)
|
||||||
def post(self, request: HttpRequest) -> HttpResponse:
|
date_from = timezone.make_aware(timezone.datetime.fromisoformat(df))
|
||||||
|
date_to = timezone.make_aware(timezone.datetime.fromisoformat(dt))
|
||||||
"""
|
except ValueError:
|
||||||
|
messages.error(
|
||||||
Args:
|
request,
|
||||||
request (HttpRequest): The incoming request
|
PARAMS_INVALID,
|
||||||
|
extra_tags="danger",
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
template = "analysis/reports/index.html"
|
|
||||||
form = TimespanReportForm(request.POST or None)
|
|
||||||
if form.is_valid():
|
|
||||||
redirect_url = form.save()
|
|
||||||
return redirect(redirect_url)
|
|
||||||
else:
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
FORM_INVALID,
|
|
||||||
extra_tags="danger",
|
|
||||||
)
|
|
||||||
context = {
|
|
||||||
"form": form
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
|
|
||||||
class ReportDetailView(LoginRequiredMixin, DetailView):
|
|
||||||
|
|
||||||
@method_decorator(conservation_office_group_required)
|
|
||||||
def get(self, request: HttpRequest, id: str):
|
|
||||||
""" Renders the detailed report for a conservation office
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The conservation_office KonovaCode id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Try to resolve the requested office id
|
|
||||||
cons_office = get_object_or_404(
|
|
||||||
KonovaCode,
|
|
||||||
id=id
|
|
||||||
)
|
)
|
||||||
# Try to resolve the date parameters into Date objects -> redirect if this fails
|
return redirect("analysis:reports")
|
||||||
try:
|
|
||||||
df = request.GET.get("df", None)
|
|
||||||
dt = request.GET.get("dt", None)
|
|
||||||
date_from = timezone.make_aware(timezone.datetime.fromisoformat(df))
|
|
||||||
date_to = timezone.make_aware(timezone.datetime.fromisoformat(dt))
|
|
||||||
except ValueError:
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
PARAMS_INVALID,
|
|
||||||
extra_tags="danger",
|
|
||||||
)
|
|
||||||
return redirect("analysis:reports")
|
|
||||||
|
|
||||||
# Check whether the html default rendering is requested or an alternative
|
# Check whether the html default rendering is requested or an alternative
|
||||||
format_param = request.GET.get("format", "html")
|
format_param = request.GET.get("format", "html")
|
||||||
report = TimespanReport(id, date_from, date_to)
|
report = TimespanReport(id, date_from, date_to)
|
||||||
|
|
||||||
if format_param == "html":
|
if format_param == "html":
|
||||||
return self.__handle_html_format(request, report, cons_office)
|
|
||||||
elif format_param == "excel":
|
|
||||||
return self.__handle_excel_format(report, cons_office, df, dt)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def __handle_html_format(self, request, report: TimespanReport, office: KonovaCode):
|
|
||||||
template = "analysis/reports/detail.html"
|
template = "analysis/reports/detail.html"
|
||||||
context = {
|
context = {
|
||||||
"office": office,
|
"office": cons_office,
|
||||||
"report": report,
|
"report": report,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, template, context)
|
return render(request, template, context)
|
||||||
|
elif format_param == "excel":
|
||||||
def __handle_excel_format(self, report: TimespanReport, office: KonovaCode, df: str, dt: str):
|
|
||||||
file = TempExcelFile(report.excel_template_path, report.excel_map)
|
file = TempExcelFile(report.excel_template_path, report.excel_map)
|
||||||
response = HttpResponse(
|
response = HttpResponse(
|
||||||
content=file.stream,
|
content=file.stream,
|
||||||
content_type="application/ms-excel",
|
content_type="application/ms-excel",
|
||||||
)
|
)
|
||||||
response['Content-Disposition'] = f'attachment; filename={office.long_name}_{df}_{dt}.xlsx'
|
response['Content-Disposition'] = f'attachment; filename={cons_office.long_name}_{df}_{dt}.xlsx'
|
||||||
return response
|
return response
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 6.0.5 on 2026-05-10 07:18
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('api', '0003_oauthtoken'),
|
|
||||||
('user', '0010_user_sso_identifier'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ExternalIdentifier',
|
|
||||||
fields=[
|
|
||||||
('external_id', models.CharField(db_comment='Identifier from a source system', max_length=255, primary_key=True, serialize=False)),
|
|
||||||
('internal_id', models.UUIDField(db_comment='Identifier in konova')),
|
|
||||||
('created', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='user.useractionlogentry')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -5,5 +5,4 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 21.01.22
|
Created on: 21.01.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from .token import *
|
from .token import *
|
||||||
from .external_identifier import *
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 10.05.26
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalIdentifier(models.Model):
|
|
||||||
""" Holds a lookup to match a given external identifier against the internal identifier in konova.
|
|
||||||
|
|
||||||
Relevant in cases of API transmitted entries, which are updates using external identifiers instead of
|
|
||||||
the internal ones directly.
|
|
||||||
|
|
||||||
"""
|
|
||||||
external_id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
db_comment="Identifier from a source system"
|
|
||||||
)
|
|
||||||
internal_id = models.UUIDField(
|
|
||||||
db_comment="Identifier in konova"
|
|
||||||
)
|
|
||||||
created = models.ForeignKey(
|
|
||||||
"user.UserActionLogEntry",
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
related_name='+'
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.external_id} -> {self.internal_id}"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def resolve_external_identifier(external_identifier: str):
|
|
||||||
""" Returns a ExternalIdentifier object, if the given parameter could be resolved as an external identifier.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
external_identifier (str): An external identifier.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ExternalIdentifier | None
|
|
||||||
"""
|
|
||||||
if external_identifier:
|
|
||||||
try:
|
|
||||||
obj = ExternalIdentifier.objects.get(external_id=external_identifier)
|
|
||||||
return obj
|
|
||||||
except ExternalIdentifier.DoesNotExist:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
+1
-23
@@ -51,7 +51,7 @@ class APIUserToken(models.Model):
|
|||||||
if token_obj.valid_until is not None and token_obj.valid_until < _today:
|
if token_obj.valid_until is not None and token_obj.valid_until < _today:
|
||||||
raise PermissionError("Token validity expired")
|
raise PermissionError("Token validity expired")
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise PermissionError("Token unknown")
|
raise PermissionError("Credentials invalid")
|
||||||
return token_obj.user
|
return token_obj.user
|
||||||
|
|
||||||
|
|
||||||
@@ -155,25 +155,3 @@ class OAuthToken(UuidModel):
|
|||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def revoke(self) -> int:
|
|
||||||
""" Revokes the OAuth2 token of the user
|
|
||||||
|
|
||||||
(/o/revoke_token/ indeed removes the corresponding access token on provider side and invalidates the
|
|
||||||
submitted refresh token in one step)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
revocation_status_code (int): HTTP status code for revocation of refresh_token
|
|
||||||
"""
|
|
||||||
revoke_url = f"{SSO_SERVER_BASE}o/revoke_token/"
|
|
||||||
token = self.refresh_token
|
|
||||||
revocation_status_code = requests.post(
|
|
||||||
revoke_url,
|
|
||||||
data={
|
|
||||||
'token': token,
|
|
||||||
'token_type_hint': "refresh_token",
|
|
||||||
},
|
|
||||||
auth=(OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET),
|
|
||||||
).status_code
|
|
||||||
|
|
||||||
return revocation_status_code
|
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
"is_coherence_keeping": false,
|
"is_coherence_keeping": false,
|
||||||
"is_pik": false,
|
"is_pik": false,
|
||||||
"intervention": "MUST_BE_SET_IN_TEST",
|
"intervention": "MUST_BE_SET_IN_TEST",
|
||||||
"external_identifier": "LOREMIPSUM-123",
|
|
||||||
"before_states": [
|
"before_states": [
|
||||||
],
|
],
|
||||||
"after_states": [
|
"after_states": [
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_ecoaccount",
|
"title": "Test_ecoaccount",
|
||||||
"external_identifier": "LOREMIPSUM-1234",
|
|
||||||
"deductable_surface": 10000.0,
|
"deductable_surface": 10000.0,
|
||||||
"is_pik": false,
|
"is_pik": false,
|
||||||
"responsible": {
|
"responsible": {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_ema",
|
"title": "Test_ema",
|
||||||
"external_identifier": "LOREMIPSUM-1235",
|
|
||||||
"is_pik": false,
|
"is_pik": false,
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"conservation_office": null,
|
"conservation_office": null,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_intervention",
|
"title": "Test_intervention",
|
||||||
"external_identifier": "LOREMIPSUM-1236",
|
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"registration_office": null,
|
"registration_office": null,
|
||||||
"registration_file_number": null,
|
"registration_file_number": null,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import json
|
|||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from api.models import ExternalIdentifier
|
|
||||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
||||||
|
|
||||||
|
|
||||||
@@ -43,22 +42,7 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
response = self._run_create_request(url, post_body)
|
response = self._run_create_request(url, post_body)
|
||||||
self.assertEqual(response.status_code, 200, msg=response.content)
|
self.assertEqual(response.status_code, 200, msg=response.content)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
_id = content.get("id", None)
|
self.assertIsNotNone(content.get("id", None), msg=response.content)
|
||||||
self.assertIsNotNone(_id, msg=response.content)
|
|
||||||
return _id
|
|
||||||
|
|
||||||
def _test_external_identifier_created(self, internal_id, external_id):
|
|
||||||
""" Tests whether an external identifier has been created
|
|
||||||
|
|
||||||
Args:
|
|
||||||
internal_id ():
|
|
||||||
external_id ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
external_identifier = ExternalIdentifier.objects.get(internal_id=internal_id)
|
|
||||||
self.assertEqual(external_identifier.external_id, external_id)
|
|
||||||
|
|
||||||
def test_create_intervention(self):
|
def test_create_intervention(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -70,8 +54,7 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
json_file_path = "api/tests/v1/create/intervention_create_post_body.json"
|
json_file_path = "api/tests/v1/create/intervention_create_post_body.json"
|
||||||
with open(json_file_path) as json_file:
|
with open(json_file_path) as json_file:
|
||||||
post_body = json.load(fp=json_file)
|
post_body = json.load(fp=json_file)
|
||||||
internal_id = self._test_create_object(url, post_body)
|
self._test_create_object(url, post_body)
|
||||||
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
|
||||||
|
|
||||||
def test_create_compensation(self):
|
def test_create_compensation(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -88,14 +71,13 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
# Expect this first request to fail, since user has no shared access on the intervention, we want to create
|
# Expect this first request to fail, since user has no shared access on the intervention, we want to create
|
||||||
# a compensation for
|
# a compensation for
|
||||||
response = self._run_create_request(url, post_body)
|
response = self._run_create_request(url, post_body)
|
||||||
self.assertEqual(response.status_code, 400, msg=response.content)
|
self.assertEqual(response.status_code, 500, msg=response.content)
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)
|
self.assertGreater(len(content.get("errors", [])), 0, msg=response.content)
|
||||||
|
|
||||||
# Add the user to the shared users of the intervention and try again! Now everything should work as expected.
|
# Add the user to the shared users of the intervention and try again! Now everything should work as expected.
|
||||||
self.intervention.users.add(self.superuser)
|
self.intervention.users.add(self.superuser)
|
||||||
internal_id = self._test_create_object(url, post_body)
|
self._test_create_object(url, post_body)
|
||||||
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
|
||||||
|
|
||||||
def test_create_eco_account(self):
|
def test_create_eco_account(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -107,8 +89,7 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
json_file_path = "api/tests/v1/create/ecoaccount_create_post_body.json"
|
json_file_path = "api/tests/v1/create/ecoaccount_create_post_body.json"
|
||||||
with open(json_file_path) as json_file:
|
with open(json_file_path) as json_file:
|
||||||
post_body = json.load(fp=json_file)
|
post_body = json.load(fp=json_file)
|
||||||
internal_id = self._test_create_object(url, post_body)
|
self._test_create_object(url, post_body)
|
||||||
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
|
||||||
|
|
||||||
def test_create_ema(self):
|
def test_create_ema(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
@@ -120,8 +101,7 @@ class APIV1CreateTestCase(BaseAPIV1TestCase):
|
|||||||
json_file_path = "api/tests/v1/create/ema_create_post_body.json"
|
json_file_path = "api/tests/v1/create/ema_create_post_body.json"
|
||||||
with open(json_file_path) as json_file:
|
with open(json_file_path) as json_file:
|
||||||
post_body = json.load(fp=json_file)
|
post_body = json.load(fp=json_file)
|
||||||
internal_id = self._test_create_object(url, post_body)
|
self._test_create_object(url, post_body)
|
||||||
self._test_external_identifier_created(internal_id, post_body["properties"]["external_identifier"])
|
|
||||||
|
|
||||||
def test_create_deduction(self):
|
def test_create_deduction(self):
|
||||||
""" Tests api creation
|
""" Tests api creation
|
||||||
|
|||||||
@@ -31,10 +31,9 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
super().setUpTestData()
|
super().setUpTestData()
|
||||||
|
|
||||||
def _run_share_request(self, url, user_list: list, team_list: list):
|
def _run_share_request(self, url, user_list: list):
|
||||||
data = {
|
data = {
|
||||||
"users": user_list,
|
"users": user_list
|
||||||
"teams": team_list
|
|
||||||
}
|
}
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
@@ -59,22 +58,16 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
self.superuser.username,
|
self.superuser.username,
|
||||||
self.user.username,
|
self.user.username,
|
||||||
]
|
]
|
||||||
team_list = [
|
|
||||||
str(self.team.id),
|
|
||||||
]
|
|
||||||
|
|
||||||
response = self._run_share_request(url, user_list, team_list)
|
response = self._run_share_request(url, user_list)
|
||||||
|
|
||||||
# Must fail, since performing user has no access on requested object
|
# Must fail, since performing user has no access on requested object
|
||||||
self.assertEqual(response.status_code, 500)
|
self.assertEqual(response.status_code, 500)
|
||||||
self.assertTrue(len(json.loads(response.content.decode("utf-8")).get("errors", [])) > 0)
|
self.assertTrue(len(json.loads(response.content.decode("utf-8")).get("errors", [])) > 0)
|
||||||
|
|
||||||
# Add performing user to shared access users, switch from team id to team name and rerun the request
|
# Add performing user to shared access users and rerun the request
|
||||||
obj.users.add(self.superuser)
|
obj.users.add(self.superuser)
|
||||||
team_list = [
|
response = self._run_share_request(url, user_list)
|
||||||
self.team.name
|
|
||||||
]
|
|
||||||
response = self._run_share_request(url, user_list, team_list)
|
|
||||||
|
|
||||||
shared_users = obj.shared_users
|
shared_users = obj.shared_users
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
@@ -91,14 +84,14 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
share_url = reverse("api:v1:intervention-share", args=(self.intervention.id,))
|
share_url = reverse("api:v1:intervention-share", args=(self.intervention.id,))
|
||||||
# Expect the first request to work properly
|
# Expect the first request to work properly
|
||||||
self.intervention.users.add(self.superuser)
|
self.intervention.users.add(self.superuser)
|
||||||
response = self._run_share_request(share_url, [self.superuser.username], [str(self.team.id)])
|
response = self._run_share_request(share_url, [self.superuser.username])
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
# Change the token
|
# Change the token
|
||||||
self.header_data["HTTP_ksptoken"] = f"{self.superuser.api_token.token}__X"
|
self.header_data["HTTP_ksptoken"] = f"{self.superuser.api_token.token}__X"
|
||||||
|
|
||||||
# Expect the request to fail now
|
# Expect the request to fail now
|
||||||
response = self._run_share_request(share_url, [self.superuser.username], [])
|
response = self._run_share_request(share_url, [self.superuser.username])
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_api_intervention_sharing(self):
|
def test_api_intervention_sharing(self):
|
||||||
@@ -151,11 +144,11 @@ class APIV1SharingTestCase(BaseAPIV1TestCase):
|
|||||||
self.assertEqual(self.intervention.users.count(), 1)
|
self.assertEqual(self.intervention.users.count(), 1)
|
||||||
|
|
||||||
# Try to add another user via API -> must work!
|
# Try to add another user via API -> must work!
|
||||||
response = self._run_share_request(share_url, [self.superuser.username, self.user.username], [])
|
response = self._run_share_request(share_url, [self.superuser.username, self.user.username])
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(self.intervention.users.count(), 2)
|
self.assertEqual(self.intervention.users.count(), 2)
|
||||||
|
|
||||||
# Now try to remove the user again -> expect no changes at all to the shared user list
|
# Now try to remove the user again -> expect no changes at all to the shared user list
|
||||||
response = self._run_share_request(share_url, [self.superuser.username], [])
|
response = self._run_share_request(share_url, [self.superuser.username])
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(self.intervention.users.count(), 2)
|
self.assertEqual(self.intervention.users.count(), 2)
|
||||||
@@ -44,7 +44,6 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "TEST_compensation_CHANGED",
|
"title": "TEST_compensation_CHANGED",
|
||||||
"external_identifier": "LOREMIPSUM-123_CHANGED",
|
|
||||||
"is_cef": true,
|
"is_cef": true,
|
||||||
"is_coherence_keeping": true,
|
"is_coherence_keeping": true,
|
||||||
"is_pik": true,
|
"is_pik": true,
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "TEST_account_CHANGED",
|
"title": "TEST_account_CHANGED",
|
||||||
"external_identifier": "LOREMIPSUM-1234_CHANGED",
|
|
||||||
"deductable_surface": "100000.0",
|
"deductable_surface": "100000.0",
|
||||||
"is_pik": true,
|
"is_pik": true,
|
||||||
"responsible": {
|
"responsible": {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "TEST_EMA_CHANGED",
|
"title": "TEST_EMA_CHANGED",
|
||||||
"external_identifier": "LOREMIPSUM-1235_CHANGED",
|
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"conservation_office": null,
|
"conservation_office": null,
|
||||||
"conservation_file_number": "TEST_CHANGED",
|
"conservation_file_number": "TEST_CHANGED",
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"title": "Test_intervention_CHANGED",
|
"title": "Test_intervention_CHANGED",
|
||||||
"external_identifier": "LOREMIPSUM-1236_CHANGED",
|
|
||||||
"responsible": {
|
"responsible": {
|
||||||
"registration_office": null,
|
"registration_office": null,
|
||||||
"registration_file_number": "CHANGED",
|
"registration_file_number": "CHANGED",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from django.contrib.gis import geos
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
from api.tests.v1.share.test_api_sharing import BaseAPIV1TestCase
|
||||||
from konova.models import Geometry
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
|
|
||||||
|
|
||||||
class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
||||||
@@ -64,7 +64,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.intervention.geometry.geom)
|
self.assertEqual(put_geom, self.intervention.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.intervention.title)
|
self.assertEqual(put_props["title"], self.intervention.title)
|
||||||
self.assertNotEqual(modified_on, self.intervention.modified)
|
self.assertNotEqual(modified_on, self.intervention.modified)
|
||||||
@@ -94,7 +94,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.compensation.geometry.geom)
|
self.assertEqual(put_geom, self.compensation.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.compensation.title)
|
self.assertEqual(put_props["title"], self.compensation.title)
|
||||||
self.assertNotEqual(modified_on, self.compensation.modified)
|
self.assertNotEqual(modified_on, self.compensation.modified)
|
||||||
@@ -124,7 +124,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.eco_account.geometry.geom)
|
self.assertEqual(put_geom, self.eco_account.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.eco_account.title)
|
self.assertEqual(put_props["title"], self.eco_account.title)
|
||||||
self.assertNotEqual(modified_on, self.eco_account.modified)
|
self.assertNotEqual(modified_on, self.eco_account.modified)
|
||||||
@@ -156,7 +156,7 @@ class APIV1UpdateTestCase(BaseAPIV1TestCase):
|
|||||||
|
|
||||||
put_props = put_body["properties"]
|
put_props = put_body["properties"]
|
||||||
put_geom = geos.fromstr(json.dumps(put_body))
|
put_geom = geos.fromstr(json.dumps(put_body))
|
||||||
put_geom = Geometry.cast_to_rlp_srid(put_geom)
|
put_geom.transform(DEFAULT_SRID_RLP)
|
||||||
self.assertEqual(put_geom, self.ema.geometry.geom)
|
self.assertEqual(put_geom, self.ema.geometry.geom)
|
||||||
self.assertEqual(put_props["title"], self.ema.title)
|
self.assertEqual(put_props["title"], self.ema.title)
|
||||||
self.assertNotEqual(modified_on, self.ema.modified)
|
self.assertNotEqual(modified_on, self.ema.modified)
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ Created on: 21.01.22
|
|||||||
"""
|
"""
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from api.views.method_views import generate_new_token_view
|
||||||
|
|
||||||
app_name = "api"
|
app_name = "api"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("v1/", include("api.urls.v1.urls", namespace="v1")),
|
path("v1/", include("api.urls.v1.urls", namespace="v1")),
|
||||||
|
path("token/generate", generate_new_token_view, name="generate-new-token"),
|
||||||
]
|
]
|
||||||
@@ -6,17 +6,14 @@ Created on: 24.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import uuid
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
|
||||||
from django.contrib.gis import geos
|
from django.contrib.gis import geos
|
||||||
from django.contrib.gis.geos import GEOSGeometry
|
from django.contrib.gis.geos import GEOSGeometry
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from api.models import ExternalIdentifier
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
from konova.models import Geometry
|
|
||||||
from konova.utils.message_templates import DATA_UNSHARED
|
from konova.utils.message_templates import DATA_UNSHARED
|
||||||
|
|
||||||
|
|
||||||
@@ -79,14 +76,6 @@ class AbstractModelAPISerializer:
|
|||||||
del self.lookup["id"]
|
del self.lookup["id"]
|
||||||
else:
|
else:
|
||||||
# Return certain object
|
# Return certain object
|
||||||
## But first check, whether this is an external identifier ...
|
|
||||||
try:
|
|
||||||
## If we can find this _id on our ExternalIdentifier model, we need to map it on the internal id
|
|
||||||
ext_id = ExternalIdentifier.objects.get(external_id=_id)
|
|
||||||
_id = ext_id.internal_id
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
# If we did not find it, we assume that this is already an internal id. (Or it does not exist at all)
|
|
||||||
pass
|
|
||||||
self.lookup["id"] = _id
|
self.lookup["id"] = _id
|
||||||
|
|
||||||
self.shared_lookup = Q(
|
self.shared_lookup = Q(
|
||||||
@@ -156,8 +145,8 @@ class AbstractModelAPISerializer:
|
|||||||
if isinstance(geojson, dict):
|
if isinstance(geojson, dict):
|
||||||
geojson = json.dumps(geojson)
|
geojson = json.dumps(geojson)
|
||||||
geometry = geos.fromstr(geojson)
|
geometry = geos.fromstr(geojson)
|
||||||
geometry = Geometry.cast_to_rlp_srid(geometry)
|
if geometry.srid != DEFAULT_SRID_RLP:
|
||||||
geometry = Geometry.cast_to_multipolygon(geometry)
|
geometry.transform(DEFAULT_SRID_RLP)
|
||||||
return geometry
|
return geometry
|
||||||
|
|
||||||
def _get_obj_from_db(self, id, user):
|
def _get_obj_from_db(self, id, user):
|
||||||
@@ -172,21 +161,6 @@ class AbstractModelAPISerializer:
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not id:
|
|
||||||
raise ValueError("No id provided. The id is expected to live in the URL!")
|
|
||||||
|
|
||||||
# First if there is an external identifier linked to an internal one, so we can continue with the internal
|
|
||||||
try:
|
|
||||||
ext_id = ExternalIdentifier.objects.get(external_id=id)
|
|
||||||
id = ext_id.internal_id
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
# Not found as external id - let's check whether this is a valid uuid and therefore
|
|
||||||
# potentially an internal id
|
|
||||||
try:
|
|
||||||
uuid.UUID(id)
|
|
||||||
except ValueError:
|
|
||||||
raise AssertionError(f"'{id}' is neither a known external identifier nor a valid uuid.")
|
|
||||||
|
|
||||||
obj = self.model.objects.get(
|
obj = self.model.objects.get(
|
||||||
id=id,
|
id=id,
|
||||||
deleted__isnull=True,
|
deleted__isnull=True,
|
||||||
@@ -210,33 +184,3 @@ class AbstractModelAPISerializer:
|
|||||||
obj (Intervention)
|
obj (Intervention)
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("Must be implemented in subclasses")
|
raise NotImplementedError("Must be implemented in subclasses")
|
||||||
|
|
||||||
|
|
||||||
def _geometry_conflicts_to_list(self, geometry) -> list:
|
|
||||||
""" Serializes geometry conflict ids into dict
|
|
||||||
|
|
||||||
Args:
|
|
||||||
geometry (Geometry): The geometry to fetch geometry conflicts from
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ids (list): Serialized geometry conflicts as dict objects inside a list
|
|
||||||
"""
|
|
||||||
ids = []
|
|
||||||
conflict_geometries = geometry.get_conflict_geometries()
|
|
||||||
for geom in conflict_geometries:
|
|
||||||
try:
|
|
||||||
data = geom.get_data_objects(["identifier", "id"])
|
|
||||||
if len(data) == 0:
|
|
||||||
# expected behaviour in case of deleted data object
|
|
||||||
continue
|
|
||||||
data = data[0]
|
|
||||||
except KeyError:
|
|
||||||
raise AssertionError(f"Geometry {geom.id} is not attached to any entries. Contact an admin!")
|
|
||||||
ids.append(
|
|
||||||
{
|
|
||||||
"identifier": data["identifier"],
|
|
||||||
"id": data["id"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return ids
|
|
||||||
@@ -88,11 +88,6 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
# Nothing to do here
|
# Nothing to do here
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
# Transform a potential external identifier into an internal one
|
|
||||||
intervention_ext_id = self._get_external_identifier(intervention_id)
|
|
||||||
if intervention_ext_id:
|
|
||||||
intervention_id = intervention_ext_id.internal_id
|
|
||||||
|
|
||||||
intervention = Intervention.objects.get(
|
intervention = Intervention.objects.get(
|
||||||
id=intervention_id,
|
id=intervention_id,
|
||||||
)
|
)
|
||||||
@@ -119,10 +114,6 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
external_identifier = properties.get("external_identifier", None)
|
|
||||||
self._check_external_identifier_on_entry_creation(external_identifier)
|
|
||||||
|
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_cef = properties["is_cef"]
|
obj.is_cef = properties["is_cef"]
|
||||||
@@ -138,7 +129,6 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
self._set_external_identifier(obj.id, properties.get("external_identifier", None), obj.created)
|
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
@@ -180,7 +170,6 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
self._set_external_identifier(obj.id, properties.get("external_identifier", None), update_action)
|
|
||||||
obj.log.add(update_action)
|
obj.log.add(update_action)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -62,14 +62,6 @@ class DeductionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
if surface <= 0:
|
if surface <= 0:
|
||||||
raise ValueError("Surface must be > 0 m²")
|
raise ValueError("Surface must be > 0 m²")
|
||||||
|
|
||||||
# Check if external identifiers need to be mapped onto internal ones
|
|
||||||
acc_ext_id = self._get_external_identifier(acc_id)
|
|
||||||
intervention_ext_id = self._get_external_identifier(intervention_id)
|
|
||||||
if acc_ext_id:
|
|
||||||
acc_id = acc_ext_id.internal_id
|
|
||||||
if intervention_ext_id:
|
|
||||||
intervention_id = intervention_ext_id.internal_id
|
|
||||||
|
|
||||||
acc = EcoAccount.objects.get(
|
acc = EcoAccount.objects.get(
|
||||||
id=acc_id,
|
id=acc_id,
|
||||||
deleted__isnull=True,
|
deleted__isnull=True,
|
||||||
|
|||||||
@@ -121,9 +121,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj = self._initialize_objects(json_model, user)
|
obj = self._initialize_objects(json_model, user)
|
||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model.get("properties", None)
|
properties = json_model["properties"]
|
||||||
if not properties:
|
|
||||||
raise AssertionError("No 'properties' found in payload!")
|
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_pik = properties.get("is_pik", False)
|
obj.is_pik = properties.get("is_pik", False)
|
||||||
@@ -149,7 +147,6 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
self._set_external_identifier(obj.id, properties.get("external_identifier", None), obj.created)
|
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
obj.users.add(user)
|
obj.users.add(user)
|
||||||
|
|
||||||
@@ -175,10 +172,6 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
external_identifier = properties.get("external_identifier", None)
|
|
||||||
self._check_external_identifier_on_entry_creation(external_identifier)
|
|
||||||
|
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_pik = properties.get("is_pik", False)
|
obj.is_pik = properties.get("is_pik", False)
|
||||||
obj.deductable_surface = float(properties["deductable_surface"])
|
obj.deductable_surface = float(properties["deductable_surface"])
|
||||||
@@ -199,7 +192,6 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
self._set_external_identifier(obj.id, external_identifier, update_action)
|
|
||||||
obj.log.add(update_action)
|
obj.log.add(update_action)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -104,10 +104,6 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
external_identifier = properties.get("external_identifier", None)
|
|
||||||
self._check_external_identifier_on_entry_creation(external_identifier)
|
|
||||||
|
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties["title"]
|
obj.title = properties["title"]
|
||||||
obj.is_pik = properties.get("is_pik", False)
|
obj.is_pik = properties.get("is_pik", False)
|
||||||
@@ -123,7 +119,6 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
self._set_external_identifier(obj.id, external_identifier, obj.created)
|
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
obj.users.add(user)
|
obj.users.add(user)
|
||||||
|
|
||||||
@@ -166,7 +161,6 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
|
|||||||
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
|
||||||
obj = self._set_deadlines(obj, properties["deadlines"])
|
obj = self._set_deadlines(obj, properties["deadlines"])
|
||||||
|
|
||||||
self._set_external_identifier(obj.id, properties.get("external_identifier", None), update_action)
|
|
||||||
obj.log.add(update_action)
|
obj.log.add(update_action)
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -150,14 +150,10 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
|
|
||||||
external_identifier = properties.get("external_identifier", None)
|
|
||||||
self._check_external_identifier_on_entry_creation(external_identifier)
|
|
||||||
|
|
||||||
obj.identifier = obj.generate_new_identifier()
|
obj.identifier = obj.generate_new_identifier()
|
||||||
obj.title = properties.get("title", None)
|
obj.title = properties["title"]
|
||||||
self._set_responsibility(obj, properties.get("responsible", None))
|
self._set_responsibility(obj, properties["responsible"])
|
||||||
self._set_legal(obj, properties.get("legal", None))
|
self._set_legal(obj, properties["legal"])
|
||||||
|
|
||||||
obj.responsible.handler.save()
|
obj.responsible.handler.save()
|
||||||
obj.responsible.save()
|
obj.responsible.save()
|
||||||
@@ -165,7 +161,6 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj.legal.save()
|
obj.legal.save()
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
self._set_external_identifier(obj.id, external_identifier, obj.created)
|
|
||||||
obj.users.add(user)
|
obj.users.add(user)
|
||||||
obj.log.add(obj.created)
|
obj.log.add(obj.created)
|
||||||
|
|
||||||
@@ -191,7 +186,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
|
|
||||||
# Fill in data to objects
|
# Fill in data to objects
|
||||||
properties = json_model["properties"]
|
properties = json_model["properties"]
|
||||||
obj.title = properties.get("title")
|
obj.title = properties["title"]
|
||||||
self._set_responsibility(obj, properties.get("responsible", None))
|
self._set_responsibility(obj, properties.get("responsible", None))
|
||||||
self._set_legal(obj, properties.get("legal", None))
|
self._set_legal(obj, properties.get("legal", None))
|
||||||
self._set_payments(obj, properties.get("payments", None))
|
self._set_payments(obj, properties.get("payments", None))
|
||||||
@@ -205,7 +200,6 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
|
|||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
obj.mark_as_edited(user, edit_comment="API update")
|
obj.mark_as_edited(user, edit_comment="API update")
|
||||||
self._set_external_identifier(obj.id, properties.get("external_identifier", None), update_action)
|
|
||||||
obj.send_data_to_egon()
|
obj.send_data_to_egon()
|
||||||
|
|
||||||
celery_update_parcels.delay(obj.geometry.id)
|
celery_update_parcels.delay(obj.geometry.id)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from django.contrib.gis.geos import MultiPolygon
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
||||||
from api.models import ExternalIdentifier
|
|
||||||
from api.utils.serializer.serializer import AbstractModelAPISerializer
|
from api.utils.serializer.serializer import AbstractModelAPISerializer
|
||||||
from codelist.models import KonovaCode
|
from codelist.models import KonovaCode
|
||||||
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
|
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
|
||||||
@@ -40,21 +39,12 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
|
|||||||
else:
|
else:
|
||||||
geom = MultiPolygon().geojson
|
geom = MultiPolygon().geojson
|
||||||
geo_json = json.loads(geom)
|
geo_json = json.loads(geom)
|
||||||
ext_ids = list(
|
|
||||||
ExternalIdentifier.objects.filter(
|
|
||||||
internal_id=entry.id
|
|
||||||
).values_list(
|
|
||||||
"external_id", flat=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.properties_data = {
|
self.properties_data = {
|
||||||
"id": entry.id,
|
"id": entry.id,
|
||||||
"identifier": entry.identifier,
|
"identifier": entry.identifier,
|
||||||
"title": entry.title,
|
"title": entry.title,
|
||||||
"created_on": self._created_on_to_json(entry),
|
"created_on": self._created_on_to_json(entry),
|
||||||
"modified_on": self._modified_on_to_json(entry),
|
"modified_on": self._modified_on_to_json(entry),
|
||||||
"external_identifiers": ext_ids,
|
|
||||||
"geometry_conflicts": self._geometry_conflicts_to_list(entry.geometry)
|
|
||||||
}
|
}
|
||||||
self._extend_properties_data(entry)
|
self._extend_properties_data(entry)
|
||||||
geo_json["properties"] = self.properties_data
|
geo_json["properties"] = self.properties_data
|
||||||
@@ -147,57 +137,6 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
|
|||||||
success = entry.deleted is not None
|
success = entry.deleted is not None
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def _set_external_identifier(self, internal_identifier, external_identifier, log_entry):
|
|
||||||
""" If an external identifier was provided in the payload, we set it
|
|
||||||
in the database
|
|
||||||
|
|
||||||
Args:
|
|
||||||
internal_identifier (BaseObject): The already processed konova object (EIV, KOM, ...)
|
|
||||||
external_identifier (any): The external identifier taken from the payload
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if external_identifier is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
ext_id_obj = ExternalIdentifier.objects.get_or_create(
|
|
||||||
internal_id=internal_identifier,
|
|
||||||
external_id=external_identifier
|
|
||||||
)[0]
|
|
||||||
if not ext_id_obj.created:
|
|
||||||
ext_id_obj.created = log_entry
|
|
||||||
ext_id_obj.save()
|
|
||||||
|
|
||||||
return ext_id_obj
|
|
||||||
|
|
||||||
def _get_external_identifier(self, external_identifier):
|
|
||||||
""" Checks whether a linkage based on an external identifier already exists and returns it if so.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
external_identifier (any): The external identifier according to payload
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
ExternalIdentifier | None
|
|
||||||
"""
|
|
||||||
return ExternalIdentifier.resolve_external_identifier(external_identifier)
|
|
||||||
|
|
||||||
def _check_external_identifier_on_entry_creation(self, external_identifier):
|
|
||||||
""" Special check for POST processing:
|
|
||||||
Checks whether an external identifier already exists on the database. This hints that
|
|
||||||
the entry already has been created in the past. Instead of POST, the PUT method shall be used
|
|
||||||
to avoid creating duplicates.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
external_identifier (any): The external identifier according to payload
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
persisted_external_identifier = self._get_external_identifier(external_identifier)
|
|
||||||
if persisted_external_identifier:
|
|
||||||
raise AssertionError(f"{external_identifier} has already been initially created! Use PUT for updates!")
|
|
||||||
|
|
||||||
|
|
||||||
class DeductableAPISerializerV1Mixin:
|
class DeductableAPISerializerV1Mixin:
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 27.01.22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpRequest, JsonResponse
|
||||||
|
|
||||||
|
from api.models import APIUserToken
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def generate_new_token_view(request: HttpRequest):
|
||||||
|
""" Handles request for fetching
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
token = APIUserToken()
|
||||||
|
while APIUserToken.objects.filter(token=token.token).exists():
|
||||||
|
token = APIUserToken()
|
||||||
|
return JsonResponse(
|
||||||
|
data={
|
||||||
|
"gen_data": token.token
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
+27
-30
@@ -6,11 +6,8 @@ Created on: 21.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
from json import JSONDecodeError
|
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.http import JsonResponse, HttpRequest
|
from django.http import JsonResponse, HttpRequest
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
|
|
||||||
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
|
from api.utils.serializer.v1.compensation import CompensationAPISerializerV1
|
||||||
from api.utils.serializer.v1.deduction import DeductionAPISerializerV1
|
from api.utils.serializer.v1.deduction import DeductionAPISerializerV1
|
||||||
@@ -29,20 +26,6 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.serializer = self.serializer()
|
self.serializer = self.serializer()
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
except (JSONDecodeError,
|
|
||||||
AssertionError,
|
|
||||||
ValueError,
|
|
||||||
PermissionError) as e:
|
|
||||||
return self._return_error_response(e, 400)
|
|
||||||
except ObjectDoesNotExist as e:
|
|
||||||
return self._return_error_response(e, 404)
|
|
||||||
except Exception as e:
|
|
||||||
return self._return_error_response(e, 500)
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id=None):
|
def get(self, request: HttpRequest, id=None):
|
||||||
""" Handles the GET request
|
""" Handles the GET request
|
||||||
|
|
||||||
@@ -55,12 +38,17 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
Returns:
|
Returns:
|
||||||
response (JsonResponse)
|
response (JsonResponse)
|
||||||
"""
|
"""
|
||||||
self.rpp = int(request.GET.get("rpp", self.rpp))
|
try:
|
||||||
self.page_number = int(request.GET.get("p", self.page_number))
|
self.rpp = int(request.GET.get("rpp", self.rpp))
|
||||||
self.serializer.rpp = self.rpp
|
self.page_number = int(request.GET.get("p", self.page_number))
|
||||||
self.serializer.page_number = self.page_number
|
|
||||||
self.serializer.prepare_lookup(id, self.user)
|
self.serializer.rpp = self.rpp
|
||||||
data = self.serializer.fetch_and_serialize()
|
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 self._return_response(request, data)
|
return self._return_response(request, data)
|
||||||
|
|
||||||
def post(self, request: HttpRequest):
|
def post(self, request: HttpRequest):
|
||||||
@@ -74,9 +62,12 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
Returns:
|
Returns:
|
||||||
response (JsonResponse)
|
response (JsonResponse)
|
||||||
"""
|
"""
|
||||||
body = request.body.decode("utf-8")
|
try:
|
||||||
body = json.loads(body)
|
body = request.body.decode("utf-8")
|
||||||
created_id = self.serializer.create_model_from_json(body, self.user)
|
body = json.loads(body)
|
||||||
|
created_id = self.serializer.create_model_from_json(body, self.user)
|
||||||
|
except Exception as e:
|
||||||
|
return self._return_error_response(e, 500)
|
||||||
return JsonResponse({"id": created_id})
|
return JsonResponse({"id": created_id})
|
||||||
|
|
||||||
def put(self, request: HttpRequest, id=None):
|
def put(self, request: HttpRequest, id=None):
|
||||||
@@ -91,9 +82,12 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
Returns:
|
Returns:
|
||||||
response (JsonResponse)
|
response (JsonResponse)
|
||||||
"""
|
"""
|
||||||
body = request.body.decode("utf-8")
|
try:
|
||||||
body = json.loads(body)
|
body = request.body.decode("utf-8")
|
||||||
updated_id = self.serializer.update_model_from_json(id, body, self.user)
|
body = json.loads(body)
|
||||||
|
updated_id = self.serializer.update_model_from_json(id, body, self.user)
|
||||||
|
except Exception as e:
|
||||||
|
return self._return_error_response(e, 500)
|
||||||
return JsonResponse({"id": updated_id})
|
return JsonResponse({"id": updated_id})
|
||||||
|
|
||||||
def delete(self, request: HttpRequest, id=None):
|
def delete(self, request: HttpRequest, id=None):
|
||||||
@@ -107,7 +101,10 @@ class AbstractAPIViewV1(AbstractAPIView):
|
|||||||
response (JsonResponse)
|
response (JsonResponse)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success = self.serializer.delete_entry(id, self.user)
|
try:
|
||||||
|
success = self.serializer.delete_entry(id, self.user)
|
||||||
|
except Exception as e:
|
||||||
|
return self._return_error_response(e, 500)
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"success": success,
|
"success": success,
|
||||||
|
|||||||
+27
-79
@@ -6,14 +6,13 @@ Created on: 21.01.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db.models import QuerySet, Q
|
from django.db.models import QuerySet
|
||||||
from django.http import JsonResponse, HttpRequest
|
from django.http import JsonResponse, HttpRequest
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from api.models import APIUserToken, ExternalIdentifier
|
from api.models import APIUserToken
|
||||||
from api.settings import KSP_TOKEN_HEADER_IDENTIFIER, KSP_USER_HEADER_IDENTIFIER
|
from api.settings import KSP_TOKEN_HEADER_IDENTIFIER, KSP_USER_HEADER_IDENTIFIER
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
from ema.models import Ema
|
from ema.models import Ema
|
||||||
@@ -51,19 +50,14 @@ class AbstractAPIView(View):
|
|||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
# Fetch the proper user from the given request header token
|
# Fetch the proper user from the given request header token
|
||||||
token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
|
ksp_token = request.headers.get(KSP_TOKEN_HEADER_IDENTIFIER, None)
|
||||||
ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
|
ksp_user = request.headers.get(KSP_USER_HEADER_IDENTIFIER, None)
|
||||||
|
token_user = APIUserToken.get_user_from_token(ksp_token)
|
||||||
|
|
||||||
if not token and not ksp_user:
|
if ksp_user != token_user.username:
|
||||||
bearer_token = request.headers.get("authorization", None)
|
|
||||||
if not bearer_token:
|
|
||||||
raise PermissionError("No token provided")
|
|
||||||
token = bearer_token.split(" ")[1]
|
|
||||||
|
|
||||||
token_user = APIUserToken.get_user_from_token(token)
|
|
||||||
if ksp_user and ksp_user != token_user.username:
|
|
||||||
raise PermissionError(f"Invalid token for {ksp_user}")
|
raise PermissionError(f"Invalid token for {ksp_user}")
|
||||||
self.user = token_user
|
else:
|
||||||
|
self.user = token_user
|
||||||
|
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
if not self.user.is_default_user():
|
if not self.user.is_default_user():
|
||||||
@@ -82,7 +76,9 @@ class AbstractAPIView(View):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
content = [f"{error.__class__.__name__}: {str(error)}"]
|
content = [error.__str__()]
|
||||||
|
if hasattr(error, "messages"):
|
||||||
|
content = error.messages
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
"errors": content
|
"errors": content
|
||||||
@@ -206,10 +202,6 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
external_identifier = ExternalIdentifier.resolve_external_identifier(id)
|
|
||||||
if external_identifier:
|
|
||||||
id = external_identifier.internal_id
|
|
||||||
|
|
||||||
users = self._get_shared_users_of_object(id)
|
users = self._get_shared_users_of_object(id)
|
||||||
teams = self._get_shared_teams_of_object(id)
|
teams = self._get_shared_teams_of_object(id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -242,9 +234,6 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
external_identifier = ExternalIdentifier.resolve_external_identifier(id)
|
|
||||||
if external_identifier:
|
|
||||||
id = external_identifier.internal_id
|
|
||||||
success = self._process_put_body(request.body, id)
|
success = self._process_put_body(request.body, id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._return_error_response(e)
|
return self._return_error_response(e)
|
||||||
@@ -317,36 +306,22 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
raise ValueError("Shared user list must not be empty!")
|
raise ValueError("Shared user list must not be empty!")
|
||||||
new_teams = content.get("teams", [])
|
new_teams = content.get("teams", [])
|
||||||
|
|
||||||
self.__process_user_sharing(new_users, obj)
|
|
||||||
self.__process_team_sharing(new_teams, obj)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __process_user_sharing(self, user_list: list, obj):
|
|
||||||
""" Processes API sharing for user payload
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_list (list): A list of users to share the obj with
|
|
||||||
obj (BaseObject): The shareable object
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Eliminate duplicates
|
# Eliminate duplicates
|
||||||
new_users = list(dict.fromkeys(user_list))
|
new_users = list(dict.fromkeys(new_users))
|
||||||
|
new_teams = list(dict.fromkeys(new_teams))
|
||||||
|
|
||||||
# Make sure each of these names exist as a user
|
# Make sure each of these names exist as a user
|
||||||
new_users_objs = []
|
new_users_objs = []
|
||||||
for user in new_users:
|
for user in new_users:
|
||||||
try:
|
new_users_objs.append(User.objects.get(username=user))
|
||||||
user_obj = User.objects.get(username=user)
|
|
||||||
except User.DoesNotExist:
|
# Make sure each of these names exist as a user
|
||||||
raise AssertionError(f"User with username {user} does not exist")
|
new_teams_objs = []
|
||||||
new_users_objs.append(user_obj)
|
for team_name in new_teams:
|
||||||
|
new_teams_objs.append(Team.objects.get(name=team_name))
|
||||||
|
|
||||||
if self.user.is_default_group_only():
|
if self.user.is_default_group_only():
|
||||||
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
||||||
# So we need to keep the ones that already have access from being removed!
|
|
||||||
new_users_to_be_added = User.objects.filter(
|
new_users_to_be_added = User.objects.filter(
|
||||||
username__in=new_users
|
username__in=new_users
|
||||||
).exclude(
|
).exclude(
|
||||||
@@ -354,44 +329,17 @@ class AbstractModelShareAPIView(AbstractAPIView):
|
|||||||
)
|
)
|
||||||
new_users_objs = obj.shared_users.union(new_users_to_be_added)
|
new_users_objs = obj.shared_users.union(new_users_to_be_added)
|
||||||
|
|
||||||
|
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_user_list(new_users_objs)
|
||||||
|
obj.share_with_team_list(new_teams_objs)
|
||||||
|
return True
|
||||||
|
|
||||||
def __process_team_sharing(self, team_list: list, obj):
|
|
||||||
""" Processes API sharing for team payload
|
|
||||||
|
|
||||||
Args:
|
|
||||||
team_list (list): A list of teams to share the obj with
|
|
||||||
obj (BaseObject): The shareable object
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Eliminate duplicates
|
|
||||||
new_teams = list(dict.fromkeys(team_list))
|
|
||||||
|
|
||||||
# Resolve team names or ids into objects
|
|
||||||
new_team_ids = []
|
|
||||||
for team in new_teams:
|
|
||||||
try:
|
|
||||||
uuid.UUID(team)
|
|
||||||
try:
|
|
||||||
new_team_ids.append(Team.objects.get(id=team).id)
|
|
||||||
except Team.DoesNotExist:
|
|
||||||
raise AssertionError(f"Team with id {team} does not exist!")
|
|
||||||
except ValueError:
|
|
||||||
# entry is a name and not a uuid -> try to resolve as name!
|
|
||||||
try:
|
|
||||||
new_team_ids.append(Team.objects.get(name=team).id)
|
|
||||||
except Team.DoesNotExist:
|
|
||||||
raise AssertionError(f"Team with name {team} does not exist!")
|
|
||||||
|
|
||||||
new_team_objs = Team.objects.filter(id__in=new_team_ids)
|
|
||||||
if self.user.is_default_group_only():
|
|
||||||
# Default only users are not allowed to remove other users from having access. They can only add new ones!
|
|
||||||
# So we need to keep the ones that already have access from being removed!
|
|
||||||
new_team_objs = obj.shared_teams.union(new_team_objs)
|
|
||||||
|
|
||||||
obj.share_with_team_list(new_team_objs)
|
|
||||||
|
|
||||||
class InterventionAPIShareView(AbstractModelShareAPIView):
|
class InterventionAPIShareView(AbstractModelShareAPIView):
|
||||||
model = Intervention
|
model = Intervention
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ class Command(BaseKonovaCommand):
|
|||||||
atom_id = element.find("atomid").text
|
atom_id = element.find("atomid").text
|
||||||
selectable = element.find("selectable").text.lower()
|
selectable = element.find("selectable").text.lower()
|
||||||
selectable = bool_map.get(selectable, False)
|
selectable = bool_map.get(selectable, False)
|
||||||
short_name = element.find("shortname").text or ""
|
short_name = element.find("shortname").text
|
||||||
long_name = element.find("longname").text or ""
|
long_name = element.find("longname").text
|
||||||
is_archived = bool_map.get((element.find("archive").text.lower()), False)
|
is_archived = bool_map.get((element.find("archive").text.lower()), False)
|
||||||
|
|
||||||
code = KonovaCode.objects.get_or_create(
|
code = KonovaCode.objects.get_or_create(
|
||||||
|
|||||||
@@ -45,14 +45,6 @@ class AbstractCompensationAdmin(BaseObjectAdmin):
|
|||||||
states = "\n".join(states)
|
states = "\n".join(states)
|
||||||
return states
|
return states
|
||||||
|
|
||||||
def get_actions(self, request):
|
|
||||||
DELETE_ACTION_IDENTIFIER = "delete_selected"
|
|
||||||
actions = super().get_actions(request)
|
|
||||||
|
|
||||||
if DELETE_ACTION_IDENTIFIER in actions:
|
|
||||||
del actions[DELETE_ACTION_IDENTIFIER]
|
|
||||||
|
|
||||||
return actions
|
|
||||||
|
|
||||||
class CompensationAdmin(AbstractCompensationAdmin):
|
class CompensationAdmin(AbstractCompensationAdmin):
|
||||||
autocomplete_fields = [
|
autocomplete_fields = [
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from compensation.models import EcoAccount
|
|||||||
from intervention.models import Handler, Responsibility, Legal
|
from intervention.models import Handler, Responsibility, Legal
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.forms.modals import RemoveModalForm
|
from konova.forms.modals import RemoveModalForm
|
||||||
from konova.settings import ETS_GROUP
|
|
||||||
from konova.utils import validators
|
from konova.utils import validators
|
||||||
from user.models import User, UserActionLogEntry
|
from user.models import User, UserActionLogEntry
|
||||||
|
|
||||||
@@ -247,13 +246,4 @@ class RemoveEcoAccountModalForm(RemoveModalForm):
|
|||||||
"confirm",
|
"confirm",
|
||||||
_("The account can not be removed, since there are still deductions.")
|
_("The account can not be removed, since there are still deductions.")
|
||||||
)
|
)
|
||||||
|
|
||||||
# If there are deductions but the performing user is not part of an ETS group, we assume this poor
|
|
||||||
# fella does not know what he/she does -> give a hint that they should contact someone in charge...
|
|
||||||
user_is_ets_user = self.user.in_group(ETS_GROUP)
|
|
||||||
if not user_is_ets_user:
|
|
||||||
self.add_error(
|
|
||||||
"confirm",
|
|
||||||
_("Please contact the responsible conservation office to find a solution!")
|
|
||||||
)
|
|
||||||
return super_valid and not has_deductions
|
return super_valid and not has_deductions
|
||||||
@@ -124,7 +124,7 @@ class CompensationTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
html += self.render_previously_checked_star(
|
html += self.render_previously_checked_star(
|
||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
def render_r(self, value, record: Compensation):
|
def render_r(self, value, record: Compensation):
|
||||||
""" Renders the registered column for a compensation
|
""" Renders the registered column for a compensation
|
||||||
@@ -146,5 +146,5 @@ class CompensationTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=recorded,
|
icn_filled=recorded,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
txt=value,
|
txt=value,
|
||||||
new_tab=False,
|
new_tab=False,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
def render_av(self, value, record: EcoAccount):
|
def render_av(self, value, record: EcoAccount):
|
||||||
""" Renders the available column for an eco account
|
""" Renders the available column for an eco account
|
||||||
@@ -113,7 +113,7 @@ class EcoAccountTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
value_relative = 0
|
value_relative = 0
|
||||||
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
html = render_to_string("konova/widgets/progressbar.html", {"value": value_relative})
|
||||||
html += f"{number_format(record.deductable_rest, decimal_pos=2)} m²"
|
html += f"{number_format(record.deductable_rest, decimal_pos=2)} m²"
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
def render_r(self, value, record: EcoAccount):
|
def render_r(self, value, record: EcoAccount):
|
||||||
""" Renders the recorded column for an eco account
|
""" Renders the recorded column for an eco account
|
||||||
@@ -135,4 +135,4 @@ class EcoAccountTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=checked,
|
icn_filled=checked,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% if deduction.intervention.recorded %}
|
{% if deduction.intervention.recorded %}
|
||||||
<em title="{% trans 'Recorded on' %} {{deduction.intervention.recorded.timestamp}} {% trans 'by' %} {{deduction.intervention.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
|
<em title="{% trans 'Recorded on' %} {{obj.recorded.timestamp}} {% trans 'by' %} {{obj.recorded.user}}" class='fas fa-bookmark registered-bookmark'></em>
|
||||||
{% else %}
|
{% else %}
|
||||||
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
|
<em title="{% trans 'Not recorded yet' %}" class='far fa-bookmark'></em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
new_title = self.create_dummy_string()
|
new_title = self.create_dummy_string()
|
||||||
new_identifier = self.create_dummy_string()
|
new_identifier = self.create_dummy_string()
|
||||||
new_comment = self.create_dummy_string()
|
new_comment = self.create_dummy_string()
|
||||||
new_geometry = self.create_dummy_geometry()
|
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
|
||||||
test_conservation_office = self.get_conservation_office_code()
|
test_conservation_office = self.get_conservation_office_code()
|
||||||
test_deductable_surface = self.eco_account.deductable_surface + 100
|
test_deductable_surface = self.eco_account.deductable_surface + 100
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ class EcoAccountWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
"identifier": new_identifier,
|
"identifier": new_identifier,
|
||||||
"title": new_title,
|
"title": new_title,
|
||||||
"comment": new_comment,
|
"comment": new_comment,
|
||||||
"output": self.create_geojson(new_geometry),
|
"output": new_geometry.geojson,
|
||||||
"surface": test_deductable_surface,
|
"surface": test_deductable_surface,
|
||||||
"conservation_office": test_conservation_office.id
|
"conservation_office": test_conservation_office.id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,32 +7,30 @@ Created on: 24.08.21
|
|||||||
"""
|
"""
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from compensation.views.compensation.detail import DetailCompensationView
|
|
||||||
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
|
from compensation.views.compensation.document import EditCompensationDocumentView, NewCompensationDocumentView, \
|
||||||
GetCompensationDocumentView, RemoveCompensationDocumentView
|
GetCompensationDocumentView, RemoveCompensationDocumentView
|
||||||
from compensation.views.compensation.remove import RemoveCompensationView
|
|
||||||
from compensation.views.compensation.resubmission import CompensationResubmissionView
|
from compensation.views.compensation.resubmission import CompensationResubmissionView
|
||||||
from compensation.views.compensation.report import CompensationPublicReportView
|
from compensation.views.compensation.report import report_view
|
||||||
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
|
from compensation.views.compensation.deadline import NewCompensationDeadlineView, EditCompensationDeadlineView, \
|
||||||
RemoveCompensationDeadlineView
|
RemoveCompensationDeadlineView
|
||||||
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
|
from compensation.views.compensation.action import NewCompensationActionView, EditCompensationActionView, \
|
||||||
RemoveCompensationActionView
|
RemoveCompensationActionView
|
||||||
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
|
from compensation.views.compensation.state import NewCompensationStateView, EditCompensationStateView, \
|
||||||
RemoveCompensationStateView
|
RemoveCompensationStateView
|
||||||
from compensation.views.compensation.compensation import IndexCompensationView, CompensationIdentifierGeneratorView, \
|
from compensation.views.compensation.compensation import index_view, new_view, new_id_view, detail_view, edit_view, \
|
||||||
EditCompensationView, NewCompensationView
|
remove_view
|
||||||
from compensation.views.compensation.log import CompensationLogView
|
from compensation.views.compensation.log import CompensationLogView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Main compensation
|
# Main compensation
|
||||||
path("", IndexCompensationView.as_view(), name="index"),
|
path("", index_view, name="index"),
|
||||||
path('new/id', CompensationIdentifierGeneratorView.as_view(), name='new-id'),
|
path('new/id', new_id_view, name='new-id'),
|
||||||
path('new/<intervention_id>', NewCompensationView.as_view(), name='new'),
|
path('new/<intervention_id>', new_view, name='new'),
|
||||||
path('new', NewCompensationView.as_view(), name='new'),
|
path('new', new_view, name='new'),
|
||||||
path('<id>', DetailCompensationView.as_view(), name='detail'),
|
path('<id>', detail_view, name='detail'),
|
||||||
path('<id>/log', CompensationLogView.as_view(), name='log'),
|
path('<id>/log', CompensationLogView.as_view(), name='log'),
|
||||||
path('<id>/edit', EditCompensationView.as_view(), name='edit'),
|
path('<id>/edit', edit_view, name='edit'),
|
||||||
path('<id>/remove', RemoveCompensationView.as_view(), name='remove'),
|
path('<id>/remove', remove_view, name='remove'),
|
||||||
|
|
||||||
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
|
path('<id>/state/new', NewCompensationStateView.as_view(), name='new-state'),
|
||||||
path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'),
|
path('<id>/state/<state_id>/edit', EditCompensationStateView.as_view(), name='state-edit'),
|
||||||
@@ -45,7 +43,7 @@ urlpatterns = [
|
|||||||
path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"),
|
path('<id>/deadline/new', NewCompensationDeadlineView.as_view(), name="new-deadline"),
|
||||||
path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'),
|
path('<id>/deadline/<deadline_id>/edit', EditCompensationDeadlineView.as_view(), name='deadline-edit'),
|
||||||
path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
|
path('<id>/deadline/<deadline_id>/remove', RemoveCompensationDeadlineView.as_view(), name='deadline-remove'),
|
||||||
path('<id>/report', CompensationPublicReportView.as_view(), name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', CompensationResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
# Documents
|
# Documents
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ Created on: 24.08.21
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
|
from compensation.autocomplete.eco_account import EcoAccountAutocomplete
|
||||||
from compensation.views.eco_account.detail import DetailEcoAccountView
|
from compensation.views.eco_account.eco_account import index_view, new_view, new_id_view, edit_view, remove_view, \
|
||||||
from compensation.views.eco_account.eco_account import IndexEcoAccountView, EcoAccountIdentifierGeneratorView, \
|
detail_view
|
||||||
NewEcoAccountView, EditEcoAccountView
|
|
||||||
from compensation.views.eco_account.log import EcoAccountLogView
|
from compensation.views.eco_account.log import EcoAccountLogView
|
||||||
from compensation.views.eco_account.record import EcoAccountRecordView
|
from compensation.views.eco_account.record import EcoAccountRecordView
|
||||||
from compensation.views.eco_account.remove import RemoveEcoAccountView
|
from compensation.views.eco_account.report import report_view
|
||||||
from compensation.views.eco_account.report import EcoAccountPublicReportView
|
|
||||||
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
|
from compensation.views.eco_account.resubmission import EcoAccountResubmissionView
|
||||||
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
|
from compensation.views.eco_account.state import NewEcoAccountStateView, EditEcoAccountStateView, \
|
||||||
RemoveEcoAccountStateView
|
RemoveEcoAccountStateView
|
||||||
@@ -30,15 +28,15 @@ from compensation.views.eco_account.deduction import NewEcoAccountDeductionView,
|
|||||||
|
|
||||||
app_name = "acc"
|
app_name = "acc"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", IndexEcoAccountView.as_view(), name="index"),
|
path("", index_view, name="index"),
|
||||||
path('new/', NewEcoAccountView.as_view(), name='new'),
|
path('new/', new_view, name='new'),
|
||||||
path('new/id', EcoAccountIdentifierGeneratorView.as_view(), name='new-id'),
|
path('new/id', new_id_view, name='new-id'),
|
||||||
path('<id>', DetailEcoAccountView.as_view(), name='detail'),
|
path('<id>', detail_view, name='detail'),
|
||||||
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
|
path('<id>/log', EcoAccountLogView.as_view(), name='log'),
|
||||||
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
|
path('<id>/record', EcoAccountRecordView.as_view(), name='record'),
|
||||||
path('<id>/report', EcoAccountPublicReportView.as_view(), name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
path('<id>/edit', EditEcoAccountView.as_view(), name='edit'),
|
path('<id>/edit', edit_view, name='edit'),
|
||||||
path('<id>/remove', RemoveEcoAccountView.as_view(), name='remove'),
|
path('<id>/remove', remove_view, name='remove'),
|
||||||
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', EcoAccountResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'),
|
path('<id>/state/new', NewEcoAccountStateView.as_view(), name='new-state'),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from compensation.views.payment import *
|
|||||||
|
|
||||||
app_name = "pay"
|
app_name = "pay"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('<id>/new', NewPaymentView.as_view(), name='new'),
|
path('<id>/new', new_payment_view, name='new'),
|
||||||
path('<id>/remove/<payment_id>', RemovePaymentView.as_view(), name='remove'),
|
path('<id>/remove/<payment_id>', payment_remove_view, name='remove'),
|
||||||
path('<id>/edit/<payment_id>', EditPaymentView.as_view(), name='edit'),
|
path('<id>/edit/<payment_id>', payment_edit_view, name='edit'),
|
||||||
]
|
]
|
||||||
@@ -6,127 +6,91 @@ Created on: 19.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db.models import Sum
|
||||||
|
from django.http import HttpRequest, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, render, redirect
|
from django.shortcuts import get_object_or_404, render, redirect
|
||||||
from django.utils.decorators import method_decorator
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
|
from compensation.forms.compensation import EditCompensationForm, NewCompensationForm
|
||||||
from compensation.models import Compensation
|
from compensation.models import Compensation
|
||||||
from compensation.tables.compensation import CompensationTable
|
from compensation.tables.compensation import CompensationTable
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import shared_access_required, default_group_required
|
from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal, \
|
||||||
|
uuid_required
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.forms.modals import RemoveModalForm
|
||||||
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, \
|
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE, DATA_CHECKED_PREVIOUSLY_TEMPLATE, \
|
||||||
IDENTIFIER_REPLACED, COMPENSATION_ADDED_TEMPLATE, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
RECORDED_BLOCKS_EDIT, CHECK_STATE_RESET, FORM_INVALID, PARAMS_INVALID, IDENTIFIER_REPLACED, \
|
||||||
from konova.views.identifier import AbstractIdentifierGeneratorView
|
COMPENSATION_ADDED_TEMPLATE, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
from konova.views.index import AbstractIndexView
|
|
||||||
|
|
||||||
|
|
||||||
class IndexCompensationView(AbstractIndexView):
|
@login_required
|
||||||
def get(self, request, *args, **kwargs) -> HttpResponse:
|
@any_group_check
|
||||||
"""
|
def index_view(request: HttpRequest):
|
||||||
Renders the index view for compensation
|
"""
|
||||||
|
Renders the index view for compensation
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A rendered view
|
A rendered view
|
||||||
"""
|
"""
|
||||||
compensations = Compensation.objects.filter(
|
template = "generic_index.html"
|
||||||
deleted=None, # only show those which are not deleted individually
|
compensations = Compensation.objects.filter(
|
||||||
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
|
deleted=None, # only show those which are not deleted individually
|
||||||
).order_by(
|
intervention__deleted=None, # and don't show the ones whose intervention has been deleted
|
||||||
"-modified__timestamp"
|
).order_by(
|
||||||
)
|
"-modified__timestamp"
|
||||||
table = CompensationTable(
|
)
|
||||||
request=request,
|
table = CompensationTable(
|
||||||
queryset=compensations
|
request=request,
|
||||||
)
|
queryset=compensations
|
||||||
context = {
|
)
|
||||||
"table": table,
|
context = {
|
||||||
TAB_TITLE_IDENTIFIER: _("Compensations - Overview"),
|
"table": table,
|
||||||
}
|
TAB_TITLE_IDENTIFIER: _("Compensations - Overview"),
|
||||||
context = BaseContext(request, context).context
|
}
|
||||||
return render(request, self._TEMPLATE, context)
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
class NewCompensationView(LoginRequiredMixin, View):
|
@login_required
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "intervention_id")
|
||||||
|
def new_view(request: HttpRequest, intervention_id: str = None):
|
||||||
|
"""
|
||||||
|
Renders a view for a new compensation creation
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
Args:
|
||||||
@method_decorator(shared_access_required(Intervention, "intervention_id"))
|
request (HttpRequest): The incoming request
|
||||||
def get(self, request: HttpRequest, intervention_id: str = None, *args, **kwargs) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
Renders a view for new compensation
|
|
||||||
|
|
||||||
A compensation creation may be called directly from the parent-intervention object. If so - we may take
|
Returns:
|
||||||
the intervention's id and directly link the compensation to it.
|
|
||||||
|
|
||||||
Args:
|
"""
|
||||||
request (HttpRequest): The incoming request
|
template = "compensation/form/view.html"
|
||||||
intervention_id (str): The intervention identifier
|
if intervention_id is not None:
|
||||||
|
try:
|
||||||
Returns:
|
intervention = Intervention.objects.get(id=intervention_id)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
"""
|
messages.error(request, PARAMS_INVALID)
|
||||||
if intervention_id:
|
return redirect("home")
|
||||||
# If the parent-intervention is recorded, we are not allowed to change anything on it's data.
|
if intervention.is_recorded:
|
||||||
# Not even adding new child elements like compensations!
|
messages.info(
|
||||||
intervention = get_object_or_404(Intervention, id=intervention_id)
|
request,
|
||||||
recording_state_blocks_actions = intervention.is_recorded
|
RECORDED_BLOCKS_EDIT
|
||||||
if recording_state_blocks_actions:
|
)
|
||||||
messages.info(
|
return redirect("intervention:detail", id=intervention_id)
|
||||||
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)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("New compensation"),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "intervention_id"))
|
|
||||||
def post(self, request: HttpRequest, intervention_id: str = None, *args, **kwargs) -> HttpResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Renders a view for a new compensation creation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
if intervention_id:
|
|
||||||
# If the parent-intervention is recorded, we are not allowed to change anything on it's data.
|
|
||||||
# Not even adding new child elements like compensations!
|
|
||||||
intervention = get_object_or_404(Intervention, id=intervention_id)
|
|
||||||
recording_state_blocks_actions = intervention.is_recorded
|
|
||||||
if recording_state_blocks_actions:
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
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":
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
comp = data_form.save(request.user, geom_form)
|
comp = data_form.save(request.user, geom_form)
|
||||||
@@ -139,127 +103,199 @@ class NewCompensationView(LoginRequiredMixin, View):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
|
messages.success(request, COMPENSATION_ADDED_TEMPLATE.format(comp.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("compensation:detail", id=comp.id)
|
return redirect("compensation:detail", id=comp.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
|
else:
|
||||||
|
# For clarification: nothing in this case
|
||||||
|
pass
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New compensation"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
@login_required
|
||||||
"geom_form": geom_form,
|
@default_group_required
|
||||||
TAB_TITLE_IDENTIFIER: _("New compensation"),
|
def new_id_view(request: HttpRequest):
|
||||||
|
""" JSON endpoint
|
||||||
|
|
||||||
|
Provides fetching of free identifiers for e.g. AJAX calls
|
||||||
|
|
||||||
|
"""
|
||||||
|
tmp = Compensation()
|
||||||
|
identifier = tmp.generate_new_identifier()
|
||||||
|
while Compensation.objects.filter(identifier=identifier).exists():
|
||||||
|
identifier = tmp.generate_new_identifier()
|
||||||
|
return JsonResponse(
|
||||||
|
data={
|
||||||
|
"gen_data": identifier
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
)
|
||||||
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
|
|
||||||
class CompensationIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
@login_required
|
||||||
_MODEL = Compensation
|
@default_group_required
|
||||||
|
@shared_access_required(Compensation, "id")
|
||||||
|
def edit_view(request: HttpRequest, id: str):
|
||||||
|
"""
|
||||||
|
Renders a view for editing compensations
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
class EditCompensationView(LoginRequiredMixin, View):
|
Returns:
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
"""
|
||||||
@method_decorator(shared_access_required(Compensation, "id"))
|
template = "compensation/form/view.html"
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
# 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
|
||||||
Renders a view for editing compensations
|
data_form = EditCompensationForm(request.POST or None, instance=comp)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=comp)
|
||||||
Args:
|
if request.method == "POST":
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Compensation, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Renders a view for editing compensations
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# 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)
|
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
# Preserve state of intervention checked to determine whether the user must be informed or not
|
# Preserve state of intervention checked to determine whether the user must be informed or not
|
||||||
# about a change of the check state
|
# about a change of the check state
|
||||||
intervention_is_checked = comp.intervention.checked is not None
|
intervention_is_checked = comp.intervention.checked is not None
|
||||||
|
|
||||||
# The data form takes the geom form for processing, as well as the performing user
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
comp = data_form.save(request.user, geom_form)
|
comp = data_form.save(request.user, geom_form)
|
||||||
if intervention_is_checked:
|
if intervention_is_checked:
|
||||||
messages.info(request, CHECK_STATE_RESET)
|
messages.info(request, CHECK_STATE_RESET)
|
||||||
messages.success(request, _("Compensation {} edited").format(comp.identifier))
|
messages.success(request, _("Compensation {} edited").format(comp.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("compensation:detail", id=comp.id)
|
return redirect("compensation:detail", id=comp.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
|
else:
|
||||||
|
# For clarification: nothing in this case
|
||||||
|
pass
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(comp.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
|
|
||||||
return render(request, self._TEMPLATE, context)
|
@login_required
|
||||||
|
@any_group_check
|
||||||
|
@uuid_required
|
||||||
|
def detail_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a detail view for a compensation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The compensation's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "compensation/detail/compensation/view.html"
|
||||||
|
comp = get_object_or_404(
|
||||||
|
Compensation.objects.select_related(
|
||||||
|
"modified",
|
||||||
|
"created",
|
||||||
|
"geometry"
|
||||||
|
),
|
||||||
|
id=id,
|
||||||
|
deleted=None,
|
||||||
|
intervention__deleted=None,
|
||||||
|
)
|
||||||
|
geom_form = SimpleGeomForm(instance=comp)
|
||||||
|
parcels = comp.get_underlying_parcels()
|
||||||
|
_user = request.user
|
||||||
|
is_data_shared = comp.intervention.is_shared_with(_user)
|
||||||
|
|
||||||
|
# Order states according to surface
|
||||||
|
before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface")
|
||||||
|
after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface")
|
||||||
|
actions = comp.actions.all().prefetch_related("action_type")
|
||||||
|
|
||||||
|
# Precalculate logical errors between before- and after-states
|
||||||
|
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
||||||
|
sum_before_states = comp.get_surface_before_states()
|
||||||
|
sum_after_states = comp.get_surface_after_states()
|
||||||
|
diff_states = abs(sum_before_states - sum_after_states)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
requesting_user_is_only_shared_user = comp.is_only_shared_with(_user)
|
||||||
|
if requesting_user_is_only_shared_user:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": comp,
|
||||||
|
"last_checked": last_checked,
|
||||||
|
"last_checked_tooltip": last_checked_tooltip,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"is_entry_shared": is_data_shared,
|
||||||
|
"actions": actions,
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"sum_before_states": sum_before_states,
|
||||||
|
"sum_after_states": sum_after_states,
|
||||||
|
"diff_states": diff_states,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": comp.get_LANIS_link(),
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
|
||||||
|
"has_finished_deadlines": comp.get_finished_deadlines().exists(),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required_modal
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(Compensation, "id")
|
||||||
|
def remove_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a modal view for removing the compensation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The compensation's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
comp = get_object_or_404(Compensation, id=id)
|
||||||
|
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request=request,
|
||||||
|
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
|
||||||
|
redirect_url=reverse("compensation:index"),
|
||||||
|
)
|
||||||
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.shortcuts import render, get_object_or_404
|
|
||||||
|
|
||||||
from compensation.models import Compensation
|
|
||||||
from konova.contexts import BaseContext
|
|
||||||
from konova.forms import SimpleGeomForm
|
|
||||||
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE, DATA_CHECKED_PREVIOUSLY_TEMPLATE
|
|
||||||
from konova.views.detail import AbstractDetailView
|
|
||||||
|
|
||||||
|
|
||||||
class DetailCompensationView(AbstractDetailView):
|
|
||||||
_TEMPLATE = "compensation/detail/compensation/view.html"
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
|
|
||||||
""" Renders a detail view for a compensation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The compensation's id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
comp = get_object_or_404(
|
|
||||||
Compensation.objects.select_related(
|
|
||||||
"modified",
|
|
||||||
"created",
|
|
||||||
"geometry"
|
|
||||||
),
|
|
||||||
id=id,
|
|
||||||
deleted=None,
|
|
||||||
intervention__deleted=None,
|
|
||||||
)
|
|
||||||
geom_form = SimpleGeomForm(instance=comp)
|
|
||||||
parcels = comp.get_underlying_parcels()
|
|
||||||
_user = request.user
|
|
||||||
is_data_shared = comp.intervention.is_shared_with(_user)
|
|
||||||
|
|
||||||
# Order states according to surface
|
|
||||||
before_states = comp.before_states.all().prefetch_related("biotope_type").order_by("-surface")
|
|
||||||
after_states = comp.after_states.all().prefetch_related("biotope_type").order_by("-surface")
|
|
||||||
actions = comp.actions.all().prefetch_related("action_type")
|
|
||||||
|
|
||||||
# Precalculate logical errors between before- and after-states
|
|
||||||
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
|
||||||
sum_before_states = comp.get_surface_before_states()
|
|
||||||
sum_after_states = comp.get_surface_after_states()
|
|
||||||
diff_states = abs(sum_before_states - sum_after_states)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
requesting_user_is_only_shared_user = comp.is_only_shared_with(_user)
|
|
||||||
if requesting_user_is_only_shared_user:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
DO_NOT_FORGET_TO_SHARE
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"obj": comp,
|
|
||||||
"last_checked": last_checked,
|
|
||||||
"last_checked_tooltip": last_checked_tooltip,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"parcels": parcels,
|
|
||||||
"is_entry_shared": is_data_shared,
|
|
||||||
"actions": actions,
|
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"sum_before_states": sum_before_states,
|
|
||||||
"sum_after_states": sum_after_states,
|
|
||||||
"diff_states": diff_states,
|
|
||||||
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
|
||||||
"is_zb_member": _user.in_group(ZB_GROUP),
|
|
||||||
"is_ets_member": _user.in_group(ETS_GROUP),
|
|
||||||
"LANIS_LINK": comp.get_LANIS_link(),
|
|
||||||
TAB_TITLE_IDENTIFIER: f"{comp.identifier} - {comp.title}",
|
|
||||||
"has_finished_deadlines": comp.get_finished_deadlines().exists(),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
|
|
||||||
from compensation.models import Compensation
|
|
||||||
from konova.decorators import shared_access_required
|
|
||||||
from konova.views.remove import AbstractRemoveView
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveCompensationView(AbstractRemoveView):
|
|
||||||
_MODEL = Compensation
|
|
||||||
_REDIRECT_URL = "compensation:index"
|
|
||||||
|
|
||||||
@method_decorator(shared_access_required(Compensation, "id"))
|
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
@@ -5,7 +5,7 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -14,72 +14,67 @@ from compensation.models import Compensation
|
|||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.generators import generate_qr_code
|
||||||
from konova.views.report import AbstractPublicReportView
|
|
||||||
|
|
||||||
|
|
||||||
class CompensationPublicReportView(AbstractPublicReportView):
|
def report_view(request: HttpRequest, id: str):
|
||||||
_TEMPLATE = "compensation/report/compensation/report.html"
|
""" Renders the public report view
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
Args:
|
||||||
""" Renders the public report view
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The id of the intervention
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
# Reuse the compensation report template since compensations are structurally identical
|
||||||
"""
|
template = "compensation/report/compensation/report.html"
|
||||||
comp = get_object_or_404(Compensation, id=id)
|
comp = get_object_or_404(Compensation, id=id)
|
||||||
tab_title = _("Report {}").format(comp.identifier)
|
|
||||||
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
|
||||||
if not comp.is_ready_for_publish():
|
|
||||||
template = "report/unavailable.html"
|
|
||||||
context = {
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
# Prepare data for map viewer
|
|
||||||
geom_form = SimpleGeomForm(
|
|
||||||
instance=comp
|
|
||||||
)
|
|
||||||
parcels = comp.get_underlying_parcels()
|
|
||||||
|
|
||||||
qrcode = QrCode(
|
|
||||||
content=request.build_absolute_uri(reverse("compensation:report", args=(id,))),
|
|
||||||
size=10
|
|
||||||
)
|
|
||||||
qrcode_lanis = QrCode(
|
|
||||||
content=comp.get_LANIS_link(),
|
|
||||||
size=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")
|
|
||||||
actions = comp.actions.all().prefetch_related("action_type")
|
|
||||||
|
|
||||||
|
tab_title = _("Report {}").format(comp.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not comp.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
context = {
|
context = {
|
||||||
"obj": comp,
|
|
||||||
"qrcode": {
|
|
||||||
"img": qrcode.get_img(),
|
|
||||||
"url": qrcode.get_content(),
|
|
||||||
},
|
|
||||||
"qrcode_lanis": {
|
|
||||||
"img": qrcode_lanis.get_img(),
|
|
||||||
"url": qrcode_lanis.get_content(),
|
|
||||||
},
|
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"parcels": parcels,
|
|
||||||
"actions": actions,
|
|
||||||
"tables_scrollable": False,
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, self._TEMPLATE, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=comp
|
||||||
|
)
|
||||||
|
parcels = comp.get_underlying_parcels()
|
||||||
|
|
||||||
|
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")
|
||||||
|
actions = comp.actions.all().prefetch_related("action_type")
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": comp,
|
||||||
|
"qrcode": {
|
||||||
|
"img": qrcode_img,
|
||||||
|
"url": qrcode_url,
|
||||||
|
},
|
||||||
|
"qrcode_lanis": {
|
||||||
|
"img": qrcode_img_lanis,
|
||||||
|
"url": qrcode_lanis_url,
|
||||||
|
},
|
||||||
|
"is_entry_shared": False, # disables action buttons during rendering
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"actions": actions,
|
||||||
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.shortcuts import render, get_object_or_404
|
|
||||||
|
|
||||||
from compensation.models import EcoAccount
|
|
||||||
from konova.contexts import BaseContext
|
|
||||||
from konova.forms import SimpleGeomForm
|
|
||||||
from konova.settings import ETS_GROUP, ZB_GROUP, DEFAULT_GROUP
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
|
|
||||||
from konova.views.detail import AbstractDetailView
|
|
||||||
|
|
||||||
|
|
||||||
class DetailEcoAccountView(AbstractDetailView):
|
|
||||||
_TEMPLATE = "compensation/detail/eco_account/view.html"
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
""" Renders a detail view for a compensation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The compensation's id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
acc = get_object_or_404(
|
|
||||||
EcoAccount.objects.prefetch_related(
|
|
||||||
"deadlines",
|
|
||||||
).select_related(
|
|
||||||
'geometry',
|
|
||||||
'responsible',
|
|
||||||
),
|
|
||||||
id=id,
|
|
||||||
deleted=None,
|
|
||||||
)
|
|
||||||
geom_form = SimpleGeomForm(instance=acc)
|
|
||||||
parcels = acc.get_underlying_parcels()
|
|
||||||
_user = request.user
|
|
||||||
is_data_shared = acc.is_shared_with(_user)
|
|
||||||
|
|
||||||
# Order states according to surface
|
|
||||||
before_states = acc.before_states.order_by("-surface")
|
|
||||||
after_states = acc.after_states.order_by("-surface")
|
|
||||||
|
|
||||||
# Precalculate logical errors between before- and after-states
|
|
||||||
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
|
||||||
sum_before_states = acc.get_surface_before_states()
|
|
||||||
sum_after_states = acc.get_surface_after_states()
|
|
||||||
diff_states = abs(sum_before_states - sum_after_states)
|
|
||||||
# Calculate rest of available surface for deductions
|
|
||||||
available_total = acc.deductable_rest
|
|
||||||
available_relative = acc.get_deductable_rest_relative()
|
|
||||||
|
|
||||||
# Prefetch related data to decrease the amount of db connections
|
|
||||||
deductions = acc.deductions.filter(
|
|
||||||
intervention__deleted=None,
|
|
||||||
)
|
|
||||||
actions = acc.actions.all()
|
|
||||||
|
|
||||||
request = acc.set_status_messages(request)
|
|
||||||
|
|
||||||
requesting_user_is_only_shared_user = acc.is_only_shared_with(_user)
|
|
||||||
if requesting_user_is_only_shared_user:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
DO_NOT_FORGET_TO_SHARE
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"obj": acc,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"parcels": parcels,
|
|
||||||
"is_entry_shared": is_data_shared,
|
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"sum_before_states": sum_before_states,
|
|
||||||
"sum_after_states": sum_after_states,
|
|
||||||
"diff_states": diff_states,
|
|
||||||
"available": available_relative,
|
|
||||||
"available_total": available_total,
|
|
||||||
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
|
||||||
"is_zb_member": _user.in_group(ZB_GROUP),
|
|
||||||
"is_ets_member": _user.in_group(ETS_GROUP),
|
|
||||||
"LANIS_LINK": acc.get_LANIS_link(),
|
|
||||||
"deductions": deductions,
|
|
||||||
"actions": actions,
|
|
||||||
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
|
|
||||||
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
@@ -6,94 +6,72 @@ Created on: 19.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.db.models import Sum
|
||||||
|
from django.http import HttpRequest, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.decorators import method_decorator
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm
|
from compensation.forms.eco_account import EditEcoAccountForm, NewEcoAccountForm, RemoveEcoAccountModalForm
|
||||||
from compensation.models import EcoAccount
|
from compensation.models import EcoAccount
|
||||||
from compensation.tables.eco_account import EcoAccountTable
|
from compensation.tables.eco_account import EcoAccountTable
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import shared_access_required, default_group_required
|
from konova.decorators import shared_access_required, default_group_required, any_group_check, login_required_modal, \
|
||||||
|
uuid_required
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.settings import ETS_GROUP, DEFAULT_GROUP, ZB_GROUP
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, FORM_INVALID, \
|
from konova.utils.message_templates import CANCEL_ACC_RECORDED_OR_DEDUCTED, RECORDED_BLOCKS_EDIT, FORM_INVALID, \
|
||||||
IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
from konova.views.identifier import AbstractIdentifierGeneratorView
|
|
||||||
from konova.views.index import AbstractIndexView
|
|
||||||
|
|
||||||
|
|
||||||
class IndexEcoAccountView(AbstractIndexView):
|
@login_required
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
@any_group_check
|
||||||
"""
|
def index_view(request: HttpRequest):
|
||||||
Renders the index view for eco accounts
|
"""
|
||||||
|
Renders the index view for eco accounts
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A rendered view
|
A rendered view
|
||||||
"""
|
"""
|
||||||
eco_accounts = EcoAccount.objects.filter(
|
template = "generic_index.html"
|
||||||
deleted=None,
|
eco_accounts = EcoAccount.objects.filter(
|
||||||
).order_by(
|
deleted=None,
|
||||||
"-modified__timestamp"
|
).order_by(
|
||||||
)
|
"-modified__timestamp"
|
||||||
table = EcoAccountTable(
|
)
|
||||||
request=request,
|
table = EcoAccountTable(
|
||||||
queryset=eco_accounts
|
request=request,
|
||||||
)
|
queryset=eco_accounts
|
||||||
context = {
|
)
|
||||||
"table": table,
|
context = {
|
||||||
TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
|
"table": table,
|
||||||
}
|
TAB_TITLE_IDENTIFIER: _("Eco-account - Overview"),
|
||||||
context = BaseContext(request, context).context
|
}
|
||||||
return render(request, self._TEMPLATE, context)
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
class NewEcoAccountView(LoginRequiredMixin, View):
|
@login_required
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
@default_group_required
|
||||||
|
def new_view(request: HttpRequest):
|
||||||
|
"""
|
||||||
|
Renders a view for a new eco account creation
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
Args:
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
request (HttpRequest): The incoming request
|
||||||
"""
|
|
||||||
Renders a view for a new eco account creation
|
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
template = "compensation/form/view.html"
|
||||||
"""
|
data_form = NewEcoAccountForm(request.POST or None)
|
||||||
data_form = NewEcoAccountForm(request.POST or None)
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
if request.method == "POST":
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Renders a view for a new eco account creation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
data_form = NewEcoAccountForm(request.POST or None)
|
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
acc = data_form.save(request.user, geom_form)
|
acc = data_form.save(request.user, geom_form)
|
||||||
@@ -106,123 +84,208 @@ class NewEcoAccountView(LoginRequiredMixin, View):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
messages.success(request, _("Eco-Account {} added").format(acc.identifier))
|
messages.success(request, _("Eco-Account {} added").format(acc.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("compensation:acc:detail", id=acc.id)
|
return redirect("compensation:acc:detail", id=acc.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
|
else:
|
||||||
|
# For clarification: nothing in this case
|
||||||
|
pass
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
@login_required
|
||||||
"geom_form": geom_form,
|
@default_group_required
|
||||||
TAB_TITLE_IDENTIFIER: _("New Eco-Account"),
|
def new_id_view(request: HttpRequest):
|
||||||
|
""" JSON endpoint
|
||||||
|
|
||||||
|
Provides fetching of free identifiers for e.g. AJAX calls
|
||||||
|
|
||||||
|
"""
|
||||||
|
tmp = EcoAccount()
|
||||||
|
identifier = tmp.generate_new_identifier()
|
||||||
|
while EcoAccount.objects.filter(identifier=identifier).exists():
|
||||||
|
identifier = tmp.generate_new_identifier()
|
||||||
|
return JsonResponse(
|
||||||
|
data={
|
||||||
|
"gen_data": identifier
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
)
|
||||||
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
class EcoAccountIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
|
||||||
_MODEL = EcoAccount
|
|
||||||
|
|
||||||
|
|
||||||
class EditEcoAccountView(LoginRequiredMixin, View):
|
@login_required
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
@default_group_required
|
||||||
|
@shared_access_required(EcoAccount, "id")
|
||||||
|
def edit_view(request: HttpRequest, id: str):
|
||||||
|
"""
|
||||||
|
Renders a view for editing compensations
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
Args:
|
||||||
@method_decorator(shared_access_required(EcoAccount, "id"))
|
request (HttpRequest): The incoming request
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
|
|
||||||
"""
|
Returns:
|
||||||
Renders a view for editing compensations
|
|
||||||
|
|
||||||
Args:
|
"""
|
||||||
request (HttpRequest): The incoming request
|
template = "compensation/form/view.html"
|
||||||
|
# Get object from db
|
||||||
Returns:
|
acc = get_object_or_404(EcoAccount, id=id)
|
||||||
|
if acc.is_recorded:
|
||||||
"""
|
messages.info(
|
||||||
# Get object from db
|
request,
|
||||||
acc = get_object_or_404(EcoAccount, id=id)
|
RECORDED_BLOCKS_EDIT
|
||||||
if acc.is_recorded:
|
)
|
||||||
messages.info(
|
return redirect("compensation:acc:detail", id=id)
|
||||||
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)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(EcoAccount, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Renders a view for editing compensations
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
if request.method == "POST":
|
||||||
data_form_valid = data_form.is_valid()
|
data_form_valid = data_form.is_valid()
|
||||||
geom_form_valid = geom_form.is_valid()
|
geom_form_valid = geom_form.is_valid()
|
||||||
if data_form_valid and geom_form_valid:
|
if data_form_valid and geom_form_valid:
|
||||||
# The data form takes the geom form for processing, as well as the performing user
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
acc = data_form.save(request.user, geom_form)
|
acc = data_form.save(request.user, geom_form)
|
||||||
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
|
messages.success(request, _("Eco-Account {} edited").format(acc.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("compensation:acc:detail", id=acc.id)
|
return redirect("compensation:acc:detail", id=acc.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
|
else:
|
||||||
|
# For clarification: nothing in this case
|
||||||
|
pass
|
||||||
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(acc.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
|
|
||||||
return render(request, self._TEMPLATE, context)
|
@login_required
|
||||||
|
@any_group_check
|
||||||
|
@uuid_required
|
||||||
|
def detail_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a detail view for a compensation
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The compensation's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "compensation/detail/eco_account/view.html"
|
||||||
|
acc = get_object_or_404(
|
||||||
|
EcoAccount.objects.prefetch_related(
|
||||||
|
"deadlines",
|
||||||
|
).select_related(
|
||||||
|
'geometry',
|
||||||
|
'responsible',
|
||||||
|
),
|
||||||
|
id=id,
|
||||||
|
deleted=None,
|
||||||
|
)
|
||||||
|
geom_form = SimpleGeomForm(instance=acc)
|
||||||
|
parcels = acc.get_underlying_parcels()
|
||||||
|
_user = request.user
|
||||||
|
is_data_shared = acc.is_shared_with(_user)
|
||||||
|
|
||||||
|
# Order states according to surface
|
||||||
|
before_states = acc.before_states.order_by("-surface")
|
||||||
|
after_states = acc.after_states.order_by("-surface")
|
||||||
|
|
||||||
|
# Precalculate logical errors between before- and after-states
|
||||||
|
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
||||||
|
sum_before_states = acc.get_surface_before_states()
|
||||||
|
sum_after_states = acc.get_surface_after_states()
|
||||||
|
diff_states = abs(sum_before_states - sum_after_states)
|
||||||
|
# Calculate rest of available surface for deductions
|
||||||
|
available_total = acc.deductable_rest
|
||||||
|
available_relative = acc.get_deductable_rest_relative()
|
||||||
|
|
||||||
|
# Prefetch related data to decrease the amount of db connections
|
||||||
|
deductions = acc.deductions.filter(
|
||||||
|
intervention__deleted=None,
|
||||||
|
)
|
||||||
|
actions = acc.actions.all()
|
||||||
|
|
||||||
|
request = acc.set_status_messages(request)
|
||||||
|
|
||||||
|
requesting_user_is_only_shared_user = acc.is_only_shared_with(_user)
|
||||||
|
if requesting_user_is_only_shared_user:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": acc,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"is_entry_shared": is_data_shared,
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"sum_before_states": sum_before_states,
|
||||||
|
"sum_after_states": sum_after_states,
|
||||||
|
"diff_states": diff_states,
|
||||||
|
"available": available_relative,
|
||||||
|
"available_total": available_total,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": acc.get_LANIS_link(),
|
||||||
|
"deductions": deductions,
|
||||||
|
"actions": actions,
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{acc.identifier} - {acc.title}",
|
||||||
|
"has_finished_deadlines": acc.get_finished_deadlines().exists(),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required_modal
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(EcoAccount, "id")
|
||||||
|
def remove_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a modal view for removing the eco account
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The account's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
acc = get_object_or_404(EcoAccount, id=id)
|
||||||
|
|
||||||
|
# If the eco account has already been recorded OR there are already deductions, it can not be deleted by a regular
|
||||||
|
# default group user
|
||||||
|
if acc.recorded is not None or acc.deductions.exists():
|
||||||
|
user = request.user
|
||||||
|
if not user.in_group(ETS_GROUP):
|
||||||
|
messages.info(request, CANCEL_ACC_RECORDED_OR_DEDUCTED)
|
||||||
|
return redirect("compensation:acc:detail", id=id)
|
||||||
|
|
||||||
|
form = RemoveEcoAccountModalForm(request.POST or None, instance=acc, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request=request,
|
||||||
|
msg_success=_("Eco-account removed"),
|
||||||
|
redirect_url=reverse("compensation:acc:index"),
|
||||||
|
)
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
|
|
||||||
from compensation.forms.eco_account import RemoveEcoAccountModalForm
|
|
||||||
from compensation.models import EcoAccount
|
|
||||||
from konova.decorators import shared_access_required
|
|
||||||
from konova.views.remove import AbstractRemoveView
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveEcoAccountView(AbstractRemoveView):
|
|
||||||
_MODEL = EcoAccount
|
|
||||||
_REDIRECT_URL = "compensation:acc:index"
|
|
||||||
_FORM = RemoveEcoAccountModalForm
|
|
||||||
|
|
||||||
@method_decorator(shared_access_required(EcoAccount, "id"))
|
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
@@ -5,7 +5,7 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -14,79 +14,74 @@ from compensation.models import EcoAccount
|
|||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.generators import generate_qr_code
|
||||||
from konova.views.report import AbstractPublicReportView
|
|
||||||
|
|
||||||
|
|
||||||
class EcoAccountPublicReportView(AbstractPublicReportView):
|
def report_view(request: HttpRequest, id: str):
|
||||||
_TEMPLATE = "compensation/report/eco_account/report.html"
|
""" Renders the public report view
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
Args:
|
||||||
""" Renders the public report view
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The id of the intervention
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
# Reuse the compensation report template since EcoAccounts are structurally identical
|
||||||
"""
|
template = "compensation/report/eco_account/report.html"
|
||||||
acc = get_object_or_404(EcoAccount, id=id)
|
acc = get_object_or_404(EcoAccount, id=id)
|
||||||
tab_title = _("Report {}").format(acc.identifier)
|
|
||||||
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
|
||||||
if not acc.is_ready_for_publish():
|
|
||||||
template = "report/unavailable.html"
|
|
||||||
context = {
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
# Prepare data for map viewer
|
|
||||||
geom_form = SimpleGeomForm(
|
|
||||||
instance=acc
|
|
||||||
)
|
|
||||||
parcels = acc.get_underlying_parcels()
|
|
||||||
|
|
||||||
qrcode = QrCode(
|
|
||||||
content=request.build_absolute_uri(reverse("compensation:acc:report", args=(id,))),
|
|
||||||
size=10
|
|
||||||
)
|
|
||||||
qrcode_lanis = QrCode(
|
|
||||||
content=acc.get_LANIS_link(),
|
|
||||||
size=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().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() \
|
|
||||||
.distinct("intervention") \
|
|
||||||
.select_related("intervention") \
|
|
||||||
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
|
|
||||||
|
|
||||||
|
tab_title = _("Report {}").format(acc.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not acc.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
context = {
|
context = {
|
||||||
"obj": acc,
|
|
||||||
"qrcode": {
|
|
||||||
"img": qrcode.get_img(),
|
|
||||||
"url": qrcode.get_content(),
|
|
||||||
},
|
|
||||||
"qrcode_lanis": {
|
|
||||||
"img": qrcode_lanis.get_img(),
|
|
||||||
"url": qrcode_lanis.get_content(),
|
|
||||||
},
|
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"parcels": parcels,
|
|
||||||
"actions": actions,
|
|
||||||
"deductions": deductions,
|
|
||||||
"tables_scrollable": False,
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, self._TEMPLATE, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=acc
|
||||||
|
)
|
||||||
|
parcels = acc.get_underlying_parcels()
|
||||||
|
|
||||||
|
qrcode_url = request.build_absolute_uri(reverse("compensation:acc: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().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()\
|
||||||
|
.distinct("intervention")\
|
||||||
|
.select_related("intervention")\
|
||||||
|
.values_list("intervention__id", "intervention__identifier", "intervention__title", named=True)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": acc,
|
||||||
|
"qrcode": {
|
||||||
|
"img": qrcode_img,
|
||||||
|
"url": qrcode_url,
|
||||||
|
},
|
||||||
|
"qrcode_lanis": {
|
||||||
|
"img": qrcode_img_lanis,
|
||||||
|
"url": qrcode_lanis_url,
|
||||||
|
},
|
||||||
|
"is_entry_shared": False, # disables action buttons during rendering
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"actions": actions,
|
||||||
|
"deductions": deductions,
|
||||||
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
@@ -5,12 +5,10 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 09.08.21
|
Created on: 09.08.21
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
|
from compensation.forms.modals.payment import NewPaymentForm, RemovePaymentModalForm, EditPaymentModalForm
|
||||||
from compensation.models import Payment
|
from compensation.models import Payment
|
||||||
@@ -19,97 +17,72 @@ from konova.decorators import default_group_required, shared_access_required
|
|||||||
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
|
from konova.utils.message_templates import PAYMENT_ADDED, PAYMENT_REMOVED, PAYMENT_EDITED
|
||||||
|
|
||||||
|
|
||||||
class NewPaymentView(LoginRequiredMixin, View):
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def new_payment_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a modal view for adding new payments
|
||||||
|
|
||||||
def __process_request(self, request: HttpRequest, id: str):
|
Args:
|
||||||
""" Renders a modal view for adding new payments
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id for which a new payment shall be added
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The intervention's id for which a new payment shall be added
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
"""
|
form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
return form.process_request(
|
||||||
form = NewPaymentForm(request.POST or None, instance=intervention, request=request)
|
request,
|
||||||
return form.process_request(
|
msg_success=PAYMENT_ADDED,
|
||||||
request,
|
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
||||||
msg_success=PAYMENT_ADDED,
|
)
|
||||||
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
|
||||||
)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str):
|
|
||||||
return self.__process_request(request, id=id)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str):
|
|
||||||
return self.__process_request(request, id=id)
|
|
||||||
|
|
||||||
|
|
||||||
class RemovePaymentView(LoginRequiredMixin, View):
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def payment_remove_view(request: HttpRequest, id: str, payment_id: str):
|
||||||
|
""" Renders a modal view for removing payments
|
||||||
|
|
||||||
def __process_request(self, request: HttpRequest, id: str, payment_id: str):
|
Args:
|
||||||
""" Renders a modal view for removing payments
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id
|
||||||
|
payment_id (str): The payment's id
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The intervention's id
|
|
||||||
payment_id (str): The payment's id
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
"""
|
payment = get_object_or_404(Payment, id=payment_id)
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
||||||
payment = get_object_or_404(Payment, id=payment_id)
|
return form.process_request(
|
||||||
form = RemovePaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
request=request,
|
||||||
return form.process_request(
|
msg_success=PAYMENT_REMOVED,
|
||||||
request=request,
|
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
||||||
msg_success=PAYMENT_REMOVED,
|
)
|
||||||
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
|
||||||
)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str, payment_id: str):
|
|
||||||
return self.__process_request(request, id=id, payment_id=payment_id)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, payment_id: str):
|
|
||||||
return self.__process_request(request, id=id, payment_id=payment_id)
|
|
||||||
|
|
||||||
|
|
||||||
class EditPaymentView(LoginRequiredMixin, View):
|
@login_required
|
||||||
def __process_request(self, request: HttpRequest, id: str, payment_id: str):
|
@default_group_required
|
||||||
""" Renders a modal view for editing payments
|
@shared_access_required(Intervention, "id")
|
||||||
|
def payment_edit_view(request: HttpRequest, id: str, payment_id: str):
|
||||||
|
""" Renders a modal view for editing payments
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): The intervention's id
|
id (str): The intervention's id
|
||||||
payment_id (str): The payment's id
|
payment_id (str): The payment's id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
payment = get_object_or_404(Payment, id=payment_id)
|
payment = get_object_or_404(Payment, id=payment_id)
|
||||||
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
form = EditPaymentModalForm(request.POST or None, instance=intervention, payment=payment, request=request)
|
||||||
return form.process_request(
|
return form.process_request(
|
||||||
request=request,
|
request=request,
|
||||||
msg_success=PAYMENT_EDITED,
|
msg_success=PAYMENT_EDITED,
|
||||||
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
redirect_url=reverse("intervention:detail", args=(payment.intervention_id,)) + "#related_data"
|
||||||
)
|
)
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str, payment_id: str):
|
|
||||||
return self.__process_request(request, id=id, payment_id=payment_id)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, payment_id: str):
|
|
||||||
return self.__process_request(request, id=id, payment_id=payment_id)
|
|
||||||
+2
-2
@@ -88,7 +88,7 @@ class EmaTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
txt=value,
|
txt=value,
|
||||||
new_tab=False,
|
new_tab=False,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
def render_r(self, value, record: Ema):
|
def render_r(self, value, record: Ema):
|
||||||
""" Renders the registered column for a EMA
|
""" Renders the registered column for a EMA
|
||||||
@@ -110,4 +110,4 @@ class EmaTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=recorded,
|
icn_filled=recorded,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Resubmission' %}" data-form-url="{% url 'ema:resubmission-create' obj.id %}">
|
||||||
{% fa5_icon 'bell' %}
|
{% fa5_icon 'bell' %}
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
|
||||||
|
{% fa5_icon 'share-alt' %}
|
||||||
|
</button>
|
||||||
{% if is_ets_member %}
|
{% if is_ets_member %}
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Share' %}" data-form-url="{% url 'ema:share-form' obj.id %}">
|
|
||||||
{% fa5_icon 'share-alt' %}
|
|
||||||
</button>
|
|
||||||
{% if obj.recorded %}
|
{% if obj.recorded %}
|
||||||
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
|
<button class="btn btn-default btn-modal mr-2" title="{% trans 'Unrecord' %}" data-form-url="{% url 'ema:record' obj.id %}">
|
||||||
{% fa5_icon 'bookmark' 'far' %}
|
{% fa5_icon 'bookmark' 'far' %}
|
||||||
@@ -28,21 +28,19 @@
|
|||||||
{% fa5_icon 'bookmark' %}
|
{% fa5_icon 'bookmark' %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
|
|
||||||
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
|
||||||
{% fa5_icon 'edit' %}
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_default_member %}
|
{% if is_default_member %}
|
||||||
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
|
<a href="{% url 'ema:edit' obj.id %}" class="mr-2">
|
||||||
{% fa5_icon 'history' %}
|
<button class="btn btn-default" title="{% trans 'Edit' %}">
|
||||||
</button>
|
{% fa5_icon 'edit' %}
|
||||||
{% endif %}
|
|
||||||
{% if is_ets_member %}
|
|
||||||
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
|
|
||||||
{% fa5_icon 'trash' %}
|
|
||||||
</button>
|
</button>
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-default btn-modal mr-2" data-form-url="{% url 'ema:log' obj.id %}" title="{% trans 'Show log' %}">
|
||||||
|
{% fa5_icon 'history' %}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default btn-modal" data-form-url="{% url 'ema:remove' obj.id %}" title="{% trans 'Delete' %}">
|
||||||
|
{% fa5_icon 'trash' %}
|
||||||
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -118,7 +118,6 @@ class EmaViewTestCase(CompensationViewTestCase):
|
|||||||
self.index_url,
|
self.index_url,
|
||||||
self.detail_url,
|
self.detail_url,
|
||||||
self.report_url,
|
self.report_url,
|
||||||
self.log_url,
|
|
||||||
]
|
]
|
||||||
fail_urls = [
|
fail_urls = [
|
||||||
self.new_url,
|
self.new_url,
|
||||||
@@ -134,6 +133,7 @@ class EmaViewTestCase(CompensationViewTestCase):
|
|||||||
self.action_remove_url,
|
self.action_remove_url,
|
||||||
self.action_new_url,
|
self.action_new_url,
|
||||||
self.new_doc_url,
|
self.new_doc_url,
|
||||||
|
self.log_url,
|
||||||
self.remove_url,
|
self.remove_url,
|
||||||
]
|
]
|
||||||
self.assert_url_fail(client, fail_urls)
|
self.assert_url_fail(client, fail_urls)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
new_title = self.create_dummy_string()
|
new_title = self.create_dummy_string()
|
||||||
new_identifier = self.create_dummy_string()
|
new_identifier = self.create_dummy_string()
|
||||||
new_comment = self.create_dummy_string()
|
new_comment = self.create_dummy_string()
|
||||||
new_geometry = self.create_dummy_geometry() # Create an empty geometry
|
new_geometry = MultiPolygon(srid=4326) # Create an empty geometry
|
||||||
test_conservation_office = self.get_conservation_office_code()
|
test_conservation_office = self.get_conservation_office_code()
|
||||||
|
|
||||||
check_on_elements = {
|
check_on_elements = {
|
||||||
@@ -99,7 +99,7 @@ class EmaWorkflowTestCase(BaseWorkflowTestCase):
|
|||||||
"identifier": new_identifier,
|
"identifier": new_identifier,
|
||||||
"title": new_title,
|
"title": new_title,
|
||||||
"comment": new_comment,
|
"comment": new_comment,
|
||||||
"output": self.create_geojson(new_geometry),
|
"output": new_geometry.geojson,
|
||||||
"conservation_office": test_conservation_office.id
|
"conservation_office": test_conservation_office.id
|
||||||
}
|
}
|
||||||
self.client_user.post(url, post_data)
|
self.client_user.post(url, post_data)
|
||||||
|
|||||||
+9
-11
@@ -9,28 +9,26 @@ from django.urls import path
|
|||||||
|
|
||||||
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
|
from ema.views.action import NewEmaActionView, EditEmaActionView, RemoveEmaActionView
|
||||||
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
|
from ema.views.deadline import NewEmaDeadlineView, EditEmaDeadlineView, RemoveEmaDeadlineView
|
||||||
from ema.views.detail import DetailEmaView
|
|
||||||
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
|
from ema.views.document import NewEmaDocumentView, EditEmaDocumentView, RemoveEmaDocumentView, GetEmaDocumentView
|
||||||
from ema.views.ema import IndexEmaView, EmaIdentifierGeneratorView, EditEmaView, NewEmaView
|
from ema.views.ema import index_view, new_view, new_id_view, detail_view, edit_view, remove_view
|
||||||
from ema.views.log import EmaLogView
|
from ema.views.log import EmaLogView
|
||||||
from ema.views.record import EmaRecordView
|
from ema.views.record import EmaRecordView
|
||||||
from ema.views.remove import RemoveEmaView
|
from ema.views.report import report_view
|
||||||
from ema.views.report import EmaPublicReportView
|
|
||||||
from ema.views.resubmission import EmaResubmissionView
|
from ema.views.resubmission import EmaResubmissionView
|
||||||
from ema.views.share import EmaShareFormView, EmaShareByTokenView
|
from ema.views.share import EmaShareFormView, EmaShareByTokenView
|
||||||
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
|
from ema.views.state import NewEmaStateView, EditEmaStateView, RemoveEmaStateView
|
||||||
|
|
||||||
app_name = "ema"
|
app_name = "ema"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", IndexEmaView.as_view(), name="index"),
|
path("", index_view, name="index"),
|
||||||
path("new/", NewEmaView.as_view(), name="new"),
|
path("new/", new_view, name="new"),
|
||||||
path("new/id", EmaIdentifierGeneratorView.as_view(), name="new-id"),
|
path("new/id", new_id_view, name="new-id"),
|
||||||
path("<id>", DetailEmaView.as_view(), name="detail"),
|
path("<id>", detail_view, name="detail"),
|
||||||
path('<id>/log', EmaLogView.as_view(), name='log'),
|
path('<id>/log', EmaLogView.as_view(), name='log'),
|
||||||
path('<id>/edit', EditEmaView.as_view(), name='edit'),
|
path('<id>/edit', edit_view, name='edit'),
|
||||||
path('<id>/remove', RemoveEmaView.as_view(), name='remove'),
|
path('<id>/remove', remove_view, name='remove'),
|
||||||
path('<id>/record', EmaRecordView.as_view(), name='record'),
|
path('<id>/record', EmaRecordView.as_view(), name='record'),
|
||||||
path('<id>/report', EmaPublicReportView.as_view(), name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', EmaResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),
|
path('<id>/state/new', NewEmaStateView.as_view(), name='new-state'),
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.http import HttpResponse, HttpRequest
|
|
||||||
from django.shortcuts import get_object_or_404, render
|
|
||||||
|
|
||||||
from ema.models import Ema
|
|
||||||
from konova.contexts import BaseContext
|
|
||||||
from konova.forms import SimpleGeomForm
|
|
||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.utils.message_templates import DO_NOT_FORGET_TO_SHARE
|
|
||||||
from konova.views.detail import AbstractDetailView
|
|
||||||
|
|
||||||
|
|
||||||
class DetailEmaView(AbstractDetailView):
|
|
||||||
_TEMPLATE = "ema/detail/view.html"
|
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
""" Renders the detail view of an EMA
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The EMA id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
ema = get_object_or_404(Ema, id=id, deleted=None)
|
|
||||||
|
|
||||||
geom_form = SimpleGeomForm(instance=ema)
|
|
||||||
parcels = ema.get_underlying_parcels()
|
|
||||||
_user = request.user
|
|
||||||
is_entry_shared = ema.is_shared_with(_user)
|
|
||||||
|
|
||||||
# Order states according to surface
|
|
||||||
before_states = ema.before_states.all().order_by("-surface")
|
|
||||||
after_states = ema.after_states.all().order_by("-surface")
|
|
||||||
|
|
||||||
# Precalculate logical errors between before- and after-states
|
|
||||||
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
|
||||||
sum_before_states = ema.get_surface_before_states()
|
|
||||||
sum_after_states = ema.get_surface_after_states()
|
|
||||||
diff_states = abs(sum_before_states - sum_after_states)
|
|
||||||
|
|
||||||
ema.set_status_messages(request)
|
|
||||||
|
|
||||||
requesting_user_is_only_shared_user = ema.is_only_shared_with(_user)
|
|
||||||
if requesting_user_is_only_shared_user:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
DO_NOT_FORGET_TO_SHARE
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"obj": ema,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"parcels": parcels,
|
|
||||||
"is_entry_shared": is_entry_shared,
|
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"sum_before_states": sum_before_states,
|
|
||||||
"sum_after_states": sum_after_states,
|
|
||||||
"diff_states": diff_states,
|
|
||||||
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
|
||||||
"is_zb_member": _user.in_group(ZB_GROUP),
|
|
||||||
"is_ets_member": _user.in_group(ETS_GROUP),
|
|
||||||
"LANIS_LINK": ema.get_LANIS_link(),
|
|
||||||
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
|
|
||||||
"has_finished_deadlines": ema.get_finished_deadlines().exists(),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
+184
-157
@@ -7,96 +7,71 @@ Created on: 19.08.22
|
|||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.db.models import Sum
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, JsonResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.decorators import method_decorator
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic.base import View
|
|
||||||
|
|
||||||
from ema.forms import NewEmaForm, EditEmaForm
|
from ema.forms import NewEmaForm, EditEmaForm
|
||||||
from ema.models import Ema
|
from ema.models import Ema
|
||||||
from ema.tables import EmaTable
|
from ema.tables import EmaTable
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import shared_access_required, conservation_office_group_required
|
from konova.decorators import shared_access_required, conservation_office_group_required, login_required_modal, \
|
||||||
|
uuid_required
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.forms.modals import RemoveModalForm
|
||||||
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
|
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, IDENTIFIER_REPLACED, FORM_INVALID, \
|
||||||
GEOMETRY_SIMPLIFIED, GEOMETRIES_IGNORED_TEMPLATE
|
DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
from konova.views.identifier import AbstractIdentifierGeneratorView
|
|
||||||
from konova.views.index import AbstractIndexView
|
|
||||||
|
|
||||||
|
|
||||||
class IndexEmaView(AbstractIndexView):
|
@login_required
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def index_view(request: HttpRequest):
|
||||||
""" Renders the index view for EMAs
|
""" Renders the index view for EMAs
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
emas = Ema.objects.filter(
|
template = "generic_index.html"
|
||||||
deleted=None,
|
emas = Ema.objects.filter(
|
||||||
).order_by(
|
deleted=None,
|
||||||
"-modified__timestamp"
|
).order_by(
|
||||||
)
|
"-modified__timestamp"
|
||||||
|
)
|
||||||
|
|
||||||
table = EmaTable(
|
table = EmaTable(
|
||||||
request,
|
request,
|
||||||
queryset=emas
|
queryset=emas
|
||||||
)
|
)
|
||||||
context = {
|
context = {
|
||||||
"table": table,
|
"table": table,
|
||||||
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
|
TAB_TITLE_IDENTIFIER: _("EMAs - Overview"),
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, self._TEMPLATE, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
class NewEmaView(LoginRequiredMixin, View):
|
|
||||||
_TEMPLATE = "ema/form/view.html"
|
|
||||||
|
|
||||||
@method_decorator(conservation_office_group_required)
|
@login_required
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
@conservation_office_group_required
|
||||||
""" GET endpoint
|
def new_view(request: HttpRequest):
|
||||||
|
"""
|
||||||
|
Renders a view for a new eco account creation
|
||||||
|
|
||||||
Renders form for new EMA
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
*args ():
|
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
template = "ema/form/view.html"
|
||||||
"""
|
data_form = NewEmaForm(request.POST or None)
|
||||||
data_form = NewEmaForm(request.POST or None)
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
if request.method == "POST":
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("New EMA"),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@method_decorator(conservation_office_group_required)
|
|
||||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
""" POST endpoint
|
|
||||||
|
|
||||||
Processes submitted form
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
*args ():
|
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
data_form = NewEmaForm(request.POST or None)
|
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
ema = data_form.save(request.user, geom_form)
|
ema = data_form.save(request.user, geom_form)
|
||||||
@@ -109,124 +84,176 @@ class NewEmaView(LoginRequiredMixin, View):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
messages.success(request, _("EMA {} added").format(ema.identifier))
|
messages.success(request, _("EMA {} added").format(ema.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("ema:detail", id=ema.id)
|
return redirect("ema:detail", id=ema.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
context = {
|
else:
|
||||||
"form": data_form,
|
# For clarification: nothing in this case
|
||||||
"geom_form": geom_form,
|
pass
|
||||||
TAB_TITLE_IDENTIFIER: _("New EMA"),
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New EMA"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@conservation_office_group_required
|
||||||
|
def new_id_view(request: HttpRequest):
|
||||||
|
""" JSON endpoint
|
||||||
|
|
||||||
|
Provides fetching of free identifiers for e.g. AJAX calls
|
||||||
|
|
||||||
|
"""
|
||||||
|
tmp = Ema()
|
||||||
|
identifier = tmp.generate_new_identifier()
|
||||||
|
while Ema.objects.filter(identifier=identifier).exists():
|
||||||
|
identifier = tmp.generate_new_identifier()
|
||||||
|
return JsonResponse(
|
||||||
|
data={
|
||||||
|
"gen_data": identifier
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
)
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
class EmaIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
|
||||||
_MODEL = Ema
|
|
||||||
|
|
||||||
@method_decorator(conservation_office_group_required)
|
@login_required
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
@uuid_required
|
||||||
return super().get(request, *args, **kwargs)
|
def detail_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders the detail view of an EMA
|
||||||
|
|
||||||
class EditEmaView(LoginRequiredMixin, View):
|
Args:
|
||||||
_TEMPLATE = "compensation/form/view.html"
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The EMA id
|
||||||
|
|
||||||
@method_decorator(conservation_office_group_required)
|
Returns:
|
||||||
@method_decorator(shared_access_required(Ema, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
""" GET endpoint
|
|
||||||
|
|
||||||
Renders form
|
"""
|
||||||
|
template = "ema/detail/view.html"
|
||||||
|
ema = get_object_or_404(Ema, id=id, deleted=None)
|
||||||
|
|
||||||
Args:
|
geom_form = SimpleGeomForm(instance=ema)
|
||||||
request (HttpRequest): The incoming request
|
parcels = ema.get_underlying_parcels()
|
||||||
id (str): The ema identifier
|
_user = request.user
|
||||||
*args ():
|
is_entry_shared = ema.is_shared_with(_user)
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
# Order states according to surface
|
||||||
|
before_states = ema.before_states.all().order_by("-surface")
|
||||||
|
after_states = ema.after_states.all().order_by("-surface")
|
||||||
|
|
||||||
"""
|
# Precalculate logical errors between before- and after-states
|
||||||
# Get object from db
|
# Sum() returns None in case of no states, so we catch that and replace it with 0 for easier handling
|
||||||
ema = get_object_or_404(Ema, id=id)
|
sum_before_states = ema.get_surface_before_states()
|
||||||
if ema.is_recorded:
|
sum_after_states = ema.get_surface_after_states()
|
||||||
messages.info(
|
diff_states = abs(sum_before_states - sum_after_states)
|
||||||
request,
|
|
||||||
RECORDED_BLOCKS_EDIT
|
|
||||||
)
|
|
||||||
return redirect("ema:detail", id=id)
|
|
||||||
|
|
||||||
# Create forms, initialize with values from db/from POST request
|
ema.set_status_messages(request)
|
||||||
data_form = EditEmaForm(instance=ema)
|
|
||||||
geom_form = SimpleGeomForm(read_only=False, instance=ema)
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@method_decorator(conservation_office_group_required)
|
requesting_user_is_only_shared_user = ema.is_only_shared_with(_user)
|
||||||
@method_decorator(shared_access_required(Ema, "id"))
|
if requesting_user_is_only_shared_user:
|
||||||
def post(self, request: HttpRequest, id:str, *args, **kwargs) -> HttpResponse:
|
messages.info(
|
||||||
""" POST endpoint
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
Process submitted forms
|
context = {
|
||||||
|
"obj": ema,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"is_entry_shared": is_entry_shared,
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"sum_before_states": sum_before_states,
|
||||||
|
"sum_after_states": sum_after_states,
|
||||||
|
"diff_states": diff_states,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": ema.get_LANIS_link(),
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{ema.identifier} - {ema.title}",
|
||||||
|
"has_finished_deadlines": ema.get_finished_deadlines().exists(),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The id of the ema
|
|
||||||
*args ():
|
|
||||||
**kwargs ():
|
|
||||||
|
|
||||||
Returns:
|
@login_required
|
||||||
|
@conservation_office_group_required
|
||||||
|
@shared_access_required(Ema, "id")
|
||||||
|
def edit_view(request: HttpRequest, id: str):
|
||||||
|
"""
|
||||||
|
Renders a view for editing compensations
|
||||||
|
|
||||||
"""
|
Args:
|
||||||
# Get object from db
|
request (HttpRequest): The incoming request
|
||||||
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
|
Returns:
|
||||||
data_form = EditEmaForm(request.POST or None, instance=ema)
|
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=ema)
|
"""
|
||||||
|
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)
|
||||||
|
if request.method == "POST":
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
# The data form takes the geom form for processing, as well as the performing user
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
ema = data_form.save(request.user, geom_form)
|
ema = data_form.save(request.user, geom_form)
|
||||||
messages.success(request, _("EMA {} edited").format(ema.identifier))
|
messages.success(request, _("EMA {} edited").format(ema.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("ema:detail", id=ema.id)
|
return redirect("ema:detail", id=ema.id)
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
context = {
|
else:
|
||||||
"form": data_form,
|
# For clarification: nothing in this case
|
||||||
"geom_form": geom_form,
|
pass
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
|
context = {
|
||||||
}
|
"form": data_form,
|
||||||
context = BaseContext(request, context).context
|
"geom_form": geom_form,
|
||||||
return render(request, self._TEMPLATE, context)
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(ema.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required_modal
|
||||||
|
@login_required
|
||||||
|
@conservation_office_group_required
|
||||||
|
@shared_access_required(Ema, "id")
|
||||||
|
def remove_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a modal view for removing the EMA
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The EMA's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
ema = get_object_or_404(Ema, id=id)
|
||||||
|
form = RemoveModalForm(request.POST or None, instance=ema, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request=request,
|
||||||
|
msg_success=_("EMA removed"),
|
||||||
|
redirect_url=reverse("ema:index"),
|
||||||
|
)
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ class EmaLogView(AbstractLogView):
|
|||||||
|
|
||||||
@method_decorator(login_required_modal)
|
@method_decorator(login_required_modal)
|
||||||
@method_decorator(login_required)
|
@method_decorator(login_required)
|
||||||
|
@method_decorator(conservation_office_group_required)
|
||||||
@method_decorator(shared_access_required(Ema, "id"))
|
@method_decorator(shared_access_required(Ema, "id"))
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
|
|
||||||
from ema.models import Ema
|
|
||||||
from konova.decorators import shared_access_required, conservation_office_group_required
|
|
||||||
from konova.views.remove import AbstractRemoveView
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveEmaView(AbstractRemoveView):
|
|
||||||
_MODEL = Ema
|
|
||||||
_REDIRECT_URL = "ema:index"
|
|
||||||
|
|
||||||
@method_decorator(conservation_office_group_required)
|
|
||||||
@method_decorator(shared_access_required(Ema, "id"))
|
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
+55
-60
@@ -5,7 +5,7 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -14,72 +14,67 @@ from ema.models import Ema
|
|||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.generators import generate_qr_code
|
||||||
from konova.views.report import AbstractPublicReportView
|
|
||||||
|
|
||||||
|
|
||||||
class EmaPublicReportView(AbstractPublicReportView):
|
def report_view(request:HttpRequest, id: str):
|
||||||
_TEMPLATE = "ema/report/report.html"
|
""" Renders the public report view
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
Args:
|
||||||
""" Renders the public report view
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The id of the intervention
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
# Reuse the compensation report template since EMAs are structurally identical
|
||||||
"""
|
template = "ema/report/report.html"
|
||||||
ema = get_object_or_404(Ema, id=id)
|
ema = get_object_or_404(Ema, id=id)
|
||||||
tab_title = _("Report {}").format(ema.identifier)
|
|
||||||
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
|
||||||
if not ema.is_ready_for_publish():
|
|
||||||
template = "report/unavailable.html"
|
|
||||||
context = {
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
# Prepare data for map viewer
|
|
||||||
geom_form = SimpleGeomForm(
|
|
||||||
instance=ema,
|
|
||||||
)
|
|
||||||
parcels = ema.get_underlying_parcels()
|
|
||||||
|
|
||||||
qrcode = QrCode(
|
|
||||||
content=request.build_absolute_uri(reverse("ema:report", args=(id,))),
|
|
||||||
size=10
|
|
||||||
)
|
|
||||||
qrcode_lanis = QrCode(
|
|
||||||
content=ema.get_LANIS_link(),
|
|
||||||
size=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")
|
|
||||||
actions = ema.actions.all().prefetch_related("action_type")
|
|
||||||
|
|
||||||
|
tab_title = _("Report {}").format(ema.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not ema.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
context = {
|
context = {
|
||||||
"obj": ema,
|
|
||||||
"qrcode": {
|
|
||||||
"img": qrcode.get_img(),
|
|
||||||
"url": qrcode.get_content(),
|
|
||||||
},
|
|
||||||
"qrcode_lanis": {
|
|
||||||
"img": qrcode_lanis.get_img(),
|
|
||||||
"url": qrcode_lanis.get_content(),
|
|
||||||
},
|
|
||||||
"is_entry_shared": False, # disables action buttons during rendering
|
|
||||||
"before_states": before_states,
|
|
||||||
"after_states": after_states,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"parcels": parcels,
|
|
||||||
"actions": actions,
|
|
||||||
"tables_scrollable": False,
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, self._TEMPLATE, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=ema,
|
||||||
|
)
|
||||||
|
parcels = ema.get_underlying_parcels()
|
||||||
|
|
||||||
|
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")
|
||||||
|
actions = ema.actions.all().prefetch_related("action_type")
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": ema,
|
||||||
|
"qrcode": {
|
||||||
|
"img": qrcode_img,
|
||||||
|
"url": qrcode_url
|
||||||
|
},
|
||||||
|
"qrcode_lanis": {
|
||||||
|
"img": qrcode_img_lanis,
|
||||||
|
"url": qrcode_lanis_url
|
||||||
|
},
|
||||||
|
"is_entry_shared": False, # disables action buttons during rendering
|
||||||
|
"before_states": before_states,
|
||||||
|
"after_states": after_states,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"actions": actions,
|
||||||
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
@@ -37,14 +37,6 @@ class InterventionAdmin(BaseObjectAdmin):
|
|||||||
"geometry",
|
"geometry",
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_actions(self, request):
|
|
||||||
DELETE_ACTION_IDENTIFIER = "delete_selected"
|
|
||||||
actions = super().get_actions(request)
|
|
||||||
|
|
||||||
if DELETE_ACTION_IDENTIFIER in actions:
|
|
||||||
del actions[DELETE_ACTION_IDENTIFIER]
|
|
||||||
|
|
||||||
return actions
|
|
||||||
|
|
||||||
class InterventionDocumentAdmin(AbstractDocumentAdmin):
|
class InterventionDocumentAdmin(AbstractDocumentAdmin):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class InterventionTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
html += self.render_previously_checked_star(
|
html += self.render_previously_checked_star(
|
||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
def render_r(self, value, record: Intervention):
|
def render_r(self, value, record: Intervention):
|
||||||
""" Renders the recorded column for an intervention
|
""" Renders the recorded column for an intervention
|
||||||
@@ -149,5 +149,5 @@ class InterventionTable(BaseTable, TableRenderMixin, TableOrderMixin):
|
|||||||
tooltip=tooltip,
|
tooltip=tooltip,
|
||||||
icn_filled=checked,
|
icn_filled=checked,
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
@@ -36,7 +36,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.run_check_url = reverse("intervention:check", args=(self.intervention.id,))
|
self.run_check_url = reverse("intervention:check", args=(self.intervention.id,))
|
||||||
self.record_url = reverse("intervention:record", args=(self.intervention.id,))
|
self.record_url = reverse("intervention:record", args=(self.intervention.id,))
|
||||||
self.report_url = reverse("intervention:report", args=(self.intervention.id,))
|
self.report_url = reverse("intervention:report", args=(self.intervention.id,))
|
||||||
self.compensation_remove_url = reverse("intervention:remove-compensation", args=(self.compensation.intervention.id, self.compensation.id))
|
|
||||||
|
|
||||||
self.deduction.intervention = self.intervention
|
self.deduction.intervention = self.intervention
|
||||||
self.deduction.save()
|
self.deduction.save()
|
||||||
@@ -84,7 +83,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
|
self.revocation_new_url: f"{login_redirect_base}{self.revocation_new_url}",
|
||||||
self.revocation_edit_url: f"{login_redirect_base}{self.revocation_edit_url}",
|
self.revocation_edit_url: f"{login_redirect_base}{self.revocation_edit_url}",
|
||||||
self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
|
self.revocation_remove_url: f"{login_redirect_base}{self.revocation_remove_url}",
|
||||||
self.compensation_remove_url: f"{login_redirect_base}{self.compensation_remove_url}",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.assert_url_success(client, success_urls)
|
self.assert_url_success(client, success_urls)
|
||||||
@@ -126,7 +124,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
self.compensation_remove_url,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assert_url_success(client, success_urls)
|
self.assert_url_success(client, success_urls)
|
||||||
@@ -165,7 +162,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
self.compensation_remove_url,
|
|
||||||
]
|
]
|
||||||
fail_urls = [
|
fail_urls = [
|
||||||
self.run_check_url,
|
self.run_check_url,
|
||||||
@@ -216,7 +212,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
self.compensation_remove_url,
|
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -263,7 +258,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
self.compensation_remove_url,
|
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -310,7 +304,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
self.compensation_remove_url,
|
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -357,7 +350,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
self.compensation_remove_url,
|
|
||||||
]
|
]
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
self.share_url: self.detail_url
|
self.share_url: self.detail_url
|
||||||
@@ -404,7 +396,6 @@ class InterventionViewTestCase(BaseViewTestCase):
|
|||||||
self.deduction_new_url,
|
self.deduction_new_url,
|
||||||
self.deduction_edit_url,
|
self.deduction_edit_url,
|
||||||
self.deduction_remove_url,
|
self.deduction_remove_url,
|
||||||
self.compensation_remove_url,
|
|
||||||
]
|
]
|
||||||
# Define urls where a redirect to a specific location is the proper response
|
# Define urls where a redirect to a specific location is the proper response
|
||||||
success_urls_redirect = {
|
success_urls_redirect = {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class EditInterventionFormTestCase(NewInterventionFormTestCase):
|
|||||||
self.assertIsNotNone(obj.responsible.handler)
|
self.assertIsNotNone(obj.responsible.handler)
|
||||||
self.assertEqual(obj.title, data["title"])
|
self.assertEqual(obj.title, data["title"])
|
||||||
self.assertEqual(obj.comment, data["comment"])
|
self.assertEqual(obj.comment, data["comment"])
|
||||||
self.assert_equal_geometries(test_geom, obj.geometry.geom)
|
self.assertTrue(test_geom.equals_exact(obj.geometry.geom, 0.000001))
|
||||||
|
|
||||||
self.assertEqual(obj.legal.binding_date, today)
|
self.assertEqual(obj.legal.binding_date, today)
|
||||||
self.assertEqual(obj.legal.registration_date, today)
|
self.assertEqual(obj.legal.registration_date, today)
|
||||||
|
|||||||
+19
-22
@@ -8,42 +8,39 @@ Created on: 30.11.20
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from intervention.autocomplete.intervention import InterventionAutocomplete
|
from intervention.autocomplete.intervention import InterventionAutocomplete
|
||||||
from intervention.views.check import InterventionCheckView
|
from intervention.views.check import check_view
|
||||||
from intervention.views.compensation import RemoveCompensationFromInterventionView
|
from intervention.views.compensation import remove_compensation_view
|
||||||
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
|
from intervention.views.deduction import NewInterventionDeductionView, EditInterventionDeductionView, \
|
||||||
RemoveInterventionDeductionView
|
RemoveInterventionDeductionView
|
||||||
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
|
from intervention.views.document import NewInterventionDocumentView, GetInterventionDocumentView, \
|
||||||
RemoveInterventionDocumentView, EditInterventionDocumentView
|
RemoveInterventionDocumentView, EditInterventionDocumentView
|
||||||
from intervention.views.intervention import IndexInterventionView, InterventionIdentifierGeneratorView, \
|
from intervention.views.intervention import index_view, new_view, new_id_view, detail_view, edit_view, remove_view
|
||||||
NewInterventionView, EditInterventionView
|
|
||||||
from intervention.views.remove import RemoveInterventionView
|
|
||||||
from intervention.views.detail import DetailInterventionView
|
|
||||||
from intervention.views.log import InterventionLogView
|
from intervention.views.log import InterventionLogView
|
||||||
from intervention.views.record import InterventionRecordView
|
from intervention.views.record import InterventionRecordView
|
||||||
from intervention.views.report import InterventionPublicReportView
|
from intervention.views.report import report_view
|
||||||
from intervention.views.resubmission import InterventionResubmissionView
|
from intervention.views.resubmission import InterventionResubmissionView
|
||||||
from intervention.views.revocation import NewInterventionRevocationView, GetInterventionRevocationView, \
|
from intervention.views.revocation import new_revocation_view, edit_revocation_view, remove_revocation_view, \
|
||||||
EditInterventionRevocationView, RemoveInterventionRevocationView
|
get_revocation_view
|
||||||
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
|
from intervention.views.share import InterventionShareFormView, InterventionShareByTokenView
|
||||||
|
|
||||||
app_name = "intervention"
|
app_name = "intervention"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", IndexInterventionView.as_view(), name="index"),
|
path("", index_view, name="index"),
|
||||||
path('new/', NewInterventionView.as_view(), name='new'),
|
path('new/', new_view, name='new'),
|
||||||
path('new/id', InterventionIdentifierGeneratorView.as_view(), name='new-id'),
|
path('new/id', new_id_view, name='new-id'),
|
||||||
path('<id>', DetailInterventionView.as_view(), name='detail'),
|
path('<id>', detail_view, name='detail'),
|
||||||
path('<id>/log', InterventionLogView.as_view(), name='log'),
|
path('<id>/log', InterventionLogView.as_view(), name='log'),
|
||||||
path('<id>/edit', EditInterventionView.as_view(), name='edit'),
|
path('<id>/edit', edit_view, name='edit'),
|
||||||
path('<id>/remove', RemoveInterventionView.as_view(), name='remove'),
|
path('<id>/remove', remove_view, name='remove'),
|
||||||
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
|
path('<id>/share/<token>', InterventionShareByTokenView.as_view(), name='share-token'),
|
||||||
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
|
path('<id>/share', InterventionShareFormView.as_view(), name='share-form'),
|
||||||
path('<id>/check', InterventionCheckView.as_view(), name='check'),
|
path('<id>/check', check_view, name='check'),
|
||||||
path('<id>/record', InterventionRecordView.as_view(), name='record'),
|
path('<id>/record', InterventionRecordView.as_view(), name='record'),
|
||||||
path('<id>/report', InterventionPublicReportView.as_view(), name='report'),
|
path('<id>/report', report_view, name='report'),
|
||||||
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
|
path('<id>/resub', InterventionResubmissionView.as_view(), name='resubmission-create'),
|
||||||
|
|
||||||
# Compensations
|
# Compensations
|
||||||
path('<id>/compensation/<comp_id>/remove', RemoveCompensationFromInterventionView.as_view(), name='remove-compensation'),
|
path('<id>/compensation/<comp_id>/remove', remove_compensation_view, name='remove-compensation'),
|
||||||
|
|
||||||
# Documents
|
# Documents
|
||||||
path('<id>/document/new/', NewInterventionDocumentView.as_view(), name='new-doc'),
|
path('<id>/document/new/', NewInterventionDocumentView.as_view(), name='new-doc'),
|
||||||
@@ -57,10 +54,10 @@ urlpatterns = [
|
|||||||
path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'),
|
path('<id>/deduction/<deduction_id>/remove', RemoveInterventionDeductionView.as_view(), name='remove-deduction'),
|
||||||
|
|
||||||
# Revocation routes
|
# Revocation routes
|
||||||
path('<id>/revocation/new', NewInterventionRevocationView.as_view(), name='new-revocation'),
|
path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
|
||||||
path('<id>/revocation/<revocation_id>/edit', EditInterventionRevocationView.as_view(), name='edit-revocation'),
|
path('<id>/revocation/<revocation_id>/edit', edit_revocation_view, name='edit-revocation'),
|
||||||
path('<id>/revocation/<revocation_id>/remove', RemoveInterventionRevocationView.as_view(), name='remove-revocation'),
|
path('<id>/revocation/<revocation_id>/remove', remove_revocation_view, name='remove-revocation'),
|
||||||
path('revocation/<doc_id>', GetInterventionRevocationView.as_view(), name='get-doc-revocation'),
|
path('revocation/<doc_id>', get_revocation_view, name='get-doc-revocation'),
|
||||||
|
|
||||||
# Autocomplete
|
# Autocomplete
|
||||||
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),
|
path("atcmplt/interventions", InterventionAutocomplete.as_view(), name="autocomplete"),
|
||||||
|
|||||||
+19
-28
@@ -5,44 +5,35 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from intervention.forms.modals.check import CheckModalForm
|
from intervention.forms.modals.check import CheckModalForm
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.decorators import registration_office_group_required, shared_access_required
|
from konova.decorators import registration_office_group_required, shared_access_required
|
||||||
from konova.utils.message_templates import INTERVENTION_INVALID
|
from konova.utils.message_templates import INTERVENTION_INVALID
|
||||||
|
|
||||||
class InterventionCheckView(LoginRequiredMixin, View):
|
|
||||||
|
|
||||||
def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
@login_required
|
||||||
""" Renders check form for an intervention
|
@registration_office_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def check_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders check form for an intervention
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): Intervention's id
|
id (str): Intervention's id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
form = CheckModalForm(request.POST or None, instance=intervention, request=request)
|
form = CheckModalForm(request.POST or None, instance=intervention, request=request)
|
||||||
return form.process_request(
|
return form.process_request(
|
||||||
request,
|
request,
|
||||||
msg_success=_("Check performed"),
|
msg_success=_("Check performed"),
|
||||||
msg_error=INTERVENTION_INVALID
|
msg_error=INTERVENTION_INVALID
|
||||||
)
|
)
|
||||||
|
|
||||||
@method_decorator(registration_office_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, *args, **kwargs)
|
|
||||||
|
|
||||||
@method_decorator(registration_office_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, *args, **kwargs)
|
|
||||||
@@ -5,52 +5,42 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.http import HttpRequest, Http404, HttpResponse
|
from django.http import HttpRequest, Http404
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.decorators import shared_access_required, default_group_required
|
from konova.decorators import shared_access_required, login_required_modal
|
||||||
from konova.forms.modals import RemoveModalForm
|
from konova.forms.modals import RemoveModalForm
|
||||||
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
from konova.utils.message_templates import COMPENSATION_REMOVED_TEMPLATE
|
||||||
|
|
||||||
|
|
||||||
class RemoveCompensationFromInterventionView(LoginRequiredMixin, View):
|
@login_required_modal
|
||||||
|
@login_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def remove_compensation_view(request: HttpRequest, id: str, comp_id: str):
|
||||||
|
""" Renders a modal view for removing the compensation
|
||||||
|
|
||||||
def __process_request(self, request: HttpRequest, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
|
Args:
|
||||||
""" Renders a modal view for removing the compensation
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The compensation's id
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The compensation's id
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
"""
|
try:
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
comp = intervention.compensations.get(
|
||||||
try:
|
id=comp_id
|
||||||
comp = intervention.compensations.get(
|
|
||||||
id=comp_id
|
|
||||||
)
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
raise Http404("Unknown compensation")
|
|
||||||
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
|
|
||||||
return form.process_request(
|
|
||||||
request=request,
|
|
||||||
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
|
|
||||||
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data",
|
|
||||||
)
|
)
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
raise Http404("Unknown compensation")
|
||||||
|
form = RemoveModalForm(request.POST or None, instance=comp, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request=request,
|
||||||
|
msg_success=COMPENSATION_REMOVED_TEMPLATE.format(comp.identifier),
|
||||||
|
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data",
|
||||||
|
)
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, comp_id, *args, **kwargs)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request, id: str, comp_id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, comp_id, *args, **kwargs)
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import get_object_or_404, render
|
|
||||||
|
|
||||||
from intervention.models import Intervention
|
|
||||||
from konova.contexts import BaseContext
|
|
||||||
from konova.forms import SimpleGeomForm
|
|
||||||
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
|
||||||
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, DO_NOT_FORGET_TO_SHARE
|
|
||||||
from konova.views.detail import AbstractDetailView
|
|
||||||
|
|
||||||
|
|
||||||
class DetailInterventionView(AbstractDetailView):
|
|
||||||
_TEMPLATE = "intervention/detail/view.html"
|
|
||||||
|
|
||||||
def get(self, request, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
# Fetch data, filter out deleted related data
|
|
||||||
intervention = get_object_or_404(
|
|
||||||
Intervention.objects.select_related(
|
|
||||||
"geometry",
|
|
||||||
"legal",
|
|
||||||
"responsible",
|
|
||||||
).prefetch_related(
|
|
||||||
"legal__revocations",
|
|
||||||
),
|
|
||||||
id=id,
|
|
||||||
deleted=None
|
|
||||||
)
|
|
||||||
compensations = intervention.compensations.filter(
|
|
||||||
deleted=None,
|
|
||||||
)
|
|
||||||
_user = request.user
|
|
||||||
is_data_shared = intervention.is_shared_with(user=_user)
|
|
||||||
|
|
||||||
geom_form = SimpleGeomForm(
|
|
||||||
instance=intervention,
|
|
||||||
)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
|
|
||||||
|
|
||||||
requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user)
|
|
||||||
if requesting_user_is_only_shared_user:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
DO_NOT_FORGET_TO_SHARE
|
|
||||||
)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"obj": intervention,
|
|
||||||
"last_checked": last_checked,
|
|
||||||
"last_checked_tooltip": last_checked_tooltip,
|
|
||||||
"compensations": compensations,
|
|
||||||
"is_entry_shared": is_data_shared,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
|
||||||
"is_zb_member": _user.in_group(ZB_GROUP),
|
|
||||||
"is_ets_member": _user.in_group(ETS_GROUP),
|
|
||||||
"LANIS_LINK": intervention.get_LANIS_link(),
|
|
||||||
"has_payment_without_document": has_payment_without_document,
|
|
||||||
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
|
|
||||||
}
|
|
||||||
|
|
||||||
request = intervention.set_status_messages(request)
|
|
||||||
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
+213
-166
@@ -7,98 +7,75 @@ Created on: 19.08.22
|
|||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.http import JsonResponse, HttpRequest
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.shortcuts import get_object_or_404, render, redirect
|
from django.shortcuts import get_object_or_404, render, redirect
|
||||||
from django.utils.decorators import method_decorator
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
|
from intervention.forms.intervention import EditInterventionForm, NewInterventionForm
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from intervention.tables import InterventionTable
|
from intervention.tables import InterventionTable
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
from konova.decorators import default_group_required, shared_access_required
|
from konova.decorators import default_group_required, shared_access_required, any_group_check, login_required_modal, \
|
||||||
|
uuid_required
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
|
from konova.forms.modals import RemoveModalForm
|
||||||
|
from konova.settings import DEFAULT_GROUP, ZB_GROUP, ETS_GROUP
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.message_templates import RECORDED_BLOCKS_EDIT, \
|
from konova.utils.message_templates import DATA_CHECKED_PREVIOUSLY_TEMPLATE, RECORDED_BLOCKS_EDIT, \
|
||||||
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, GEOMETRY_SIMPLIFIED, \
|
CHECK_STATE_RESET, FORM_INVALID, IDENTIFIER_REPLACED, DO_NOT_FORGET_TO_SHARE, GEOMETRY_SIMPLIFIED
|
||||||
GEOMETRIES_IGNORED_TEMPLATE
|
|
||||||
from konova.views.identifier import AbstractIdentifierGeneratorView
|
|
||||||
from konova.views.index import AbstractIndexView
|
|
||||||
|
|
||||||
|
|
||||||
class IndexInterventionView(AbstractIndexView):
|
@login_required
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
@any_group_check
|
||||||
"""
|
def index_view(request: HttpRequest):
|
||||||
Renders the index view for Interventions
|
"""
|
||||||
|
Renders the index view for Interventions
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A rendered view
|
A rendered view
|
||||||
"""
|
"""
|
||||||
# Filtering by user access is performed in table filter inside InterventionTableFilter class
|
template = "generic_index.html"
|
||||||
interventions = Intervention.objects.filter(
|
|
||||||
deleted=None, # not deleted
|
# Filtering by user access is performed in table filter inside InterventionTableFilter class
|
||||||
).select_related(
|
interventions = Intervention.objects.filter(
|
||||||
"legal"
|
deleted=None, # not deleted
|
||||||
).order_by(
|
).select_related(
|
||||||
"-modified__timestamp"
|
"legal"
|
||||||
)
|
).order_by(
|
||||||
table = InterventionTable(
|
"-modified__timestamp"
|
||||||
request=request,
|
)
|
||||||
queryset=interventions
|
table = InterventionTable(
|
||||||
)
|
request=request,
|
||||||
context = {
|
queryset=interventions
|
||||||
"table": table,
|
)
|
||||||
TAB_TITLE_IDENTIFIER: _("Interventions - Overview"),
|
context = {
|
||||||
}
|
"table": table,
|
||||||
context = BaseContext(request, context).context
|
TAB_TITLE_IDENTIFIER: _("Interventions - Overview"),
|
||||||
return render(request, self._TEMPLATE, context)
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
class NewInterventionView(LoginRequiredMixin, View):
|
@login_required
|
||||||
_TEMPLATE = "intervention/form/view.html"
|
@default_group_required
|
||||||
|
def new_view(request: HttpRequest):
|
||||||
|
"""
|
||||||
|
Renders a view for a new intervention creation
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
Args:
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
request (HttpRequest): The incoming request
|
||||||
|
|
||||||
"""
|
Returns:
|
||||||
Renders a view for a new intervention creation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
data_form = NewInterventionForm()
|
|
||||||
geom_form = SimpleGeomForm(read_only=False)
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("New intervention"),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Renders a view for a new intervention creation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
data_form = NewInterventionForm(request.POST or None)
|
|
||||||
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "intervention/form/view.html"
|
||||||
|
data_form = NewInterventionForm(request.POST or None)
|
||||||
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False)
|
||||||
|
if request.method == "POST":
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
generated_identifier = data_form.cleaned_data.get("identifier", None)
|
||||||
intervention = data_form.save(request.user, geom_form)
|
intervention = data_form.save(request.user, geom_form)
|
||||||
@@ -110,100 +87,147 @@ class NewInterventionView(LoginRequiredMixin, View):
|
|||||||
intervention.identifier
|
intervention.identifier
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
messages.success(request, _("Intervention {} added").format(intervention.identifier))
|
messages.success(request, _("Intervention {} added").format(intervention.identifier))
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("intervention:detail", id=intervention.id)
|
return redirect("intervention:detail", id=intervention.id)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
messages.error(request, FORM_INVALID, extra_tags="danger", )
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
context = {
|
else:
|
||||||
"form": data_form,
|
# For clarification: nothing in this case
|
||||||
"geom_form": geom_form,
|
pass
|
||||||
TAB_TITLE_IDENTIFIER: _("New intervention"),
|
context = {
|
||||||
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("New intervention"),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
def new_id_view(request: HttpRequest):
|
||||||
|
""" JSON endpoint
|
||||||
|
|
||||||
|
Provides fetching of free identifiers for e.g. AJAX calls
|
||||||
|
|
||||||
|
"""
|
||||||
|
tmp_intervention = Intervention()
|
||||||
|
identifier = tmp_intervention.generate_new_identifier()
|
||||||
|
while Intervention.objects.filter(identifier=identifier).exists():
|
||||||
|
identifier = tmp_intervention.generate_new_identifier()
|
||||||
|
return JsonResponse(
|
||||||
|
data={
|
||||||
|
"gen_data": identifier
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
)
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
|
|
||||||
class InterventionIdentifierGeneratorView(AbstractIdentifierGeneratorView):
|
@login_required
|
||||||
_MODEL = Intervention
|
@any_group_check
|
||||||
|
@uuid_required
|
||||||
|
def detail_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a detail view for viewing an intervention's data
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
template = "intervention/detail/view.html"
|
||||||
|
|
||||||
|
# Fetch data, filter out deleted related data
|
||||||
|
intervention = get_object_or_404(
|
||||||
|
Intervention.objects.select_related(
|
||||||
|
"geometry",
|
||||||
|
"legal",
|
||||||
|
"responsible",
|
||||||
|
).prefetch_related(
|
||||||
|
"legal__revocations",
|
||||||
|
),
|
||||||
|
id=id,
|
||||||
|
deleted=None
|
||||||
|
)
|
||||||
|
compensations = intervention.compensations.filter(
|
||||||
|
deleted=None,
|
||||||
|
)
|
||||||
|
_user = request.user
|
||||||
|
is_data_shared = intervention.is_shared_with(user=_user)
|
||||||
|
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=intervention,
|
||||||
|
)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
has_payment_without_document = intervention.payments.exists() and not intervention.get_documents()[1].exists()
|
||||||
|
|
||||||
|
requesting_user_is_only_shared_user = intervention.is_only_shared_with(_user)
|
||||||
|
if requesting_user_is_only_shared_user:
|
||||||
|
messages.info(
|
||||||
|
request,
|
||||||
|
DO_NOT_FORGET_TO_SHARE
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"obj": intervention,
|
||||||
|
"last_checked": last_checked,
|
||||||
|
"last_checked_tooltip": last_checked_tooltip,
|
||||||
|
"compensations": compensations,
|
||||||
|
"is_entry_shared": is_data_shared,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"is_default_member": _user.in_group(DEFAULT_GROUP),
|
||||||
|
"is_zb_member": _user.in_group(ZB_GROUP),
|
||||||
|
"is_ets_member": _user.in_group(ETS_GROUP),
|
||||||
|
"LANIS_LINK": intervention.get_LANIS_link(),
|
||||||
|
"has_payment_without_document": has_payment_without_document,
|
||||||
|
TAB_TITLE_IDENTIFIER: f"{intervention.identifier} - {intervention.title}",
|
||||||
|
}
|
||||||
|
|
||||||
|
request = intervention.set_status_messages(request)
|
||||||
|
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
class EditInterventionView(LoginRequiredMixin, View):
|
@login_required
|
||||||
_TEMPLATE = "intervention/form/view.html"
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def edit_view(request: HttpRequest, id: str):
|
||||||
|
"""
|
||||||
|
Renders a view for editing interventions
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
Args:
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
request (HttpRequest): The incoming request
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
Renders a view for editing interventions
|
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The intervention identifier
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
HttpResponse: The rendered view
|
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)
|
||||||
|
|
||||||
# Get object from db
|
# Create forms, initialize with values from db/from POST request
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
data_form = EditInterventionForm(request.POST or None, instance=intervention)
|
||||||
if intervention.is_recorded:
|
geom_form = SimpleGeomForm(request.POST or None, read_only=False, instance=intervention)
|
||||||
messages.info(
|
if request.method == "POST":
|
||||||
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)
|
|
||||||
context = {
|
|
||||||
"form": data_form,
|
|
||||||
"geom_form": geom_form,
|
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, self._TEMPLATE, context)
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
Process saved form content
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The intervention id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
HttpResponse:
|
|
||||||
"""
|
|
||||||
# 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)
|
|
||||||
if data_form.is_valid() and geom_form.is_valid():
|
if data_form.is_valid() and geom_form.is_valid():
|
||||||
# The data form takes the geom form for processing, as well as the performing user
|
# The data form takes the geom form for processing, as well as the performing user
|
||||||
# Save the current state of recorded|checked to inform the user in case of a status reset due to editing
|
# Save the current state of recorded|checked to inform the user in case of a status reset due to editing
|
||||||
@@ -212,22 +236,45 @@ class EditInterventionView(LoginRequiredMixin, View):
|
|||||||
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
|
messages.success(request, _("Intervention {} edited").format(intervention.identifier))
|
||||||
if intervention_is_checked:
|
if intervention_is_checked:
|
||||||
messages.info(request, CHECK_STATE_RESET)
|
messages.info(request, CHECK_STATE_RESET)
|
||||||
if geom_form.has_geometry_simplified():
|
if geom_form.geometry_simplified:
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
request,
|
||||||
GEOMETRY_SIMPLIFIED
|
GEOMETRY_SIMPLIFIED
|
||||||
)
|
)
|
||||||
num_ignored_geometries = geom_form.get_num_geometries_ignored()
|
|
||||||
if num_ignored_geometries > 0:
|
|
||||||
messages.info(
|
|
||||||
request,
|
|
||||||
GEOMETRIES_IGNORED_TEMPLATE.format(num_ignored_geometries)
|
|
||||||
)
|
|
||||||
return redirect("intervention:detail", id=intervention.id)
|
return redirect("intervention:detail", id=intervention.id)
|
||||||
context = {
|
else:
|
||||||
"form": data_form,
|
messages.error(request, FORM_INVALID, extra_tags="danger",)
|
||||||
"geom_form": geom_form,
|
else:
|
||||||
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
|
# For clarification: nothing in this case
|
||||||
}
|
pass
|
||||||
context = BaseContext(request, context).context
|
context = {
|
||||||
return render(request, self._TEMPLATE, context)
|
"form": data_form,
|
||||||
|
"geom_form": geom_form,
|
||||||
|
TAB_TITLE_IDENTIFIER: _("Edit {}").format(intervention.identifier),
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required_modal
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def remove_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders a remove view for this intervention
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The uuid id as string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
obj = Intervention.objects.get(id=id)
|
||||||
|
identifier = obj.identifier
|
||||||
|
form = RemoveModalForm(request.POST or None, instance=obj, request=request)
|
||||||
|
return form.process_request(
|
||||||
|
request,
|
||||||
|
_("{} removed").format(identifier),
|
||||||
|
redirect_url=reverse("intervention:index")
|
||||||
|
)
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
|
|
||||||
from intervention.models import Intervention
|
|
||||||
from konova.decorators import shared_access_required
|
|
||||||
from konova.views.remove import AbstractRemoveView
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveInterventionView(AbstractRemoveView):
|
|
||||||
_MODEL = Intervention
|
|
||||||
_REDIRECT_URL = "intervention:index"
|
|
||||||
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
||||||
return super().get(request, *args, **kwargs)
|
|
||||||
@@ -5,78 +5,72 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from intervention.models import Intervention
|
from intervention.models import Intervention
|
||||||
from konova.contexts import BaseContext
|
from konova.contexts import BaseContext
|
||||||
|
from konova.decorators import uuid_required
|
||||||
from konova.forms import SimpleGeomForm
|
from konova.forms import SimpleGeomForm
|
||||||
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER
|
||||||
from konova.utils.qrcode import QrCode
|
from konova.utils.generators import generate_qr_code
|
||||||
from konova.views.report import AbstractPublicReportView
|
|
||||||
|
|
||||||
|
|
||||||
class InterventionPublicReportView(AbstractPublicReportView):
|
@uuid_required
|
||||||
_TEMPLATE = "intervention/report/report.html"
|
def report_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders the public report view
|
||||||
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
Args:
|
||||||
""" Renders the public report view
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The id of the intervention
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The id of the intervention
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
template = "intervention/report/report.html"
|
||||||
"""
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
|
||||||
|
|
||||||
tab_title = _("Report {}").format(intervention.identifier)
|
|
||||||
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
|
||||||
if not intervention.is_ready_for_publish():
|
|
||||||
template = "report/unavailable.html"
|
|
||||||
context = {
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
|
||||||
}
|
|
||||||
context = BaseContext(request, context).context
|
|
||||||
return render(request, template, context)
|
|
||||||
|
|
||||||
# Prepare data for map viewer
|
|
||||||
geom_form = SimpleGeomForm(
|
|
||||||
instance=intervention
|
|
||||||
)
|
|
||||||
parcels = intervention.get_underlying_parcels()
|
|
||||||
|
|
||||||
distinct_deductions = intervention.deductions.all().distinct(
|
|
||||||
"account"
|
|
||||||
)
|
|
||||||
|
|
||||||
qrcode = QrCode(
|
|
||||||
content=request.build_absolute_uri(reverse("intervention:report", args=(id,))),
|
|
||||||
size=10
|
|
||||||
)
|
|
||||||
qrcode_lanis = QrCode(
|
|
||||||
content=intervention.get_LANIS_link(),
|
|
||||||
size=7
|
|
||||||
)
|
|
||||||
|
|
||||||
|
tab_title = _("Report {}").format(intervention.identifier)
|
||||||
|
# If intervention is not recorded (yet or currently) we need to render another template without any data
|
||||||
|
if not intervention.is_ready_for_publish():
|
||||||
|
template = "report/unavailable.html"
|
||||||
context = {
|
context = {
|
||||||
"obj": intervention,
|
|
||||||
"deductions": distinct_deductions,
|
|
||||||
"qrcode": {
|
|
||||||
"img": qrcode.get_img(),
|
|
||||||
"url": qrcode.get_content(),
|
|
||||||
},
|
|
||||||
"qrcode_lanis": {
|
|
||||||
"img": qrcode_lanis.get_img(),
|
|
||||||
"url": qrcode_lanis.get_content(),
|
|
||||||
},
|
|
||||||
"geom_form": geom_form,
|
|
||||||
"parcels": parcels,
|
|
||||||
"tables_scrollable": False,
|
|
||||||
TAB_TITLE_IDENTIFIER: tab_title,
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
}
|
}
|
||||||
context = BaseContext(request, context).context
|
context = BaseContext(request, context).context
|
||||||
return render(request, self._TEMPLATE, context)
|
return render(request, template, context)
|
||||||
|
|
||||||
|
# Prepare data for map viewer
|
||||||
|
geom_form = SimpleGeomForm(
|
||||||
|
instance=intervention
|
||||||
|
)
|
||||||
|
parcels = intervention.get_underlying_parcels()
|
||||||
|
|
||||||
|
distinct_deductions = intervention.deductions.all().distinct(
|
||||||
|
"account"
|
||||||
|
)
|
||||||
|
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": {
|
||||||
|
"img": qrcode_img,
|
||||||
|
"url": qrcode_url,
|
||||||
|
},
|
||||||
|
"qrcode_lanis": {
|
||||||
|
"img": qrcode_img_lanis,
|
||||||
|
"url": qrcode_lanis_url,
|
||||||
|
},
|
||||||
|
"geom_form": geom_form,
|
||||||
|
"parcels": parcels,
|
||||||
|
"tables_scrollable": False,
|
||||||
|
TAB_TITLE_IDENTIFIER: tab_title,
|
||||||
|
}
|
||||||
|
context = BaseContext(request, context).context
|
||||||
|
return render(request, template, context)
|
||||||
@@ -6,12 +6,10 @@ Created on: 19.08.22
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
|
from intervention.forms.modals.revocation import NewRevocationModalForm, EditRevocationModalForm, \
|
||||||
RemoveRevocationModalForm
|
RemoveRevocationModalForm
|
||||||
@@ -21,125 +19,100 @@ from konova.utils.documents import get_document
|
|||||||
from konova.utils.message_templates import REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED
|
from konova.utils.message_templates import REVOCATION_ADDED, DATA_UNSHARED, REVOCATION_EDITED, REVOCATION_REMOVED
|
||||||
|
|
||||||
|
|
||||||
class NewInterventionRevocationView(LoginRequiredMixin, View):
|
@login_required
|
||||||
def __process_request(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
@default_group_required
|
||||||
""" Renders sharing form for an intervention
|
@shared_access_required(Intervention, "id")
|
||||||
|
def new_revocation_view(request: HttpRequest, id: str):
|
||||||
|
""" Renders sharing form for an intervention
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): Intervention's id
|
id (str): Intervention's id
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
|
form = NewRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, request=request)
|
||||||
request=request)
|
return form.process_request(
|
||||||
return form.process_request(
|
request,
|
||||||
|
msg_success=REVOCATION_ADDED,
|
||||||
|
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@default_group_required
|
||||||
|
def get_revocation_view(request: HttpRequest, doc_id: str):
|
||||||
|
""" Returns the revocation document as downloadable file
|
||||||
|
|
||||||
|
Wraps the generic document fetcher function from konova.utils.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
doc_id (str): The document id
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
doc = get_object_or_404(RevocationDocument, id=doc_id)
|
||||||
|
# File download only possible if related instance is shared with user
|
||||||
|
if not doc.instance.legal.intervention.users.filter(id=request.user.id):
|
||||||
|
messages.info(
|
||||||
request,
|
request,
|
||||||
msg_success=REVOCATION_ADDED,
|
DATA_UNSHARED
|
||||||
redirect_url=reverse("intervention:detail", args=(id,)) + "#related_data"
|
|
||||||
)
|
)
|
||||||
|
return redirect("intervention:detail", id=doc.instance.id)
|
||||||
@method_decorator(default_group_required)
|
return get_document(doc)
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, *args, **kwargs)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class GetInterventionRevocationView(LoginRequiredMixin, View):
|
@login_required
|
||||||
@method_decorator(default_group_required)
|
@default_group_required
|
||||||
def get(self, request: HttpRequest, doc_id: str, *args, **kwargs) -> HttpResponse:
|
@shared_access_required(Intervention, "id")
|
||||||
""" Returns the revocation document as downloadable file
|
def edit_revocation_view(request: HttpRequest, id: str, revocation_id: str):
|
||||||
|
""" Renders a edit view for a revocation
|
||||||
|
|
||||||
Wraps the generic document fetcher function from konova.utils.
|
Args:
|
||||||
|
request (HttpRequest): The incoming request
|
||||||
|
id (str): The intervention's id as string
|
||||||
|
revocation_id (str): The revocation's id as string
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
doc_id (str): The document id
|
|
||||||
|
|
||||||
Returns:
|
"""
|
||||||
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
|
revocation = get_object_or_404(Revocation, id=revocation_id)
|
||||||
|
|
||||||
"""
|
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention, revocation=revocation, request=request)
|
||||||
doc = get_object_or_404(RevocationDocument, id=doc_id)
|
return form.process_request(
|
||||||
# File download only possible if related instance is shared with user
|
request,
|
||||||
if not doc.instance.legal.intervention.users.filter(id=request.user.id):
|
REVOCATION_EDITED,
|
||||||
messages.info(
|
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
||||||
request,
|
)
|
||||||
DATA_UNSHARED
|
|
||||||
)
|
|
||||||
return redirect("intervention:detail", id=doc.instance.id)
|
|
||||||
return get_document(doc)
|
|
||||||
|
|
||||||
|
|
||||||
class EditInterventionRevocationView(LoginRequiredMixin, View):
|
@login_required_modal
|
||||||
def __process_request(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
@login_required
|
||||||
""" Renders a edit view for a revocation
|
@default_group_required
|
||||||
|
@shared_access_required(Intervention, "id")
|
||||||
|
def remove_revocation_view(request: HttpRequest, id: str, revocation_id: str):
|
||||||
|
""" Renders a remove view for a revocation
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (HttpRequest): The incoming request
|
request (HttpRequest): The incoming request
|
||||||
id (str): The intervention's id as string
|
id (str): The intervention's id as string
|
||||||
revocation_id (str): The revocation's id as string
|
revocation_id (str): The revocation's id as string
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
intervention = get_object_or_404(Intervention, id=id)
|
||||||
revocation = get_object_or_404(Revocation, id=revocation_id)
|
revocation = get_object_or_404(Revocation, id=revocation_id)
|
||||||
|
|
||||||
form = EditRevocationModalForm(request.POST or None, request.FILES or None, instance=intervention,
|
form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation, request=request)
|
||||||
revocation=revocation, request=request)
|
return form.process_request(
|
||||||
return form.process_request(
|
request,
|
||||||
request,
|
REVOCATION_REMOVED,
|
||||||
REVOCATION_EDITED,
|
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
||||||
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveInterventionRevocationView(LoginRequiredMixin, View):
|
|
||||||
def __process_request(self, request, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
""" Renders a remove view for a revocation
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request (HttpRequest): The incoming request
|
|
||||||
id (str): The intervention's id as string
|
|
||||||
revocation_id (str): The revocation's id as string
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
intervention = get_object_or_404(Intervention, id=id)
|
|
||||||
revocation = get_object_or_404(Revocation, id=revocation_id)
|
|
||||||
|
|
||||||
form = RemoveRevocationModalForm(request.POST or None, instance=intervention, revocation=revocation,
|
|
||||||
request=request)
|
|
||||||
return form.process_request(
|
|
||||||
request,
|
|
||||||
REVOCATION_REMOVED,
|
|
||||||
redirect_url=reverse("intervention:detail", args=(intervention.id,)) + "#related_data"
|
|
||||||
)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def get(self, request: HttpRequest, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
|
||||||
|
|
||||||
@method_decorator(default_group_required)
|
|
||||||
@method_decorator(shared_access_required(Intervention, "id"))
|
|
||||||
def post(self, request, id: str, revocation_id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
return self.__process_request(request, id, revocation_id, *args, **kwargs)
|
|
||||||
@@ -11,7 +11,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from bootstrap_modal_forms.mixins import is_ajax
|
from bootstrap_modal_forms.mixins import is_ajax
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import BadRequest
|
from django.http import Http404
|
||||||
from django.shortcuts import redirect, get_object_or_404, render
|
from django.shortcuts import redirect, get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -185,7 +185,7 @@ def uuid_required(function):
|
|||||||
try:
|
try:
|
||||||
uuid = UUID(uuid)
|
uuid = UUID(uuid)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise BadRequest(
|
raise Http404(
|
||||||
"Invalid UUID"
|
"Invalid UUID"
|
||||||
)
|
)
|
||||||
return function(request, *args, **kwargs)
|
return function(request, *args, **kwargs)
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class SimpleGeomForm(BaseForm):
|
|||||||
""" A geometry form for rendering geometry read-only using a widget
|
""" A geometry form for rendering geometry read-only using a widget
|
||||||
|
|
||||||
"""
|
"""
|
||||||
read_only: bool = True
|
read_only = True
|
||||||
_geometry_simplified: bool = False
|
geometry_simplified = False
|
||||||
output = JSONField(
|
output = JSONField(
|
||||||
label=_("Geometry"),
|
label=_("Geometry"),
|
||||||
help_text=_(""),
|
help_text=_(""),
|
||||||
@@ -34,8 +34,6 @@ class SimpleGeomForm(BaseForm):
|
|||||||
required=False,
|
required=False,
|
||||||
disabled=False,
|
disabled=False,
|
||||||
)
|
)
|
||||||
_num_geometries_ignored: int = 0
|
|
||||||
empty = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.read_only = kwargs.pop("read_only", True)
|
self.read_only = kwargs.pop("read_only", True)
|
||||||
@@ -50,11 +48,11 @@ class SimpleGeomForm(BaseForm):
|
|||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
geojson = self.instance.geometry.as_feature_collection(srid=DEFAULT_SRID_RLP)
|
||||||
geojson = self._set_geojson_properties(geojson, title=self.instance.identifier or None)
|
geojson = self._set_properties(geojson, self.instance.identifier)
|
||||||
geom = json.dumps(geojson)
|
geom = json.dumps(geojson)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
# If no geometry exists for this form, we simply set the value to None and zoom to the maximum level
|
||||||
geom = json.dumps({})
|
geom = ""
|
||||||
self.empty = True
|
self.empty = True
|
||||||
|
|
||||||
self.initialize_form_field("output", geom)
|
self.initialize_form_field("output", geom)
|
||||||
@@ -63,20 +61,21 @@ class SimpleGeomForm(BaseForm):
|
|||||||
super().is_valid()
|
super().is_valid()
|
||||||
is_valid = True
|
is_valid = True
|
||||||
|
|
||||||
# Make sure invalid geometry is properly rendered again to the user
|
# Get geojson from form
|
||||||
# Therefore: write submitted data back into form field
|
geom = self.data.get("output", None)
|
||||||
# (does not matter whether we know if it is valid or invalid)
|
if geom is None or len(geom) == 0:
|
||||||
submitted_data = self.data["output"]
|
# empty geometry is a valid geometry
|
||||||
submitted_data = json.loads(submitted_data)
|
self.cleaned_data["output"] = MultiPolygon(srid=DEFAULT_SRID_RLP).ewkt
|
||||||
submitted_data = self._set_geojson_properties(submitted_data)
|
return is_valid
|
||||||
self.initialize_form_field("output", json.dumps(submitted_data))
|
|
||||||
|
|
||||||
# Get geojson from form for validity checking
|
|
||||||
geom = self.data.get("output", json.dumps({}))
|
|
||||||
geom = json.loads(geom)
|
geom = json.loads(geom)
|
||||||
|
|
||||||
# Initialize features list with empty MultiPolygon, so that an empty input will result in a
|
# Write submitted data back into form field to make sure invalid geometry
|
||||||
# proper empty MultiPolygon object
|
# will be rendered again on failed submit
|
||||||
|
self.initialize_form_field("output", self.data.get("output", None))
|
||||||
|
|
||||||
|
# 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 = []
|
||||||
features_json = geom.get("features", [])
|
features_json = geom.get("features", [])
|
||||||
accepted_ogr_types = [
|
accepted_ogr_types = [
|
||||||
@@ -85,61 +84,44 @@ class SimpleGeomForm(BaseForm):
|
|||||||
"MultiPolygon",
|
"MultiPolygon",
|
||||||
"MultiPolygon25D",
|
"MultiPolygon25D",
|
||||||
]
|
]
|
||||||
# Check validity for each feature of the geometry
|
|
||||||
for feature in features_json:
|
for feature in features_json:
|
||||||
feature_geom = feature.get("geometry", feature)
|
feature_geom = feature.get("geometry", feature)
|
||||||
if feature_geom is None:
|
if feature_geom is None:
|
||||||
# Fallback for rare cases where a feature does not contain any geometry
|
# Fallback for rare cases where a feature does not contain any geometry
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Try to create a geometry object from the single feature
|
|
||||||
feature_geom = json.dumps(feature_geom)
|
feature_geom = json.dumps(feature_geom)
|
||||||
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
|
g = gdal.OGRGeometry(feature_geom, srs=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
geometry_has_unwanted_dimensions = g.coord_dim > 2
|
flatten_geometry = g.coord_dim > 2
|
||||||
if geometry_has_unwanted_dimensions:
|
if flatten_geometry:
|
||||||
g = self.__flatten_geom_to_2D(g)
|
g = self.__flatten_geom_to_2D(g)
|
||||||
|
|
||||||
geometry_type_is_accepted = g.geom_type not in accepted_ogr_types
|
if g.geom_type not in accepted_ogr_types:
|
||||||
if geometry_type_is_accepted:
|
|
||||||
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
|
self.add_error("output", _("Only surfaces allowed. Points or lines must be buffered."))
|
||||||
is_valid &= False
|
is_valid &= False
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
is_area_valid = self.__is_area_valid(g)
|
is_valid &= self.__is_area_valid(g)
|
||||||
if not is_area_valid:
|
|
||||||
# Geometries with an invalid size will not be saved to the db
|
|
||||||
# We assume these are malicious snippets which are not supposed to be in the geometry in the first place
|
|
||||||
self._num_geometries_ignored += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Whatever this geometry object is -> try to create a Polygon from it
|
polygon = Polygon.from_ewkt(g.ewkt)
|
||||||
# The resulting polygon object automatically detects whether a valid polygon has been created or not
|
is_valid &= polygon.valid
|
||||||
g = Polygon.from_ewkt(g.ewkt)
|
if not polygon.valid:
|
||||||
is_valid &= g.valid
|
self.add_error("output", polygon.valid_reason)
|
||||||
if not g.valid:
|
|
||||||
self.add_error("output", g.valid_reason)
|
|
||||||
return is_valid
|
return is_valid
|
||||||
|
|
||||||
# If the resulting polygon is just a single polygon, we add it to the list of properly casted features
|
features.append(polygon)
|
||||||
if isinstance(g, Polygon):
|
|
||||||
features.append(g)
|
|
||||||
elif isinstance(g, MultiPolygon):
|
|
||||||
# The resulting polygon could be of type MultiPolygon (due to multiple surfaces)
|
|
||||||
# If so, we extract all polygons from the MultiPolygon and extend the casted features list
|
|
||||||
features.extend(list(g))
|
|
||||||
|
|
||||||
# Unionize all polygon features into one new MultiPolygon
|
# Unionize all geometry features into one new MultiPolygon
|
||||||
if features:
|
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
||||||
form_geom = MultiPolygon(*features, srid=DEFAULT_SRID_RLP).unary_union
|
for feature in features:
|
||||||
else:
|
form_geom = form_geom.union(feature)
|
||||||
# If no features have been processed, this indicates an empty geometry - so we store an empty geometry
|
|
||||||
form_geom = MultiPolygon(srid=DEFAULT_SRID_RLP)
|
|
||||||
|
|
||||||
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
# Make sure to convert into a MultiPolygon. Relevant if a single Polygon is provided.
|
||||||
form_geom = Geometry.cast_to_multipolygon(form_geom)
|
if form_geom.geom_type != "MultiPolygon":
|
||||||
|
form_geom = MultiPolygon(form_geom, srid=DEFAULT_SRID_RLP)
|
||||||
|
|
||||||
# Write unionized Multipolygon back into cleaned data
|
# Write unioned Multipolygon into cleaned data
|
||||||
if self.cleaned_data is None:
|
if self.cleaned_data is None:
|
||||||
self.cleaned_data = {}
|
self.cleaned_data = {}
|
||||||
self.cleaned_data["output"] = form_geom.ewkt
|
self.cleaned_data["output"] = form_geom.ewkt
|
||||||
@@ -165,6 +147,15 @@ class SimpleGeomForm(BaseForm):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
is_area_valid = geom.area > 1 # > 1m² (SRID:25832)
|
is_area_valid = geom.area > 1 # > 1m² (SRID:25832)
|
||||||
|
|
||||||
|
if not is_area_valid:
|
||||||
|
self.add_error(
|
||||||
|
"output",
|
||||||
|
_("Geometry must be greater than 1m². Currently is {}m²").format(
|
||||||
|
float(geom.area)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return is_area_valid
|
return is_area_valid
|
||||||
|
|
||||||
def __simplify_geometry(self, geom, max_vert: int):
|
def __simplify_geometry(self, geom, max_vert: int):
|
||||||
@@ -217,29 +208,13 @@ class SimpleGeomForm(BaseForm):
|
|||||||
if not is_vertices_num_valid:
|
if not is_vertices_num_valid:
|
||||||
geometry.geom = self.__simplify_geometry(geometry.geom, max_vert=GEOM_MAX_VERTICES)
|
geometry.geom = self.__simplify_geometry(geometry.geom, max_vert=GEOM_MAX_VERTICES)
|
||||||
geometry.save()
|
geometry.save()
|
||||||
self._geometry_simplified = True
|
self.geometry_simplified = True
|
||||||
|
|
||||||
# Start parcel update and geometry conflict checking procedure in a background process
|
# Start parcel update and geometry conflict checking procedure in a background process
|
||||||
celery_update_parcels.delay(geometry.id)
|
celery_update_parcels.delay(geometry.id)
|
||||||
celery_check_for_geometry_conflicts.delay(geometry.id)
|
celery_check_for_geometry_conflicts.delay(geometry.id)
|
||||||
return geometry
|
return geometry
|
||||||
|
|
||||||
def get_num_geometries_ignored(self):
|
|
||||||
""" Returns the number of geometries which had to be ignored for various reasons
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._num_geometries_ignored
|
|
||||||
|
|
||||||
def has_geometry_simplified(self):
|
|
||||||
""" Returns whether the geometry has been simplified or not.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._geometry_simplified
|
|
||||||
|
|
||||||
def __flatten_geom_to_2D(self, geom):
|
def __flatten_geom_to_2D(self, geom):
|
||||||
"""
|
"""
|
||||||
Enforces a given OGRGeometry from higher dimensions into 2D
|
Enforces a given OGRGeometry from higher dimensions into 2D
|
||||||
@@ -250,20 +225,17 @@ class SimpleGeomForm(BaseForm):
|
|||||||
geom = gdal.OGRGeometry(g_wkt)
|
geom = gdal.OGRGeometry(g_wkt)
|
||||||
return geom
|
return geom
|
||||||
|
|
||||||
def _set_geojson_properties(self, geojson: dict, title: str = None):
|
def _set_properties(self, geojson: dict, title: str):
|
||||||
""" Toggles the editable property of the geojson for proper handling in map client
|
""" Toggles the editable property of the geojson for proper handling in map client
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
geojson (dict): The GeoJson
|
geojson (dict): The GeoJson
|
||||||
title (str): An alternative title for the geometry
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
geojson (dict): The altered GeoJson
|
geojson (dict): The altered GeoJson
|
||||||
"""
|
"""
|
||||||
features = geojson.get("features", [])
|
features = geojson.get("features", [])
|
||||||
for feature in features:
|
for feature in features:
|
||||||
if not feature.get("properties", None):
|
|
||||||
feature["properties"] = {}
|
|
||||||
feature["properties"]["editable"] = not self.read_only
|
feature["properties"]["editable"] = not self.read_only
|
||||||
if title:
|
if title:
|
||||||
feature["properties"]["title"] = title
|
feature["properties"]["title"] = title
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ class Command(BaseKonovaCommand):
|
|||||||
def recalculate_parcels(self, options: dict):
|
def recalculate_parcels(self, options: dict):
|
||||||
force_all = options.get("force_all", False)
|
force_all = options.get("force_all", False)
|
||||||
|
|
||||||
geometry_objects = Geometry.objects.filter(
|
geometry_objects = Geometry.objects.all().exclude(
|
||||||
geom__isempty=False,
|
|
||||||
).exclude(
|
|
||||||
geom=None
|
geom=None
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,14 +48,11 @@ class Command(BaseKonovaCommand):
|
|||||||
"geometry__id",
|
"geometry__id",
|
||||||
flat=True
|
flat=True
|
||||||
)
|
)
|
||||||
# ... and resolve into Geometry objects again ...
|
|
||||||
intersected_geom_objs = Geometry.objects.filter(
|
intersected_geom_objs = Geometry.objects.filter(
|
||||||
id__in=geom_with_intersection_ids
|
id__in=geom_with_intersection_ids
|
||||||
)
|
)
|
||||||
# ... to be able to use the way more efficient difference() function ...
|
# Filter those geometries out (they have intersections and do not need to be processed)
|
||||||
geometry_objects_ids = geometry_objects.difference(intersected_geom_objs).values_list("id", flat=True)
|
geometry_objects = geometry_objects.difference(intersected_geom_objs)
|
||||||
# ... so we can resolve these into proper Geometry objects again for further annotation usage
|
|
||||||
geometry_objects = Geometry.objects.filter(id__in=geometry_objects_ids)
|
|
||||||
|
|
||||||
self._write_warning("=== Update parcels and districts ===")
|
self._write_warning("=== Update parcels and districts ===")
|
||||||
# Order geometries by size to process smaller once at first
|
# Order geometries by size to process smaller once at first
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 19.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
|
from intervention.models import Intervention
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Performs test on collisions using the identifier generation"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
identifiers = {}
|
||||||
|
max_iterations = 100000
|
||||||
|
try:
|
||||||
|
collisions = 0
|
||||||
|
len_ids = len(identifiers)
|
||||||
|
while len_ids < max_iterations:
|
||||||
|
tmp_intervention = Intervention()
|
||||||
|
_id = tmp_intervention.generate_new_identifier()
|
||||||
|
len_ids = len(identifiers)
|
||||||
|
if _id not in identifiers:
|
||||||
|
if len_ids % (max_iterations/5) == 0:
|
||||||
|
print(len_ids)
|
||||||
|
identifiers[_id] = None
|
||||||
|
else:
|
||||||
|
collisions += 1
|
||||||
|
print("+++ Collision after {} identifiers +++".format(len_ids))
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._break_line()
|
||||||
|
exit(-1)
|
||||||
|
print(
|
||||||
|
"\n{} collisions in {} identifiers; Collision rate {}%".format(
|
||||||
|
collisions,
|
||||||
|
len_ids,
|
||||||
|
(collisions / len_ids)*100,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _break_line(self):
|
||||||
|
""" Simply prints a line break
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.stdout.write("\n")
|
||||||
+9
-107
@@ -10,9 +10,7 @@ import json
|
|||||||
from django.contrib.gis.db.models import MultiPolygonField
|
from django.contrib.gis.db.models import MultiPolygonField
|
||||||
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.gis.geos import MultiPolygon
|
|
||||||
|
|
||||||
from konova.models import BaseResource, UuidModel
|
from konova.models import BaseResource, UuidModel
|
||||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
|
||||||
@@ -103,66 +101,33 @@ class Geometry(BaseResource):
|
|||||||
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
|
resolved_conflicts = all_conflicted_by_conflicts.exclude(id__in=still_conflicting_conflicts)
|
||||||
resolved_conflicts.delete()
|
resolved_conflicts.delete()
|
||||||
|
|
||||||
def get_data_objects(self, limit_to_attrs: list = None):
|
def get_data_objects(self):
|
||||||
""" Getter for all objects which are related to this geometry
|
""" Getter for all objects which are related to this geometry
|
||||||
|
|
||||||
Using the limit_to_attrs we can limit the amount of returned data directly onto the data object attributes
|
|
||||||
we want to have. Reduces memory consumption and runtime.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
objs (list): The list of objects
|
objs (list): The list of objects
|
||||||
"""
|
"""
|
||||||
objs = []
|
objs = []
|
||||||
stop_searching = False
|
sets = [
|
||||||
# Some related data sets can be processed rather easily
|
|
||||||
regular_sets = [
|
|
||||||
self.intervention_set,
|
self.intervention_set,
|
||||||
|
self.compensation_set,
|
||||||
self.ema_set,
|
self.ema_set,
|
||||||
self.ecoaccount_set,
|
self.ecoaccount_set,
|
||||||
]
|
]
|
||||||
for _set in regular_sets:
|
for _set in sets:
|
||||||
set_objs = _set.filter(
|
set_objs = _set.filter(
|
||||||
deleted=None
|
deleted=None
|
||||||
)
|
)
|
||||||
if limit_to_attrs:
|
objs += set_objs
|
||||||
objs += set_objs.values(*limit_to_attrs)
|
|
||||||
else:
|
|
||||||
objs += set_objs
|
|
||||||
|
|
||||||
stop_searching = len(objs) > 0
|
|
||||||
if stop_searching:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not stop_searching:
|
|
||||||
# ... but we need a special treatment for compensations, since they can be deleted directly OR inherit their
|
|
||||||
# de-facto-deleted status from their deleted parent intervention
|
|
||||||
comp_objs = self.compensation_set.filter(
|
|
||||||
Q(deleted=None) | Q(intervention__deleted=None)
|
|
||||||
)
|
|
||||||
if limit_to_attrs:
|
|
||||||
objs += comp_objs.values(*limit_to_attrs)
|
|
||||||
else:
|
|
||||||
objs += comp_objs
|
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
def get_data_object(self, limit_to_attrs: list = None):
|
def get_data_object(self):
|
||||||
"""
|
"""
|
||||||
Getter for the specific data object which is related to this geometry.
|
Getter for the specific data object which is related to this geometry
|
||||||
|
|
||||||
!!! Only returns undeleted entries !!!
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
result (str|None): Returns the desired attributes or None if the data object is marked as deleted
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
objs = self.get_data_objects(limit_to_attrs)
|
objs = self.get_data_objects()
|
||||||
assert (len(objs) <= 1)
|
assert (len(objs) <= 1)
|
||||||
try:
|
result = objs.pop()
|
||||||
result = objs.pop()
|
|
||||||
except IndexError:
|
|
||||||
# If this happens, we just processed a GeometryConflict with an entry which is marked as deleted.
|
|
||||||
# Therefore we return None
|
|
||||||
result = None
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def update_parcels(self):
|
def update_parcels(self):
|
||||||
@@ -419,49 +384,6 @@ class Geometry(BaseResource):
|
|||||||
|
|
||||||
return complexity_factor
|
return complexity_factor
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cast_to_multipolygon(input_geom):
|
|
||||||
""" If input_geom is not a MultiPolygon, cast to MultiPolygon
|
|
||||||
|
|
||||||
Args:
|
|
||||||
input_geom ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
output_geom
|
|
||||||
"""
|
|
||||||
output_geom = input_geom
|
|
||||||
if not isinstance(input_geom, MultiPolygon):
|
|
||||||
try:
|
|
||||||
output_geom = MultiPolygon(input_geom, srid=DEFAULT_SRID_RLP)
|
|
||||||
except TypeError as e:
|
|
||||||
raise AssertionError(f"Only (Multi)Polygon allowed! Could not convert {input_geom.geom_type} to MultiPolygon")
|
|
||||||
return output_geom
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def cast_to_rlp_srid(input_geom):
|
|
||||||
""" If input_geom is not of RLP SRID (25832), cast to RLP SRID
|
|
||||||
|
|
||||||
Args:
|
|
||||||
input_geom ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
output_geom
|
|
||||||
"""
|
|
||||||
output_geom = input_geom
|
|
||||||
if output_geom.srid != DEFAULT_SRID_RLP:
|
|
||||||
output_geom.transform(DEFAULT_SRID_RLP)
|
|
||||||
return output_geom
|
|
||||||
|
|
||||||
def get_conflict_geometries(self):
|
|
||||||
""" Getter for geometry ids which conflict with this geometry or are conflicted by this one
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
geom_ids (list): List of geometry ids
|
|
||||||
"""
|
|
||||||
conflict_geoms_id = GeometryConflict.get_conflict_geometries(self)
|
|
||||||
conflict_geoms = Geometry.objects.filter(id__in=conflict_geoms_id)
|
|
||||||
return conflict_geoms
|
|
||||||
|
|
||||||
|
|
||||||
class GeometryConflict(UuidModel):
|
class GeometryConflict(UuidModel):
|
||||||
"""
|
"""
|
||||||
@@ -485,23 +407,3 @@ class GeometryConflict(UuidModel):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.conflicting_geometry.id} conflicts with {self.affected_geometry.id}"
|
return f"{self.conflicting_geometry.id} conflicts with {self.affected_geometry.id}"
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_conflict_geometries(geometry: Geometry):
|
|
||||||
""" Getter for geometries which conflict in one or another way with the given one
|
|
||||||
|
|
||||||
Args:
|
|
||||||
geometry (Geometry): The geometry which shall be checked
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
conflict_geometries (QuerySet): QuerySet of geometries which have conflicts with the given geometry
|
|
||||||
"""
|
|
||||||
conflict_geometries = GeometryConflict.objects.filter(
|
|
||||||
affected_geometry=geometry.id,
|
|
||||||
).values_list("conflicting_geometry__id", flat=True)
|
|
||||||
conflict_geometries = conflict_geometries.union(
|
|
||||||
GeometryConflict.objects.filter(
|
|
||||||
conflicting_geometry=geometry.id,
|
|
||||||
).values_list("affected_geometry__id", flat=True)
|
|
||||||
)
|
|
||||||
return conflict_geometries
|
|
||||||
+12
-9
@@ -676,17 +676,20 @@ class GeoReferencedMixin(models.Model):
|
|||||||
if self.geometry is None:
|
if self.geometry is None:
|
||||||
return request
|
return request
|
||||||
|
|
||||||
conflicting_geometries = self.geometry.get_conflict_geometries()
|
instance_objs = []
|
||||||
data_object_identifiers = []
|
conflicts = self.geometry.conflicts_geometries.all()
|
||||||
for conflicting_geom in conflicting_geometries:
|
|
||||||
data_obj_id = conflicting_geom.get_data_object(["identifier"])
|
|
||||||
if data_obj_id:
|
|
||||||
data_object_identifiers.append(data_obj_id)
|
|
||||||
|
|
||||||
add_message = len(data_object_identifiers) > 0
|
for conflict in conflicts:
|
||||||
|
instance_objs += conflict.affected_geometry.get_data_objects()
|
||||||
|
|
||||||
|
conflicts = self.geometry.conflicted_by_geometries.all()
|
||||||
|
for conflict in conflicts:
|
||||||
|
instance_objs += conflict.conflicting_geometry.get_data_objects()
|
||||||
|
|
||||||
|
add_message = len(instance_objs) > 0
|
||||||
if add_message:
|
if add_message:
|
||||||
data_object_identifiers = [x["identifier"] for x in data_object_identifiers]
|
instance_identifiers = [x.identifier for x in instance_objs]
|
||||||
instance_identifiers = ", ".join(data_object_identifiers)
|
instance_identifiers = ", ".join(instance_identifiers)
|
||||||
message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
|
message_str = GEOMETRY_CONFLICT_WITH_TEMPLATE.format(instance_identifiers)
|
||||||
messages.info(request, message_str)
|
messages.info(request, message_str)
|
||||||
return request
|
return request
|
||||||
|
|||||||
@@ -278,18 +278,4 @@ Similar to bootstraps 'shadow-lg'
|
|||||||
}
|
}
|
||||||
.alert{
|
.alert{
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Overwrites netgis.css attributes
|
|
||||||
*/
|
|
||||||
.netgis-gradient-a{
|
|
||||||
/*
|
|
||||||
Overwrites gradient used on default css of netgis map client
|
|
||||||
*/
|
|
||||||
background: var(--rlp-red) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.netgis-menu{
|
|
||||||
z-index: 1 !important;
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
@@ -11,4 +11,4 @@ BASE_TITLE = "KSP - Kompensationsverzeichnis Service Portal"
|
|||||||
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
BASE_FRONTEND_TITLE = "Kompensationsverzeichnis Service Portal"
|
||||||
TAB_TITLE_IDENTIFIER = "tab_title"
|
TAB_TITLE_IDENTIFIER = "tab_title"
|
||||||
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
|
HELP_LINK = "https://dienste.naturschutz.rlp.de/doku/doku.php?id=ksp2:start"
|
||||||
IMPRESSUM_LINK = "https://naturschutz.rlp.de/ueber-uns/impressum"
|
IMPRESSUM_LINK = "https://naturschutz.rlp.de/index.php?q=impressum"
|
||||||
@@ -66,6 +66,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django.contrib.gis',
|
'django.contrib.gis',
|
||||||
'django.contrib.humanize',
|
'django.contrib.humanize',
|
||||||
|
'simple_sso.sso_server',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
'bootstrap_modal_forms',
|
'bootstrap_modal_forms',
|
||||||
'fontawesome_5',
|
'fontawesome_5',
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 31.01.22
|
Created on: 31.01.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from konova.sub_settings.django_settings import env
|
|
||||||
|
|
||||||
# MAPS
|
# MAPS
|
||||||
DEFAULT_LAT = 50.00
|
DEFAULT_LAT = 50.00
|
||||||
@@ -29,6 +28,3 @@ LANIS_ZOOM_LUT = {
|
|||||||
1000: 30,
|
1000: 30,
|
||||||
500: 31,
|
500: 31,
|
||||||
}
|
}
|
||||||
|
|
||||||
MAP_PROXY_HOST_WHITELIST = env.list("MAP_PROXY_HOST_WHITELIST")
|
|
||||||
i = 0
|
|
||||||
@@ -16,5 +16,3 @@ OAUTH_CODE_VERIFIER = env("OAUTH_CODE_VERIFIER")
|
|||||||
|
|
||||||
OAUTH_CLIENT_ID = env("OAUTH_CLIENT_ID")
|
OAUTH_CLIENT_ID = env("OAUTH_CLIENT_ID")
|
||||||
OAUTH_CLIENT_SECRET = env("OAUTH_CLIENT_SECRET")
|
OAUTH_CLIENT_SECRET = env("OAUTH_CLIENT_SECRET")
|
||||||
|
|
||||||
PROPAGATION_SECRET = env("PROPAGATION_SECRET")
|
|
||||||
@@ -85,6 +85,12 @@ class GeometryTestCase(BaseTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_get_data_objects(self):
|
def test_get_data_objects(self):
|
||||||
|
num_objs_with_geom = 0
|
||||||
|
self.assertEqual(
|
||||||
|
len(self.geom_1.get_data_objects()),
|
||||||
|
num_objs_with_geom
|
||||||
|
)
|
||||||
|
|
||||||
objs = [
|
objs = [
|
||||||
self.intervention,
|
self.intervention,
|
||||||
self.compensation,
|
self.compensation,
|
||||||
@@ -92,13 +98,16 @@ class GeometryTestCase(BaseTestCase):
|
|||||||
self.ema,
|
self.ema,
|
||||||
]
|
]
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if not obj.geometry:
|
obj.geometry = self.geom_1
|
||||||
obj.geometry = Geometry.objects.create(geom=self.create_dummy_geometry())
|
obj.save()
|
||||||
obj.save()
|
|
||||||
|
|
||||||
data_objs = obj.geometry.get_data_objects()
|
num_objs_with_geom += 1
|
||||||
self.assertEqual(len(data_objs), 1)
|
geom_objs = self.geom_1.get_data_objects()
|
||||||
self.assertIn(obj, data_objs)
|
self.assertEqual(
|
||||||
|
len(geom_objs),
|
||||||
|
num_objs_with_geom
|
||||||
|
)
|
||||||
|
self.assertIn(obj, geom_objs)
|
||||||
|
|
||||||
def test_as_feature_collection(self):
|
def test_as_feature_collection(self):
|
||||||
geometry = self.geom_1.geom
|
geometry = self.geom_1.geom
|
||||||
@@ -115,7 +124,7 @@ class GeometryTestCase(BaseTestCase):
|
|||||||
{
|
{
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
"geometry": json.loads(p.json),
|
"geometry": json.loads(p.json),
|
||||||
"properties": {}
|
"properties": {},
|
||||||
}
|
}
|
||||||
for p in polygons
|
for p in polygons
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ class BaseTestCase(TestCase):
|
|||||||
{
|
{
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
"geometry": json.loads(geometry.geojson),
|
"geometry": json.loads(geometry.geojson),
|
||||||
|
"properties": {},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -469,7 +470,7 @@ class BaseTestCase(TestCase):
|
|||||||
eco_account.save()
|
eco_account.save()
|
||||||
return eco_account
|
return eco_account
|
||||||
|
|
||||||
def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon, tolerance=0.001):
|
def assert_equal_geometries(self, geom1: MultiPolygon, geom2: MultiPolygon, tolerance = 0.001):
|
||||||
""" Assert for geometries to be equal
|
""" Assert for geometries to be equal
|
||||||
|
|
||||||
Transforms the geometries to matching srids before checking
|
Transforms the geometries to matching srids before checking
|
||||||
@@ -491,10 +492,7 @@ class BaseTestCase(TestCase):
|
|||||||
# transformation from one coordinate system into the other, which is valid
|
# transformation from one coordinate system into the other, which is valid
|
||||||
geom1.transform(geom2.srid)
|
geom1.transform(geom2.srid)
|
||||||
geom2.transform(geom1.srid)
|
geom2.transform(geom1.srid)
|
||||||
self.assertTrue(
|
self.assertTrue(geom1.equals_exact(geom2, tolerance) or geom2.equals_exact(geom1, tolerance))
|
||||||
geom1.equals_exact(geom2, tolerance=tolerance),
|
|
||||||
msg=f"Difference is {abs(geom1.area - geom2.area)} with {geom1.area} and {geom2.area} in a tolerance of {tolerance}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseViewTestCase(BaseTestCase):
|
class BaseViewTestCase(BaseTestCase):
|
||||||
|
|||||||
@@ -42,6 +42,5 @@ urlpatterns = [
|
|||||||
path('client/proxy/wfs', ClientProxyParcelWFS.as_view(), name="client-proxy-wfs"),
|
path('client/proxy/wfs', ClientProxyParcelWFS.as_view(), name="client-proxy-wfs"),
|
||||||
]
|
]
|
||||||
|
|
||||||
handler400 = "konova.views.error.get_400_view"
|
|
||||||
handler404 = "konova.views.error.get_404_view"
|
handler404 = "konova.views.error.get_404_view"
|
||||||
handler500 = "konova.views.error.get_500_view"
|
handler500 = "konova.views.error.get_500_view"
|
||||||
@@ -5,9 +5,6 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 11.12.23
|
Created on: 11.12.23
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
from json import JSONDecodeError
|
|
||||||
|
|
||||||
from django.views.debug import ExceptionReporter
|
from django.views.debug import ExceptionReporter
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +30,7 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"""
|
"""
|
||||||
whitelist = [
|
whitelist = [
|
||||||
"is_email",
|
"is_email",
|
||||||
"unicode_hint",
|
"unicdoe_hint",
|
||||||
"frames",
|
"frames",
|
||||||
"request",
|
"request",
|
||||||
"user_str",
|
"user_str",
|
||||||
@@ -42,8 +39,6 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"raising_view_name",
|
"raising_view_name",
|
||||||
"exception_type",
|
"exception_type",
|
||||||
"exception_value",
|
"exception_value",
|
||||||
"filtered_GET_items",
|
|
||||||
"filtered_POST_items",
|
|
||||||
]
|
]
|
||||||
clean_data = dict()
|
clean_data = dict()
|
||||||
for entry in whitelist:
|
for entry in whitelist:
|
||||||
@@ -61,28 +56,7 @@ class KonovaExceptionReporter(ExceptionReporter):
|
|||||||
"""
|
"""
|
||||||
tb_data = super().get_traceback_data()
|
tb_data = super().get_traceback_data()
|
||||||
|
|
||||||
return_data = tb_data
|
|
||||||
if self.is_email:
|
if self.is_email:
|
||||||
filtered_data = dict()
|
tb_data = self._filter_traceback_data(tb_data)
|
||||||
filtered_data.update(self._filter_traceback_data(tb_data))
|
|
||||||
filtered_data.update(self._filter_POST_body(tb_data))
|
|
||||||
return_data = filtered_data
|
|
||||||
return return_data
|
|
||||||
|
|
||||||
def _filter_POST_body(self, tb_data: dict):
|
return tb_data
|
||||||
""" Filters POST body from traceback data
|
|
||||||
|
|
||||||
"""
|
|
||||||
post_data = tb_data.get("request", None)
|
|
||||||
if post_data:
|
|
||||||
post_data = post_data.body
|
|
||||||
try:
|
|
||||||
post_data = json.loads(post_data)
|
|
||||||
except JSONDecodeError:
|
|
||||||
pass
|
|
||||||
post_data = {
|
|
||||||
"filtered_POST_items": [
|
|
||||||
("body", post_data),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return post_data
|
|
||||||
+25
-22
@@ -5,18 +5,22 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 09.11.20
|
Created on: 09.11.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import secrets
|
import random
|
||||||
import string
|
import string
|
||||||
|
import qrcode
|
||||||
|
import qrcode.image.svg
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
def generate_token(length: int = 64) -> str:
|
def generate_token() -> str:
|
||||||
""" Shortcut for default generating of e.g. API token
|
""" Shortcut for default generating of e.g. API token
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
token (str)
|
token (str)
|
||||||
"""
|
"""
|
||||||
return generate_random_string(
|
return generate_random_string(
|
||||||
length=length,
|
length=64,
|
||||||
use_numbers=True,
|
use_numbers=True,
|
||||||
use_letters_lc=True
|
use_letters_lc=True
|
||||||
)
|
)
|
||||||
@@ -35,27 +39,26 @@ def generate_random_string(length: int, use_numbers: bool = False, use_letters_l
|
|||||||
elements.append(string.ascii_uppercase)
|
elements.append(string.ascii_uppercase)
|
||||||
|
|
||||||
elements = "".join(elements)
|
elements = "".join(elements)
|
||||||
ret_val = "".join(secrets.choice(elements) for i in range(length))
|
ret_val = "".join(random.choice(elements) for i in range(length))
|
||||||
return ret_val
|
return ret_val
|
||||||
|
|
||||||
class IdentifierGenerator:
|
|
||||||
_MODEL = None
|
|
||||||
|
|
||||||
def __init__(self, model):
|
def generate_qr_code(content: str, size: int = 20) -> str:
|
||||||
from konova.models import BaseObject
|
""" Generates a qr code from given content
|
||||||
if not issubclass(model, BaseObject):
|
|
||||||
raise AssertionError("Model must be a subclass of BaseObject!")
|
|
||||||
|
|
||||||
self._MODEL = model
|
Args:
|
||||||
|
content (str): The content for the qr code
|
||||||
|
size (int): The image size
|
||||||
|
|
||||||
def generate_id(self) -> str:
|
Returns:
|
||||||
""" Generates a unique identifier
|
qrcode_svg (str): The qr code as svg
|
||||||
|
"""
|
||||||
Returns:
|
qrcode_factory = qrcode.image.svg.SvgImage
|
||||||
|
qrcode_img = qrcode.make(
|
||||||
"""
|
content,
|
||||||
unpersisted_object = self._MODEL()
|
image_factory=qrcode_factory,
|
||||||
identifier = unpersisted_object.generate_new_identifier()
|
box_size=size
|
||||||
while self._MODEL.objects.filter(identifier=identifier).exists():
|
)
|
||||||
identifier = unpersisted_object.generate_new_identifier()
|
stream = BytesIO()
|
||||||
return identifier
|
qrcode_img.save(stream)
|
||||||
|
return stream.getvalue().decode()
|
||||||
+8
-21
@@ -5,7 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de
|
|||||||
Created on: 09.11.20
|
Created on: 09.11.20
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.core.mail import send_mail, EmailMultiAlternatives
|
from django.core.mail import send_mail
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@@ -45,19 +45,6 @@ class Mailer:
|
|||||||
auth_password=self.auth_password
|
auth_password=self.auth_password
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_via_bcc(self, recipient_list: list, subject: str, msg: str):
|
|
||||||
"""
|
|
||||||
Sends a mail with subject and message where recipients will be masked via bcc
|
|
||||||
"""
|
|
||||||
email_obj = EmailMultiAlternatives(
|
|
||||||
subject,
|
|
||||||
msg,
|
|
||||||
self.from_mail,
|
|
||||||
bcc=recipient_list,
|
|
||||||
)
|
|
||||||
email_obj.attach_alternative(msg, "text/html")
|
|
||||||
return email_obj.send(fail_silently=self.fail_silently)
|
|
||||||
|
|
||||||
def send_mail_shared_access_removed(self, obj, user, municipals_names):
|
def send_mail_shared_access_removed(self, obj, user, municipals_names):
|
||||||
""" Send a mail if user has no access to the object anymore
|
""" Send a mail if user has no access to the object anymore
|
||||||
|
|
||||||
@@ -128,7 +115,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/sharing/shared_access_given_team.html", context)
|
msg = render_to_string("email/sharing/shared_access_given_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send_via_bcc(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared access given").format(obj.identifier),
|
_("{} - Shared access given").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -154,7 +141,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/sharing/shared_access_removed_team.html", context)
|
msg = render_to_string("email/sharing/shared_access_removed_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send_via_bcc(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared access removed").format(obj.identifier),
|
_("{} - Shared access removed").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -180,7 +167,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/recording/shared_data_unrecorded_team.html", context)
|
msg = render_to_string("email/recording/shared_data_unrecorded_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send_via_bcc(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data unrecorded").format(obj.identifier),
|
_("{} - Shared data unrecorded").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -206,7 +193,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/recording/shared_data_recorded_team.html", context)
|
msg = render_to_string("email/recording/shared_data_recorded_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send_via_bcc(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data recorded").format(obj.identifier),
|
_("{} - Shared data recorded").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -232,7 +219,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/checking/shared_data_checked_team.html", context)
|
msg = render_to_string("email/checking/shared_data_checked_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send_via_bcc(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data checked").format(obj.identifier),
|
_("{} - Shared data checked").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -257,7 +244,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/other/deduction_changed_team.html", context)
|
msg = render_to_string("email/other/deduction_changed_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send_via_bcc(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Deduction changed").format(obj.identifier),
|
_("{} - Deduction changed").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
@@ -283,7 +270,7 @@ class Mailer:
|
|||||||
}
|
}
|
||||||
msg = render_to_string("email/deleting/shared_data_deleted_team.html", context)
|
msg = render_to_string("email/deleting/shared_data_deleted_team.html", context)
|
||||||
user_mail_address = users_to_notify.values_list("email", flat=True)
|
user_mail_address = users_to_notify.values_list("email", flat=True)
|
||||||
self.send_via_bcc(
|
self.send(
|
||||||
user_mail_address,
|
user_mail_address,
|
||||||
_("{} - Shared data deleted").format(obj.identifier),
|
_("{} - Shared data deleted").format(obj.identifier),
|
||||||
msg
|
msg
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ EDITED_GENERAL_DATA = _("Edited general data")
|
|||||||
# Geometry
|
# Geometry
|
||||||
GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
|
GEOMETRY_CONFLICT_WITH_TEMPLATE = _("Geometry conflict detected with {}")
|
||||||
GEOMETRY_SIMPLIFIED = _("The geometry contained more than {} vertices. It had to be simplified to match the allowed limit of {} vertices.").format(GEOM_MAX_VERTICES, GEOM_MAX_VERTICES)
|
GEOMETRY_SIMPLIFIED = _("The geometry contained more than {} vertices. It had to be simplified to match the allowed limit of {} vertices.").format(GEOM_MAX_VERTICES, GEOM_MAX_VERTICES)
|
||||||
GEOMETRIES_IGNORED_TEMPLATE = _("The geometry contained {} parts which have been detected as invalid (e.g. too small to be valid). These parts have been removed. Please check the stored geometry.")
|
|
||||||
|
|
||||||
# INTERVENTION
|
# INTERVENTION
|
||||||
INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations")
|
INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations")
|
||||||
@@ -92,6 +91,3 @@ INTERVENTION_HAS_REVOCATIONS_TEMPLATE = _("This intervention has {} revocations"
|
|||||||
DATA_CHECKED_ON_TEMPLATE = _("Checked on {} by {}")
|
DATA_CHECKED_ON_TEMPLATE = _("Checked on {} by {}")
|
||||||
DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by {}")
|
DATA_CHECKED_PREVIOUSLY_TEMPLATE = _("Data has changed since last check on {} by {}")
|
||||||
DATA_IS_UNCHECKED = _("Current data not checked yet")
|
DATA_IS_UNCHECKED = _("Current data not checked yet")
|
||||||
|
|
||||||
# API TOKEN SETTINGS
|
|
||||||
NEW_API_TOKEN_GENERATED = _("New token generated. Administrators need to validate.")
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"""
|
||||||
|
Author: Michel Peltriaux
|
||||||
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
||||||
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
||||||
|
Created on: 17.08.21
|
||||||
|
|
||||||
|
"""
|
||||||
|
from collections import Iterable
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from user.models import User
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from konova.settings import SSO_SERVER_BASE, SSO_PUBLIC_KEY, PROXIES
|
||||||
|
from konova.sub_settings.context_settings import BASE_TITLE_SHORT
|
||||||
|
|
||||||
|
|
||||||
|
class Messenger:
|
||||||
|
""" Used to send messages to the SSO server.
|
||||||
|
|
||||||
|
Messages can be seen by the user the next time they login on their SSO dashboard.
|
||||||
|
Documentation for SSO Server-Client communication can be found here:
|
||||||
|
https://git.naturschutz.rlp.de/SGD-Nord/arnova/wiki/Messages
|
||||||
|
|
||||||
|
"""
|
||||||
|
server_url = "{}communication/message/".format(SSO_SERVER_BASE)
|
||||||
|
|
||||||
|
def __init__(self, users: Iterable, subject: str = None, body: str = None, type: str = None):
|
||||||
|
self.users = users
|
||||||
|
self.msg_subject = subject
|
||||||
|
self.msg_body = body
|
||||||
|
self.msg_type = type
|
||||||
|
|
||||||
|
def send(self):
|
||||||
|
""" Sends a message
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self.msg_body is None or len(self.msg_body) == 0:
|
||||||
|
raise AttributeError("No message body set")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"x-services-public-key": SSO_PUBLIC_KEY
|
||||||
|
}
|
||||||
|
for user in self.users:
|
||||||
|
data = {
|
||||||
|
"type": self.msg_type,
|
||||||
|
"sender": BASE_TITLE_SHORT,
|
||||||
|
"receiver": user.username,
|
||||||
|
"subject": self.msg_subject,
|
||||||
|
"body": self.msg_body,
|
||||||
|
}
|
||||||
|
requests.post(
|
||||||
|
self.server_url,
|
||||||
|
data=data,
|
||||||
|
headers=headers,
|
||||||
|
proxies=PROXIES
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_object_checked(self, obj_identifier: str, performing_user: User, detail_view_url: str = ""):
|
||||||
|
""" Wraps sending of a message related to the checking of an object, like an intervention
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj_identifier (str): The object's identifier (e.g. 'EIV-123'
|
||||||
|
performing_user (User): The user who performed the checking
|
||||||
|
detail_view_url (str): If a direct link to the object shall be added to the message, it can be provided here
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.msg_subject = _("{} checked").format(obj_identifier)
|
||||||
|
if len(detail_view_url) > 0:
|
||||||
|
detail_view_url = _('<a href="{}">Check it out</a>').format(detail_view_url)
|
||||||
|
self.msg_body = _("{} has been checked successfully by user {}! {}").format(
|
||||||
|
obj_identifier,
|
||||||
|
performing_user.username,
|
||||||
|
detail_view_url
|
||||||
|
)
|
||||||
|
self.send()
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import qrcode
|
|
||||||
import qrcode.image.svg as svg
|
|
||||||
|
|
||||||
|
|
||||||
class QrCode:
|
|
||||||
""" A wrapping class for creating a qr code with content
|
|
||||||
|
|
||||||
"""
|
|
||||||
_content = None
|
|
||||||
_img = None
|
|
||||||
|
|
||||||
def __init__(self, content: str, size: int):
|
|
||||||
self._content = content
|
|
||||||
self._img = self._generate_qr_code(content, size)
|
|
||||||
|
|
||||||
def _generate_qr_code(self, content: str, size: int = 20) -> str:
|
|
||||||
""" Generates a qr code from given content
|
|
||||||
|
|
||||||
Args:
|
|
||||||
content (str): The content for the qr code
|
|
||||||
size (int): The image size
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
qrcode_svg (str): The qr code as svg
|
|
||||||
"""
|
|
||||||
qr = qrcode.QRCode(
|
|
||||||
image_factory=qrcode.image.svg.SvgPathImage,
|
|
||||||
box_size=size
|
|
||||||
)
|
|
||||||
qr.add_data(content)
|
|
||||||
qr.make(
|
|
||||||
fit=True
|
|
||||||
)
|
|
||||||
|
|
||||||
img = qr.make_image()
|
|
||||||
return img.to_string(encoding="unicode")
|
|
||||||
|
|
||||||
def get_img(self):
|
|
||||||
return self._img
|
|
||||||
|
|
||||||
def get_content(self):
|
|
||||||
return self._content
|
|
||||||
@@ -11,7 +11,6 @@ from json import JSONDecodeError
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from konova.sub_settings import schneider_settings
|
from konova.sub_settings import schneider_settings
|
||||||
from konova.sub_settings.lanis_settings import DEFAULT_SRID
|
|
||||||
from konova.sub_settings.proxy_settings import PROXIES
|
from konova.sub_settings.proxy_settings import PROXIES
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ class ParcelFetcher:
|
|||||||
if geom.area < buffer_threshold:
|
if geom.area < buffer_threshold:
|
||||||
# Fallback for malicious geometries which are way too small and would disappear on negative buffering
|
# Fallback for malicious geometries which are way too small and would disappear on negative buffering
|
||||||
geom = geometry.geom
|
geom = geometry.geom
|
||||||
geom.transform(DEFAULT_SRID)
|
|
||||||
self.geojson = geom.ewkt
|
self.geojson = geom.ewkt
|
||||||
self.results = []
|
self.results = []
|
||||||
|
|
||||||
|
|||||||
@@ -178,9 +178,7 @@ class TableRenderMixin:
|
|||||||
if len(value) > max_length:
|
if len(value) > max_length:
|
||||||
value = f"{value[:max_length]}..."
|
value = f"{value[:max_length]}..."
|
||||||
value = format_html(
|
value = format_html(
|
||||||
'<div title="{}">{}</div>',
|
f'<div title="{value_orig}">{value}</div>'
|
||||||
value_orig,
|
|
||||||
value
|
|
||||||
)
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -224,7 +222,7 @@ class TableRenderMixin:
|
|||||||
tooltip=_("Full access granted") if is_entry_shared else _("Access not granted"),
|
tooltip=_("Full access granted") if is_entry_shared else _("Access not granted"),
|
||||||
icn_class="fas fa-edit rlp-r-inv" if is_entry_shared else "far fa-edit",
|
icn_class="fas fa-edit rlp-r-inv" if is_entry_shared else "far fa-edit",
|
||||||
)
|
)
|
||||||
return format_html(html, None)
|
return format_html(html)
|
||||||
|
|
||||||
|
|
||||||
class TableOrderMixin:
|
class TableOrderMixin:
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
"""
|
|
||||||
Author: Michel Peltriaux
|
|
||||||
Created on: 14.12.25
|
|
||||||
|
|
||||||
"""
|
|
||||||
from abc import ABC
|
|
||||||
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
|
||||||
|
|
||||||
from konova.decorators import uuid_required, any_group_check
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractDetailView(LoginRequiredMixin, View, ABC):
|
|
||||||
_TEMPLATE = None
|
|
||||||
|
|
||||||
@method_decorator(uuid_required)
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
@method_decorator(any_group_check)
|
|
||||||
def get(self, request: HttpRequest, id: str, *args, **kwargs) -> HttpResponse:
|
|
||||||
raise NotImplementedError()
|
|
||||||
@@ -25,20 +25,6 @@ def get_404_view(request: HttpRequest, exception=None):
|
|||||||
return render(request, "404.html", context, status=404)
|
return render(request, "404.html", context, status=404)
|
||||||
|
|
||||||
|
|
||||||
def get_400_view(request: HttpRequest, exception=None):
|
|
||||||
""" Returns a 400 handling view
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request ():
|
|
||||||
exception ():
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
|
|
||||||
"""
|
|
||||||
context = BaseContext.context
|
|
||||||
return render(request, "400.html", context, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
def get_500_view(request: HttpRequest):
|
def get_500_view(request: HttpRequest):
|
||||||
""" Returns a 404 handling view
|
""" Returns a 404 handling view
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Contact: ksp-servicestelle@sgdnord.rlp.de
|
|||||||
Created on: 19.08.22
|
Created on: 19.08.22
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.gis.geos import MultiPolygon
|
from django.contrib.gis.geos import MultiPolygon
|
||||||
from django.http import HttpResponse, HttpRequest
|
from django.http import HttpResponse, HttpRequest
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ class HomeView(LoginRequiredMixin, View):
|
|||||||
# Repeat for other objects
|
# Repeat for other objects
|
||||||
comps = Compensation.objects.filter(
|
comps = Compensation.objects.filter(
|
||||||
deleted=None,
|
deleted=None,
|
||||||
intervention__deleted=None,
|
|
||||||
)
|
)
|
||||||
user_comps = comps.filter(
|
user_comps = comps.filter(
|
||||||
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams)
|
Q(intervention__users__in=[user]) | Q(intervention__teams__in=user_teams)
|
||||||
|
|||||||
Loaded 100 of 131 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user