Compare commits
793 Commits
1.16.0
...
6a3df990c4
Author | SHA1 | Date | |
---|---|---|---|
6a3df990c4 | |||
893b368776 | |||
dea1daed98 | |||
92760a6a0c | |||
c743ddf127 | |||
29959759da | |||
52ab68c4fb | |||
1639d06ff4 | |||
b8a1e73400 | |||
3b738b7897 | |||
5c6ba7412f | |||
bc19a9b6f9 | |||
3f7838ac26 | |||
e2cbf7eadd | |||
580ea43cad | |||
86d0b47a7b | |||
52a7475c95 | |||
423cd7d52b | |||
a0107899b9 | |||
cc19d56549 | |||
9e44405248 | |||
5b0cd33dfc | |||
c26355f7f4 | |||
46af371540 | |||
dc146bbf9f | |||
bd48a96f35 | |||
c2cb179e50 | |||
b55c41be7f | |||
8d4692efd7 | |||
48068632f5 | |||
ecdb6fd18d | |||
5522e7f6ed | |||
dca87f51e0 | |||
43f53a5ad8 | |||
7a69076e5d | |||
e6d3f8d476 | |||
3307febf14 | |||
0b186616ae | |||
d94e4c0f77 | |||
2b10183f5c | |||
14d5198058 | |||
69e75043a6 | |||
8aacbe158b | |||
019d6240a9 | |||
a36329201d | |||
9cf8d32f43 | |||
ef5f913830 | |||
43e89aa501 | |||
4c837be6a6 | |||
7637c8c583 | |||
df5d23c79b | |||
9a65e67f33 | |||
ef24e41b44 | |||
f71fcd0381 | |||
e778d5b2a0 | |||
96a9a16326 | |||
838553577e | |||
f8a00a3d5c | |||
177f4863d3 | |||
c1707e84f7 | |||
092bf8f621 | |||
d71e9a692c | |||
eb036e77b8 | |||
0ab6cda35f | |||
368ef0dc4e | |||
a57512f2d0 | |||
8f6ee7732a | |||
c5af5a46c9 | |||
016f3d70d4 | |||
ce73f9a87a | |||
a921d110da | |||
0aa1e49505 | |||
67cba6f65f | |||
f8c18e24a7 | |||
581f49206e | |||
484bbafe99 | |||
cc65b85145 | |||
9f61264c5d | |||
4ad40f8b57 | |||
7cdcdb7d58 | |||
c062fe7c5f | |||
12f2dae154 | |||
3b7fda19ca | |||
908dc8fed9 | |||
9b02834662 | |||
cdf1c0f12f | |||
6319d35948 | |||
5a2dd9c4f6 | |||
67fa2a9ee7 | |||
42579611fc | |||
174a457923 | |||
72f3aa53a2 | |||
f959190a9a | |||
8fa5f7d5c0 | |||
61b17811c4 | |||
a1a758b785 | |||
2a8108dbc8 | |||
9445159106 | |||
3c13232b63 | |||
88ddc95725 | |||
e67b154cae | |||
741101fe1c | |||
d220b25eb5 | |||
8aee5c59cc | |||
b1a0d1178a | |||
75f97c3ef4 | |||
48eb0a8206 | |||
77b1cc8a1e | |||
e91b6aeceb | |||
25c69540ec | |||
df3d18281d | |||
c5ba8603b6 | |||
288ae09c1b | |||
cbf935d06c | |||
c2934dae8b | |||
fcf6c4db03 | |||
0a20adcdc8 | |||
7c871737cd | |||
8578d80fbd | |||
e3e639682b | |||
5714862c7a | |||
7890af7984 | |||
bf0f4c4d15 | |||
efd60f994d | |||
2020552814 | |||
24e17948bd | |||
4e728df6c2 | |||
988b990e23 | |||
141923460a | |||
550d4d9a43 | |||
f31598b33f | |||
506ce7ecd5 | |||
0aad84f296 | |||
a9209ab5d2 | |||
cf83ec0886 | |||
1f578a0a6b | |||
f0049d1639 | |||
0e2f1309ff | |||
511a9b6cb0 | |||
63d257dcf8 | |||
c7bd251bc0 | |||
3b56f4cee2 | |||
919bc274c8 | |||
19e864b8e2 | |||
fb8427c253 | |||
2335493709 | |||
b68b49080c | |||
6b80e86520 | |||
12f7e0575a | |||
c1ce204e12 | |||
e67aedc06c | |||
02a49c14b8 | |||
1f9061199c | |||
00b47e9d70 | |||
5aacecedcf | |||
316c86f54e | |||
5dd76258ee | |||
d3039da85b | |||
f79f934a0e | |||
efa3eeed5d | |||
9f938a9a7e | |||
5d7ab6a740 | |||
13884953cd | |||
0b9ff0c779 | |||
a0452ae2a6 | |||
9cf42cbcbd | |||
6c59d77267 | |||
5c6c2587a6 | |||
c15ef67813 | |||
2b4ebb31b3 | |||
9c3541922f | |||
cc3de16032 | |||
febcf7a128 | |||
aabcf1bb23 | |||
91fd05c3f9 | |||
aa6a215926 | |||
68de6c0de2 | |||
09caddccc8 | |||
cfa2f81b0d | |||
d2b95eacda | |||
ec7bb51803 | |||
59c590614c | |||
af84ae44ca | |||
f9f33d9582 | |||
152da48e15 | |||
2d2c3092a6 | |||
ea2b642570 | |||
f75f2eae4d | |||
425ec4373a | |||
bc9c39ce95 | |||
854d6a0e37 | |||
e328ead070 | |||
7872b0b8f0 | |||
a75cd5a52e | |||
237778100b | |||
4fbad8bcbd | |||
52bbf59ff5 | |||
8088a827f4 | |||
5a5989d5e2 | |||
1c0baa6cad | |||
39aa135c24 | |||
6856c54b42 | |||
2ca99002c2 | |||
e0c6b66448 | |||
3049c22dc0 | |||
8eb9b6f6c4 | |||
4f3f9e9f4c | |||
cdf7b668e0 | |||
55dad89412 | |||
db359a5267 | |||
c29fdf1fc9 | |||
bd77f211ac | |||
4498ecf13f | |||
1290dc81f2 | |||
fc14718bf2 | |||
b462bccc0b | |||
06ca4971d0 | |||
7816fe0309 | |||
48b3982edc | |||
106d77c396 | |||
79790dcda3 | |||
13fece3478 | |||
d004413a6e | |||
dcff288283 | |||
69a6d720b0 | |||
3a31e56a80 | |||
dd99267eac | |||
6e70b255be | |||
83d8913757 | |||
3c10f6adda | |||
94f4463b7a | |||
aca49d58a7 | |||
69555f3526 | |||
a8494def17 | |||
efda724e03 | |||
7099f61112 | |||
78326692a7 | |||
5d5d4b3217 | |||
36ef739e02 | |||
d30f0416fe | |||
54ed000e34 | |||
f7f4f10815 | |||
b2de0747fd | |||
df5bac48b4 | |||
f4fa3a4da7 | |||
b7a89ebd51 | |||
d62deb7034 | |||
b30a513d72 | |||
08707ee235 | |||
c547e1fa36 | |||
e8b7b707d0 | |||
d68087ff02 | |||
e1b450b537 | |||
50c9a70e8f | |||
1772d69a1e | |||
fa36806233 | |||
e782f1bdac | |||
1ecdfb9918 | |||
dd4a0e631e | |||
e4d2826b91 | |||
2ed4fe0b9a | |||
28a24a387e | |||
d9815a4ba1 | |||
3acf962b08 | |||
bdb370f499 | |||
05be8df1a3 | |||
35d2d3672c | |||
b60e39869f | |||
ab36f37eca | |||
e80d305dfa | |||
d3671519f8 | |||
2c22da6f58 | |||
34402b46eb | |||
e545aa4cb5 | |||
35829cbeff | |||
2c8ae8214d | |||
16840a1212 | |||
be61435f67 | |||
6fa37d70de | |||
89c483d89f | |||
b962004f07 | |||
1a7146f42c | |||
d8f58f9841 | |||
0e7d7bf5ff | |||
7af52a3cda | |||
e4aa07ac58 | |||
643a5f9307 | |||
964bbc6b0c | |||
e5e8820cab | |||
13e7f3047a | |||
95b043eadc | |||
7ac8a66072 | |||
6bc3d9fa7c | |||
339f39c2a1 | |||
811b3063b6 | |||
516c4876ce | |||
1a06172247 | |||
51fcc18f61 | |||
55f9c2cf17 | |||
4ec2844bd5 | |||
a1b0aa3726 | |||
616f0b35f9 | |||
a0c52bf149 | |||
4888d64a29 | |||
7348c1a9aa | |||
1e8c37e4a2 | |||
1d840adaa7 | |||
407c295ead | |||
357a3ce53f | |||
b8a4834e7b | |||
b20d8e35f0 | |||
5328c72c37 | |||
c244a26494 | |||
ae980e4b1f | |||
2e7666c3c0 | |||
ab3aec59ae | |||
e564f92166 | |||
6a82301e37 | |||
0c549310a5 | |||
86899b666b | |||
0cd65ee907 | |||
dda3c458d2 | |||
f2b7263013 | |||
36ab4ad212 | |||
da969330db | |||
7d8c71abcc | |||
3539d4368e | |||
ab948187b2 | |||
0a3a94c2d8 | |||
1bd3922253 | |||
7f93f624e2 | |||
9c43b62880 | |||
8f7b3769c5 | |||
129bdf3c52 | |||
60fa41ab77 | |||
6ea6a9190a | |||
e68c5e2cef | |||
b6d777380d | |||
51203ae257 | |||
06c5600a09 | |||
38b1334f28 | |||
e26d653a6d | |||
950f9dd748 | |||
a0f1368281 | |||
5cf899175a | |||
b1155ee310 | |||
ee7996e1d4 | |||
4caaa73d2b | |||
cbde1f4e42 | |||
cee1f2bec1 | |||
bd0880744f | |||
0be181c34e | |||
9a83bf0fb4 | |||
8f70c1ac7b | |||
68f79e5c90 | |||
500b96dc79 | |||
48c0b950cc | |||
90991d403c | |||
d7c1f571c6 | |||
cf093ac708 | |||
d91011d83d | |||
a1873aef9c | |||
da1c691394 | |||
a9071d624d | |||
0b2567677a | |||
087cd08a86 | |||
f8575302e0 | |||
2e72d2c595 | |||
aad95797a8 | |||
afbdf8309c | |||
9addfc3abc | |||
d786b713f6 | |||
4302a913e4 | |||
8298c18ad0 | |||
05fa018de8 | |||
da7638c196 | |||
a1c8624c22 | |||
5df9f35b87 | |||
5aee3d495e | |||
0dd4f90b79 | |||
9f58eb60e5 | |||
d193f6661e | |||
9fe0324118 | |||
420f8eedc0 | |||
56e94c814a | |||
03d0d2565c | |||
aa9ac74886 | |||
d55f322bd7 | |||
3819839893 | |||
1fe0bbdf33 | |||
27b48f7f30 | |||
513bc6223b | |||
978e5d3e56 | |||
69e8402f89 | |||
a5df8f0ff8 | |||
808e9b25ad | |||
9b5acf65e8 | |||
fc7620aa63 | |||
be465fcac6 | |||
5d18ed991b | |||
738e6a9322 | |||
f7fcbb38d8 | |||
794da69567 | |||
89bb082b61 | |||
7e4f705e1e | |||
bc7f50b958 | |||
8a4879f1f2 | |||
2787e7b67c | |||
455e2e4139 | |||
cad76581ad | |||
93242a3710 | |||
40cddc2283 | |||
ea55873478 | |||
3ce7bbbcbf | |||
46fb0e08e2 | |||
1023e516c9 | |||
c79cb8ec68 | |||
264227c6a6 | |||
dd869977e6 | |||
b67609336a | |||
65cf0873ca | |||
0297ea3406 | |||
6dc4d493f9 | |||
bbddb42fab | |||
c4d36accd6 | |||
a639bcfdeb | |||
14968bdfcc | |||
d0e39f4bb1 | |||
9e506a23c4 | |||
8c7fbc31b6 | |||
13353c761e | |||
c97f05e5a5 | |||
0a8dc74db5 | |||
d651a07be7 | |||
11ebb94fd7 | |||
ccd4f5caa4 | |||
c7479168bd | |||
2841ff1374 | |||
a1392a97f0 | |||
9928a18220 | |||
da7d1d9295 | |||
0db90d60cd | |||
600c05658c | |||
a82a417977 | |||
c8187e90a1 | |||
a339f55214 | |||
719b02dd2b | |||
d4d4305852 | |||
1acae25889 | |||
d6fd8fd569 | |||
269873972c | |||
c2dd015264 | |||
a9c698b531 | |||
d9ef3c2535 | |||
8b77ee1078 | |||
e6aa8cb69b | |||
392c2ee284 | |||
c741972173 | |||
334fd7bc8a | |||
46cb7ab752 | |||
e7350bcd03 | |||
eada5f1e27 | |||
80bac75426 | |||
8f9db90fb7 | |||
7fbff96600 | |||
baa810cb0c | |||
c452117173 | |||
61b44614b6 | |||
d396cc7c14 | |||
5ab9d51c99 | |||
b82907ecb5 | |||
5eb11a0ee3 | |||
a0d8008215 | |||
33c9d475a1 | |||
ce998fe5aa | |||
70888ab701 | |||
00558eefde | |||
3770faf0a3 | |||
1930cd302e | |||
0d8ca91943 | |||
6d5ff86d5c | |||
c6cafd1bbb | |||
0bedff4bfe | |||
2be03622e8 | |||
36b2a93c2f | |||
0d292dcddc | |||
c2d8cf8de6 | |||
1ace682088 | |||
49dec22e98 | |||
332cca361a | |||
3cc6b06762 | |||
67701a99ba | |||
2fd4ffe8e7 | |||
a970fa4eb2 | |||
d4e8f99993 | |||
e74f30fd7b | |||
c0ee9f1c51 | |||
fa1b90b90c | |||
bbfbbae98d | |||
a53ba4eb50 | |||
cadc934b94 | |||
7e6f7a9e76 | |||
488e60b90c | |||
ec04f601be | |||
920801c82b | |||
5742a0cb35 | |||
4374193573 | |||
c9cdd1d69c | |||
51ba4b1631 | |||
9fc4d46569 | |||
6c32eeb73e | |||
6f5c50ac06 | |||
7b9cde6b76 | |||
a02c45861f | |||
b3d1045c57 | |||
0a7dcbb0ed | |||
1a9d94e3ee | |||
16c39438d9 | |||
640c145197 | |||
b6c721a089 | |||
9f5b199557 | |||
371ba25f93 | |||
3b3587d596 | |||
3ee61c2441 | |||
48d826ec06 | |||
1fef7de91e | |||
631a430a2a | |||
7b41f35d37 | |||
1866738763 | |||
48d7f7f7b2 | |||
8aece83b36 | |||
6e3224b60f | |||
1e521c04f6 | |||
f4aa19990f | |||
b50923915f | |||
bb13547140 | |||
91561c6857 | |||
027c9e977e | |||
45ee804278 | |||
62bc8f93a1 | |||
ca1171c0f7 | |||
1dcd24950d | |||
10f776aa72 | |||
917a0c23cd | |||
eff00ca518 | |||
1214e78bc6 | |||
ce8e3575c3 | |||
94180e2063 | |||
f3681897fa | |||
e607153027 | |||
9f60ad7fca | |||
e6dd88f9d4 | |||
12a11e8e81 | |||
c40076c0ea | |||
11081e444f | |||
55642823d3 | |||
6a2687d5b8 | |||
351f577104 | |||
590783a5a1 | |||
829e5324f3 | |||
4fb4044168 | |||
b8a732b314 | |||
724af5fb5c | |||
0e1b87e15e | |||
aff770657f | |||
c6dd701e8c | |||
2dea4ea043 | |||
d79dbc15a2 | |||
b79dd3e3bf | |||
406e579921 | |||
1e16718d9d | |||
eddd610613 | |||
3c73c272cb | |||
a41a30e11e | |||
53fc305fa3 | |||
1c77b69745 | |||
bb8474d200 | |||
48b77855b1 | |||
909e5b5bf2 | |||
3dc65adcf2 | |||
a0b99eae3b | |||
307e4ce614 | |||
4fd5ca5017 | |||
7aa24c3ec1 | |||
c2ad95f927 | |||
06f3839569 | |||
f975e84611 | |||
81f7c50877 | |||
2010f461f9 | |||
7335daf806 | |||
17f636789b | |||
446949ce5b | |||
d1c77a0bb5 | |||
da11de47dc | |||
b4d14cc1c0 | |||
c15a37b7b6 | |||
dc83f60f2c | |||
7251b60389 | |||
4b97245f27 | |||
eea6e266e3 | |||
27e2f3d664 | |||
0f5720c715 | |||
2f7d7e9db2 | |||
8227fe5de0 | |||
ed49ee6a10 | |||
db9af1f232 | |||
5e095b9deb | |||
4fb94bdd6d | |||
e2582373ad | |||
309483ea08 | |||
b2bc200eb1 | |||
a0ab0deb2a | |||
1f9491ce73 | |||
1663f19b2a | |||
07277862cf | |||
f1016bd9cc | |||
c39f479b96 | |||
a4d1a2bf76 | |||
8bba04305f | |||
516d6fc136 | |||
2e096cb495 | |||
3505f915c5 | |||
254cd02649 | |||
97e37f34a1 | |||
ae6b66b270 | |||
7a1aecebd5 | |||
387ffc9ade | |||
d0c3537753 | |||
eb97bfbeeb | |||
7fee33999f | |||
99c0e92336 | |||
46494c033c | |||
85986ce13e | |||
bdeae5b38b | |||
edc7aedbe0 | |||
900e03791a | |||
2a977bbf47 | |||
d29df73d05 | |||
1150e1b047 | |||
cf7eeb5bde | |||
c2a367bd28 | |||
3c19e9d4cf | |||
e295e53f86 | |||
be02061b94 | |||
08dbe1e35b | |||
8e9dca56bc | |||
2e9fe29e99 | |||
b0a6f207cd | |||
a51a79a763 | |||
b2d1903009 | |||
53f99f620c | |||
312267567f | |||
34b2571a2e | |||
b79874d056 | |||
27e42fac56 | |||
4124d5eb38 | |||
bb8c6378bc | |||
c3109f1894 | |||
59b4b5ff39 | |||
2d7335ff85 | |||
2e49de8573 | |||
a4772e3c25 | |||
de9937606e | |||
1b749e9f1a | |||
f0b3748596 | |||
2ea42d10e6 | |||
55cf10d289 | |||
09ab52b971 | |||
c00aa6fa73 | |||
817c8d63e1 | |||
cce885d961 | |||
6d92c484cd | |||
f5be3b0f8e | |||
85afbbc01e | |||
e0c0c69d35 | |||
df82650931 | |||
3196006bad | |||
820755b9e0 | |||
1946c7aa88 | |||
7c35a2d427 | |||
c4ee706591 | |||
4ee5f349f0 | |||
21a1974ba1 | |||
969b4d22fc | |||
6169324ffd | |||
003cad1f58 | |||
c8df4b21a0 | |||
7f49fb2d0e | |||
1cb028c273 | |||
4ad6c10a64 | |||
899df95994 | |||
0fa7ffa34a | |||
c46a597828 | |||
764f1b20d9 | |||
01b4a681da | |||
16299d480e | |||
b0cb9f6fd0 | |||
5b133c951d | |||
e29a7ec0e2 | |||
e7213d8a70 | |||
4d7510ad3a | |||
d8ef918a67 | |||
cb49d0fbf7 | |||
70c835eb93 | |||
40b51f1a77 | |||
1bff76c637 | |||
24b6bcfa47 | |||
b22baefa5a | |||
a6e0f4e728 | |||
e366220e8b | |||
f71e304731 | |||
55f58bb689 | |||
03b752759f | |||
85c6ed8483 | |||
a081c6a371 | |||
6c3122a3d8 | |||
0c80b87606 | |||
b6da539fcd | |||
d4d99772b9 | |||
4f8be43527 | |||
eb2282efac | |||
85b5bf7b58 | |||
626a5ccb11 | |||
806ffb2754 | |||
5900c13e08 | |||
d399698eb1 | |||
457d329b0b | |||
45c428d30a | |||
7ddb72239e | |||
580820ef44 | |||
d037150eb3 | |||
35a102e4a9 | |||
f7c75df9be | |||
d0e18fe75a | |||
e30c08fd5b | |||
1058659174 | |||
b9ac588f87 | |||
40b4bb0a3e | |||
9bd9652b3d | |||
46c4fe9516 | |||
a71b5e6aba | |||
e0cbf1447f | |||
b69e54f1e9 | |||
ecb1c21179 | |||
acff19ad58 | |||
f79f796a14 | |||
a08a43e2bd | |||
8238e97a45 | |||
88202b57ee | |||
324fa39b56 | |||
233393e853 | |||
84afc3274a | |||
a5bfc4977d | |||
19205bbe8b | |||
f144e0b8ef | |||
8473d00148 | |||
c8391caf07 | |||
d19bb3a204 | |||
ed71e935fc | |||
5ba243a1ea | |||
88a30650a5 | |||
420b223ca4 | |||
7168d519d1 | |||
4a09463f0a | |||
0a52029840 | |||
6841b30a77 | |||
dbaff89b8d | |||
b7cd9ea75c | |||
b54f3b7ab4 | |||
83d7c38c38 | |||
644dbc8159 | |||
e566a5edf1 | |||
ef5c6b73af | |||
3d7b133005 | |||
37a18d3c6e | |||
d6290471ba | |||
c13b360ca5 | |||
e5cb0261ba | |||
6c91cb008c | |||
9561301500 | |||
349d67fa7b | |||
ce64664447 | |||
370b8cd40f | |||
efde919689 | |||
9a88b75654 | |||
0ceab03334 | |||
c8cec06d85 | |||
018cd7d245 | |||
471b5c08f6 | |||
b11a978962 | |||
95aba2b44b | |||
5d6d0e95ec | |||
f0f75ecaa8 |
2
.cz.yaml
2
.cz.yaml
@ -17,5 +17,5 @@ commitizen:
|
||||
prerelease_offset: 1
|
||||
tag_format: $version
|
||||
update_changelog_on_bump: false
|
||||
version: 1.16.0
|
||||
version: 1.17.1
|
||||
version_scheme: semver
|
||||
|
55
.github/ISSUE_TEMPLATE/new_model.md
vendored
55
.github/ISSUE_TEMPLATE/new_model.md
vendored
@ -33,39 +33,22 @@ Describe in detail the following:
|
||||
|
||||
- [ ] 🛠️ Migrations added
|
||||
|
||||
- [ ] ♻️ Serializer Created
|
||||
|
||||
- [ ] 🔄 [ViewSet Created](https://nofusscomputing.com/projects/centurion_erp/development/views/)
|
||||
|
||||
- [ ] 🔗 URL Route Added
|
||||
|
||||
- [ ] 🏷️ Model tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()` function
|
||||
- [ ] 🏷️ [Model tag]().
|
||||
|
||||
- [ ] 📘 Tag updated in the [docs](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
- [ ] tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()`
|
||||
- [ ] ⚒️ Migration _Ticket Linked Item item_type choices update_
|
||||
|
||||
>[!note]
|
||||
> Ensure that when creating the tag the following is adhered to:
|
||||
> - Two words are not to contain a space char, `\s`. It is to be replaced with an underscore `_`
|
||||
> - As much as practical, keep the tag as close to the model name as possible
|
||||
|
||||
- [ ] 📝 New [History model](https://nofusscomputing.com/projects/centurion_erp/development/core/model_history/) created
|
||||
|
||||
- Sub-Models **_ONLY_**
|
||||
|
||||
- [ ] Model class variable `history_app_label` set to correct application label
|
||||
|
||||
- [ ] Model class variable `history_model_name` set to correct model label
|
||||
|
||||
- [ ] 📓 New [Notes model](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/) created
|
||||
- [ ] 🆕 Model Created
|
||||
- [ ] 🛠️ Migrations added
|
||||
- [ ] Add `app_label` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().model_apps`
|
||||
- [ ] _(Notes not used/required) -_ Add `model_name` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().excluded_models`
|
||||
- [ ] 🧪 [Unit tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
- [ ] 🧪 [Functional tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
- [ ] tag added to class
|
||||
|
||||
- [ ] Admin Documentation added/updated _if applicable_
|
||||
|
||||
- [ ] Developer Documentation added/updated _if applicable_
|
||||
|
||||
- [ ] User Documentation added/updated
|
||||
|
||||
---
|
||||
@ -78,15 +61,15 @@ Describe in detail the following:
|
||||
|
||||
- Unit Tests
|
||||
- [ ] [Model](https://nofusscomputing.com/projects/centurion_erp/development/models/#tests)
|
||||
- [ ] ViewSet
|
||||
- [ ] Serializer
|
||||
- [ ] ViewSet
|
||||
- Function Test
|
||||
- [ ] ViewSet
|
||||
- [ ] API Metadata
|
||||
- [ ] API Permissions
|
||||
- [ ] API Render (fields)
|
||||
- [ ] History Entries
|
||||
- [ ] History API Render (fields)
|
||||
- [ ] Model
|
||||
- [ ] Serializer
|
||||
- [ ] ViewSet
|
||||
|
||||
|
||||
## ✅ Requirements
|
||||
@ -95,6 +78,24 @@ A Requirement is a must have. In addition will also be tested.
|
||||
|
||||
- [ ] Must have a [model_tag](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
|
||||
<!--
|
||||
|
||||
When detailing requirements the following must be taken into account:
|
||||
|
||||
- what the user should be able to do
|
||||
|
||||
- what the user should not be able to do
|
||||
|
||||
- what should occur when a user performs an action
|
||||
|
||||
-->
|
||||
|
||||
- Functional Requirements
|
||||
|
||||
|
||||
- Non-Functional Requirements
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- Add additional requirement here and as a check box list -->
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,9 +1,10 @@
|
||||
venv/**
|
||||
*/static/**
|
||||
__pycache__
|
||||
**.sqlite3
|
||||
**.sqlite*
|
||||
**.sqlite
|
||||
**.coverage
|
||||
.coverage*
|
||||
artifacts/
|
||||
**.tmp.*
|
||||
volumes/
|
||||
@ -19,3 +20,4 @@ package.json
|
||||
feature_flags.json
|
||||
coverage_*.json
|
||||
*-coverage.xml
|
||||
log/
|
||||
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -8,5 +8,6 @@
|
||||
"qwtel.sqlite-viewer",
|
||||
"jebbs.markdown-extended",
|
||||
"william-voyek.vscode-nginx",
|
||||
"detachhead.basedpyright",
|
||||
]
|
||||
}
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -29,7 +29,7 @@
|
||||
"3",
|
||||
"--bind",
|
||||
"0.0.0.0:8002",
|
||||
"app.wsgi:application",
|
||||
"centurion.wsgi:application",
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
@ -50,6 +50,18 @@
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Centurion Model (Management Command)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"models",
|
||||
// "0.0.0.0:8002"
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Migrate",
|
||||
"type": "debugpy",
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -6,6 +6,7 @@
|
||||
],
|
||||
"python.testing.pytestArgs": [
|
||||
"--override-ini", "addopts=",
|
||||
"--no-migrations",
|
||||
"app",
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
@ -23,4 +24,6 @@
|
||||
"yellow": 60,
|
||||
"green": 90
|
||||
},
|
||||
"telemetry.feedback.enabled": false,
|
||||
"python.languageServer": "None",
|
||||
}
|
173
CHANGELOG.md
173
CHANGELOG.md
@ -1,3 +1,176 @@
|
||||
## 1.17.1 (2025-06-02)
|
||||
|
||||
### Fixes
|
||||
|
||||
- **base**: Add python metrics to prometheus exporter
|
||||
|
||||
## 1.17.0 (2025-05-16)
|
||||
|
||||
### feat
|
||||
|
||||
- **access**: model access.Company feature flag `2025-00008`
|
||||
- **access**: URL route for model access.Company
|
||||
- **access**: Migration for model access.Company
|
||||
- **access**: Serializer for model access.Company
|
||||
- **access**: New model access.Company
|
||||
- **access**: Organization -> Tenant Permission Migration
|
||||
- **docker**: Serve a robots.txt file for NO indexing
|
||||
- **access**: Organization -> Tenant Permission Migration
|
||||
- **base**: Add var `AUTH_USER_MODEL` to settings
|
||||
- **core**: Add Action comments on ticket change
|
||||
- **core**: Remove add, change and delete permissions for model TicketCommentAction from permission selector
|
||||
- **core**: Serializer for model TicketCommentAction
|
||||
- **core**: Migrations for model TicketCommentAction
|
||||
- **core**: New model TicketCommentAction
|
||||
- **core**: Setup serializer to meet requirements
|
||||
- **core**: Setup model to meet requirements
|
||||
- **api**: Add exception logging to ViewSetCommon
|
||||
- **python**: Upgrade DRF Spectacular 0.27.2 -> 0.28.0
|
||||
- **python**: Upgrade DRF 3.15.2 -> 3.16.0
|
||||
- **core**: When processing slash command duration, cater for new ticket models
|
||||
- **api**: Add Logging function to Common ViewSet
|
||||
- **access**: Add Logging function to Tenancy model
|
||||
- **base**: Enable user to customize log file location
|
||||
- **core**: Do validate the comment_type field for TicketCommentBase
|
||||
- **itam**: Add Feature Flag `2025-00007` ITAMAssetBase
|
||||
- **itam**: Add endpoint for ITAMAssetBase
|
||||
- Model tag migration for Asset and IT Asset
|
||||
- **itam**: Model tag for ITAsset
|
||||
- **accounting**: Model tag for Asset
|
||||
- **accounting**: Add app label to kb articles for notes
|
||||
- **accounting**: Migrations for notes model for AssetBase
|
||||
- **accounting**: Migrations for history model for AssetBase
|
||||
- **accounting**: Notes Viewset for AssetBase
|
||||
- **accounting**: Notes Serializer for AssetBase
|
||||
- **accounting**: Notes model for AssetBase
|
||||
- **accounting**: History model for AssetBase
|
||||
- **itam**: Serializer for ITAssetBase
|
||||
- **itam**: Migrations for ITAssetBase
|
||||
- **itam**: Add Model ITAssetBase
|
||||
- **accounting**: Viewset for Assets
|
||||
- **accounting**: Serializer for model AssetBase
|
||||
- **accounting**: Migrations for model AssetBase
|
||||
- **accounting**: Add Model AssetBase
|
||||
|
||||
### Fixes
|
||||
|
||||
- **api**: Dont try to access attribute if not exist in common viewset
|
||||
- **api**: Dont try to access attribute if not exist in common viewset
|
||||
- **api**: Correct ViewSet Sub-Model lookup
|
||||
- **core**: Only take action on ticket comment if view exists
|
||||
- **api**: Ensure multi-nested searching for sub-models works
|
||||
- **core**: ensure slash command is called on ticket description
|
||||
- **core**: Spent slash command is valid for time spent
|
||||
- **core**: Correct logic for TicketCommentSolution
|
||||
- **core**: Correct logic for TicketCommentBase
|
||||
- **accounting**: Ensure correct sub-model check is conducted within model type
|
||||
- **itam**: ensure RO field asset_type is set
|
||||
- **itim**: Ensure that itam base model is always imported
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **human_resources**: Update Functional ViewSet to use PyTest for Employee Model
|
||||
- **Access**: Update Functional ViewSet to use PyTest for Person Model
|
||||
- **Access**: Update Functional ViewSet to use PyTest for Entity Model
|
||||
- **Access**: Update Functional ViewSet to use PyTest for Contact Model
|
||||
- **Access**: Update Functional Permission to use PyTest for Person Model
|
||||
- **Access**: Update Functional Permission to use PyTest for Entity Model
|
||||
- **Access**: Update Functional Permission to use PyTest for Contact Model
|
||||
- **Access**: Update Functional Serializer to use PyTest for Contact Model
|
||||
- **Access**: Update Functional Serializer to use PyTest for Entity Model
|
||||
- **Access**: Update Functional Serializer to use PyTest for Person Model
|
||||
- **human_resources**: Update Functional Serializer to use PyTest for Employee Model
|
||||
- **human_resources**: Update Functional Permissions to use PyTest for Employee Model
|
||||
- **human_resources**: Update Functional Metadata to use PyTest for Employee Model
|
||||
- **access**: Update Functional Metadata to use PyTest for Person Model
|
||||
- **access**: Update Functional Metadata to use PyTest for Entity Model
|
||||
- **access**: Update Functional Metadata to use PyTest for Contact Model
|
||||
- **access**: Update Model Entity to use PyTest for Model Test Suite
|
||||
- **access**: Update Model Contact to use PyTest for Model Test Suite
|
||||
- **access**: Update Model Person to use PyTest for Model Test Suite
|
||||
- **human_resources**: Update Model Employee to use PyTest for Model Test Suite
|
||||
- **human_resources**: Update Model Employee to use PyTest API Fields Render
|
||||
- **access**: Update Model Person to use PyTest API Fields Render
|
||||
- **access**: Update Model Contact to use PyTest API Fields Render
|
||||
- **access**: Update Model Entity to use PyTest API Fields Render
|
||||
- **access**: Rename model Organization -> Tenant
|
||||
- **settings**: Update all references to `User` to use `get_user_model()`
|
||||
- **project_management**: Update all references to `User` to use `get_user_model()`
|
||||
- **itam**: Update all references to `User` to use `get_user_model()`
|
||||
- **devops**: Update all references to `User` to use `get_user_model()`
|
||||
- **core**: Update all references to `User` to use `get_user_model()`
|
||||
- **config_management**: Update all references to `User` to use `get_user_model()`
|
||||
- **assistance**: Update all references to `User` to use `get_user_model()`
|
||||
- **app**: Update all references to `User` to use `get_user_model()`
|
||||
- **api**: Update all references to `User` to use `get_user_model()`
|
||||
- **accounting**: Update all references to `User` to use `get_user_model()`
|
||||
- **access**: Update all references to `User` to use `get_user_model()`
|
||||
- **access**: when fetching parent object, use the parent_model get function
|
||||
- **api**: Limit url pk regex to ensure the value is a number
|
||||
|
||||
### Tests
|
||||
|
||||
- **access**: Functional ViewSet Test Suite Company model
|
||||
- **access**: Functional Serializer Test Suite Company model
|
||||
- **access**: Functional Permissions Test Suite Company model
|
||||
- **access**: Functional MetaData Test Suite Company model
|
||||
- **access**: ViewSet Test Suite Company model
|
||||
- **access**: API field render Test Suite Company model
|
||||
- **access**: Model Test Suite Company model
|
||||
- **core**: Unit viewset Test Cases for TicketCommentAction model
|
||||
- **core**: Unit model Test Cases for TicketCommentAction model
|
||||
- **core**: Unit API Render Test Cases for TicketCommentAction model
|
||||
- **core**: Interim Functional model Test Case TicketCommentAction
|
||||
- **core**: Ensure that a ticket milestone comes from the same assigned project
|
||||
- **core**: SKIP Tests TicketBase Description Slash command Checks
|
||||
- **core**: TicketBase Description Slash command Checks
|
||||
- **core**: TicketBase Remaining Serializer Chacks
|
||||
- **core**: Partial functional Model Test Suite covering some slash commande for TicketCommentSolution
|
||||
- **core**: ensure ticket is un-solved for ticketcomment unit api render fields check
|
||||
- **core**: ensure slash command is called on ticket comment
|
||||
- **core**: Unit ViewSet Test Suite for TicketCommentSolution
|
||||
- **core**: Unit ViewSet Test Suite for TicketCommentBase
|
||||
- **core**: Skip Related slash command checks until migrating tickets to new model
|
||||
- **core**: Add ability to unit api field rendering test case for second api request if required
|
||||
- **core**: Partial Functional Model test cases (Slash Commands) for TicketCommentBase
|
||||
- **core**: Functional Model test cases (Slash Commands) for TicketBaseModel
|
||||
- **core**: Partial Slash Command re-write
|
||||
- **core**: correct field so its valid for unit TicketCommentBase model
|
||||
- **core**: Unit API Fields Render for TicketCommentSolution model
|
||||
- **core**: Unit API Fields Render for TicketCommentBase model
|
||||
- **core**: Unit Model assert save and call are called for TicketBase
|
||||
- **core**: Unit Model Checks for TicketCommentSolution
|
||||
- **core**: Unit Model Checks for TicketCommentBase
|
||||
- **itam**: test meta attribute itam_sub_model_type for ITAMBaseModel
|
||||
- **itam**: Dont use constants where variables should be used
|
||||
- **itam**: Remaining Unit Model test cases for AssetBase
|
||||
- **accounting**: Remaining Unit Model test cases for AssetBase
|
||||
- **itam**: Functional ViewSet Test Cases for ITAMAssetBase
|
||||
- **itam**: Functional Serializer Test Cases for ITAMAssetBase
|
||||
- **itam**: Functional Permissions Test Cases for ITAMAssetBase
|
||||
- **itam**: Functional Metadata Test Cases for ITAMAssetBase
|
||||
- **itam**: Functional History Test Cases for ITAMAssetBase
|
||||
- **accounting**: Functional ViewSet Test Cases for AssetBase
|
||||
- **accounting**: Functional Serializer Test Cases for AssetBase
|
||||
- **accounting**: Functional Permissions Test Cases for AssetBase
|
||||
- **accounting**: Functional Metadata Test Cases for AssetBase
|
||||
- **accounting**: History Test Cases for AssetBase
|
||||
- add missing merge of add_data for api permissions tests
|
||||
- remove ticket only vars from api permissions tests
|
||||
- **api**: dont use constants for variable data
|
||||
- correct viewset tests
|
||||
- **itam**: Unit Viewset checks for AssetBase Model
|
||||
- **core**: Add missing fields is_global checks for ticket base
|
||||
- **api**: Add submodel url resolution for metadata
|
||||
- **itam**: Unit API Fields checks for ITAM AssetBase Model
|
||||
- **accounting**: Unit API Fields checks for AssetBase Model
|
||||
- Support variables that were defined as properties.
|
||||
- **api**: Ensure that model notes is added to model create for api field tests
|
||||
- **accounting**: Unit Viewset checks for AssetBase Model
|
||||
- **itam**: Unit Model checks for ITAMAssetBase Model
|
||||
- **base**: update Model base test suite for model_notes field
|
||||
- **accounting**: Unit Model checks for AssetBase Model
|
||||
|
||||
## 1.16.0 (2025-05-04)
|
||||
|
||||
### feat
|
||||
|
@ -1,3 +1,45 @@
|
||||
## Version 1.18.0
|
||||
|
||||
- Added new model for History
|
||||
|
||||
!!! info
|
||||
Migration of the old history tables to the new history tables occurs as part of post migration. As such the time it will take to migrate the history is dependent upon how many history entries per model. This should be planned for when upgrading to this version. if for some reason the migration is interrupted, you can safely restart it again by running the migrate command.
|
||||
|
||||
- Added new model for notes
|
||||
|
||||
!!! info
|
||||
Migration of the old notes tables to the new note tables occurs as part of post migration. As such the time it will take to migrate the history is dependent upon how many history entries per model. This should be planned for when upgrading to this version. if for some reason the migration is interrupted, you can safely restart it again by running the migrate command.
|
||||
|
||||
- Removed Django UI
|
||||
|
||||
[UI](https://github.com/nofusscomputing/centurion_erp) must be deployed seperatly.
|
||||
|
||||
- Removed API v1
|
||||
|
||||
|
||||
## Version 1.17.0
|
||||
|
||||
- Added setting for log files.
|
||||
|
||||
Enables user to specify a default path for centurion's logging. Add the following to your settings file `/etc/itsm/settings.py`
|
||||
|
||||
``` py
|
||||
LOG_FILES = {
|
||||
"centurion": "/var/log/centurion.log", # Normal Centurion Operations
|
||||
"weblog": "/var/log/weblog.log", # All web requests made to Centurion
|
||||
"rest_api": "/var/log/rest_api.log", # Rest API
|
||||
"catch_all":"/var/log/catch-all.log" # A catch all log. Note: does not log anything that has already been logged.
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
With this new setting, the previous setting `LOGGING` will no longer function.
|
||||
|
||||
- Renamed `Organization` model to `Tenant` so as to reflect what is actually is.
|
||||
|
||||
- `robots.txt` file now being served from the API container at path `/robots.txt` with `User-agent: *` and `Disallow: /`
|
||||
|
||||
|
||||
## Version 1.16.0
|
||||
|
||||
- Employees model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
||||
|
@ -1,12 +1,15 @@
|
||||
import django
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
admin.site.unregister(Group)
|
||||
|
||||
class TeamInline(admin.TabularInline):
|
||||
|
@ -1,6 +1,7 @@
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
|
||||
|
||||
class AutoCreatedField(models.DateTimeField):
|
||||
"""
|
||||
@ -50,7 +51,7 @@ class AutoLastModifiedField(AutoCreatedField):
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
|
||||
value = now()
|
||||
value = now().replace(microsecond=0)
|
||||
|
||||
setattr(model_instance, self.attname, value)
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
class OrganizationForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = [
|
||||
'name',
|
||||
'manager',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
@ -1,69 +0,0 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from .team_users import TeamUsersForm, TeamUsers
|
||||
|
||||
from access.models.team import Team
|
||||
from access.functions import permissions
|
||||
|
||||
from app import settings
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
TeamUserFormSet = inlineformset_factory(
|
||||
model=TeamUsers,
|
||||
parent_model= Team,
|
||||
extra = 1,
|
||||
fields=[
|
||||
'user',
|
||||
'manager'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TeamFormAdd(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = [
|
||||
'team_name',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = [
|
||||
'team_name',
|
||||
'permissions',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['permissions'].widget.attrs = {'style': "height: 200px;"}
|
||||
|
||||
self.fields['permissions'].queryset = permissions.permission_queryset()
|
@ -1,16 +0,0 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
class TeamUsersForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = TeamUsers
|
||||
fields = [
|
||||
'user',
|
||||
'manager',
|
||||
]
|
@ -9,6 +9,7 @@ def permission_queryset():
|
||||
|
||||
apps = [
|
||||
'access',
|
||||
'accounting',
|
||||
'assistance',
|
||||
'config_management',
|
||||
'core',
|
||||
@ -36,14 +37,17 @@ def permission_queryset():
|
||||
'add_history',
|
||||
'add_organization',
|
||||
'add_taskresult',
|
||||
'add_ticketcommentaction',
|
||||
'change_checkin',
|
||||
'change_history',
|
||||
'change_organization',
|
||||
'change_taskresult',
|
||||
'change_ticketcommentaction',
|
||||
'delete_checkin',
|
||||
'delete_history',
|
||||
'delete_organization',
|
||||
'delete_taskresult',
|
||||
'delete_ticketcommentaction',
|
||||
'view_checkin',
|
||||
'view_history',
|
||||
]
|
||||
|
@ -1,18 +1,17 @@
|
||||
from django.contrib.auth.middleware import (
|
||||
AuthenticationMiddleware,
|
||||
SimpleLazyObject,
|
||||
partial,
|
||||
)
|
||||
from django.contrib.auth.models import User, Group
|
||||
import django
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
from access.models.team import Team
|
||||
|
||||
|
||||
from settings.models.app_settings import AppSettings
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
class RequestTenancy(MiddlewareMixin):
|
||||
"""Access Middleware
|
||||
@ -24,9 +23,9 @@ class RequestTenancy(MiddlewareMixin):
|
||||
|
||||
def process_request(self, request):
|
||||
|
||||
request.app_settings = AppSettings.objects.select_related('global_organization').get(
|
||||
request.app_settings = AppSettings.objects.select_related('global_organization').filter(
|
||||
owner_organization = None
|
||||
)
|
||||
)[0]
|
||||
|
||||
request.tenancy = Tenancy(user = request.user, app_settings = request.app_settings)
|
||||
|
||||
@ -41,8 +40,8 @@ class Tenancy:
|
||||
_app_settings: AppSettings = None
|
||||
|
||||
|
||||
_user_organizations: list([Organization]) = None
|
||||
"""Cached User Organizations"""
|
||||
_user_organizations: list([Tenant]) = None
|
||||
"""Cached User Tenants"""
|
||||
|
||||
_user_teams: list([Team]) = None
|
||||
"""Cached User Teams"""
|
||||
@ -91,7 +90,7 @@ class Tenancy:
|
||||
|
||||
|
||||
|
||||
def is_member(self, organization: Organization) -> bool:
|
||||
def is_member(self, organization: Tenant) -> bool:
|
||||
"""Returns true if the current user is a member of the organization
|
||||
|
||||
iterates over the user_organizations list and returns true if the user is a member
|
||||
@ -114,11 +113,11 @@ class Tenancy:
|
||||
|
||||
|
||||
|
||||
def has_organization_permission(self, organization: Organization, permissions_required: str) -> bool:
|
||||
def has_organization_permission(self, organization: Tenant, permissions_required: str) -> bool:
|
||||
""" Check if user has permission within organization.
|
||||
|
||||
Args:
|
||||
organization (int): Organization to check.
|
||||
organization (int): Tenant to check.
|
||||
permissions_required (list): if doing object level permissions, pass in required permission.
|
||||
|
||||
Returns:
|
||||
@ -127,9 +126,9 @@ class Tenancy:
|
||||
|
||||
has_permission: bool = False
|
||||
|
||||
if type(organization) is not Organization:
|
||||
if type(organization) is not Tenant:
|
||||
|
||||
raise TypeError('Organization must be of type Organization')
|
||||
raise TypeError('Tenant must be of type Tenant')
|
||||
|
||||
|
||||
if type(permissions_required) is not str:
|
||||
|
25
app/access/migrations/0007_rename_organization_tenant.py
Normal file
25
app/access/migrations/0007_rename_organization_tenant.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-14 11:06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0005_entity_person_entityhistory_entitynotes_role_and_more'),
|
||||
('assistance', '0005_knowledgebasecategoryhistory_knowledgebasehistory'),
|
||||
('config_management', '0007_configgroupshistory_configgrouphostshistory_and_more'),
|
||||
('core', '0022_ticketcommentbase_ticketbase_ticketcommentsolution_and_more'),
|
||||
('devops', '0011_alter_gitgroup_unique_together_and_more'),
|
||||
('itam', '0010_alter_software_organization'),
|
||||
('itim', '0009_slmticket_requestticket'),
|
||||
('project_management', '0005_projecthistory_projectmilestonehistory_and_more'),
|
||||
('settings', '0011_appsettingshistory_externallinkhistory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name = 'Organization',
|
||||
new_name = 'Tenant'
|
||||
),
|
||||
]
|
@ -0,0 +1,47 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-14 13:48
|
||||
|
||||
import access.models.team
|
||||
import access.models.tenancy
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0007_rename_organization_tenant'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='tenant',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Tenant', 'verbose_name_plural': 'Tenants'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entity',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Tenancy this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.tenant', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Tenant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Tenancy this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.tenant', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Tenant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Tenant this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.tenant', validators=[access.models.team.Team.validatate_organization_exists], verbose_name='Tenant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tenant',
|
||||
name='manager',
|
||||
field=models.ForeignKey(help_text='Manager for this Tenancy', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Manager'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tenant',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Name of this Tenancy', max_length=50, unique=True, verbose_name='Name'),
|
||||
),
|
||||
]
|
@ -0,0 +1,135 @@
|
||||
|
||||
from django.contrib.auth.models import ContentType, Permission
|
||||
from django.db import migrations
|
||||
|
||||
from access.models.team import Team
|
||||
|
||||
ContentType.DoesNotExist
|
||||
|
||||
def add_tenancy_permissions(apps, schema_editor):
|
||||
|
||||
print('')
|
||||
print(f"Begin permission migration for rename of Organization to Tenant.")
|
||||
|
||||
try:
|
||||
|
||||
add_permission = Permission.objects.get(
|
||||
codename = 'add_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
change_permission = Permission.objects.get(
|
||||
codename = 'change_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
delete_permission = Permission.objects.get(
|
||||
codename = 'delete_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
view_permission = Permission.objects.get(
|
||||
codename = 'view_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
print(f' Searching for Teams.')
|
||||
|
||||
teams = Team.objects.select_related('group_ptr__permissions')
|
||||
|
||||
print(f'Found {str(len(teams))} Teams.')
|
||||
|
||||
for team in teams:
|
||||
|
||||
print(f' Processing Team {str(team.team_name)}.')
|
||||
|
||||
permissions = team.group_ptr.permissions.all()
|
||||
|
||||
print(f' Searching for Organization Permissions.')
|
||||
print(f' Found {str(len(permissions))} Permissions.')
|
||||
|
||||
for permission in permissions:
|
||||
|
||||
if '_organization' not in permission.codename:
|
||||
|
||||
continue
|
||||
|
||||
action = str(permission.codename).split('_')[0]
|
||||
|
||||
print(f' Found Organization Permission {str(action)}')
|
||||
|
||||
if action == 'add':
|
||||
|
||||
team.group_ptr.permissions.add( add_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
elif action == 'change':
|
||||
|
||||
team.group_ptr.permissions.add( change_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
elif action == 'delete':
|
||||
|
||||
team.group_ptr.permissions.add( delete_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
elif action == 'view':
|
||||
|
||||
team.group_ptr.permissions.add( view_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
|
||||
print(f' Completed Team {str(team.team_name)}.')
|
||||
|
||||
except ContentType.DoesNotExist:
|
||||
# DB is new so no content types. no migration to be done.
|
||||
pass
|
||||
|
||||
print(' Permission Migration Actions Complete.')
|
||||
|
||||
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0008_alter_tenant_options_alter_entity_organization_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_tenancy_permissions),
|
||||
]
|
||||
|
||||
|
@ -0,0 +1,43 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-16 09:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0009_migrate_organization_permission_tenant'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Company',
|
||||
fields=[
|
||||
('entity_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='access.entity')),
|
||||
('name', models.CharField(help_text='The name of this entity', max_length=80, verbose_name='Name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Company',
|
||||
'verbose_name_plural': 'Companies',
|
||||
'ordering': ['name'],
|
||||
'sub_model_type': 'company',
|
||||
},
|
||||
bases=('access.entity',),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entity',
|
||||
name='entity_type',
|
||||
field=models.CharField(help_text='Type this entity is', max_length=30, verbose_name='Entity Type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='dob',
|
||||
field=models.DateField(blank=True, help_text='The Persons Date of Birth (DOB)', null=True, verbose_name='DOB'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='m_name',
|
||||
field=models.CharField(blank=True, help_text='The persons middle name(s)', max_length=100, null=True, verbose_name='Middle Name(s)'),
|
||||
),
|
||||
]
|
@ -0,0 +1,110 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-06 01:41
|
||||
|
||||
import access.models.tenancy_abstract
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
|
||||
("core", "0028_delete_history"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="team",
|
||||
name="is_global",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="team",
|
||||
name="model_notes",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="Tid bits of information",
|
||||
null=True,
|
||||
verbose_name="Notes",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="team",
|
||||
name="organization",
|
||||
field=models.ForeignKey(
|
||||
help_text="Tenant this belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.tenant",
|
||||
validators=[
|
||||
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
|
||||
],
|
||||
verbose_name="Tenant",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TeamAuditHistory",
|
||||
fields=[
|
||||
(
|
||||
"centurionaudit_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionaudit",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this history belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="audit_history",
|
||||
to="access.team",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Team History",
|
||||
"verbose_name_plural": "Team Histories",
|
||||
"db_table": "access_team_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit", models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TeamCenturionModelNote",
|
||||
fields=[
|
||||
(
|
||||
"centurionmodelnote_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionmodelnote",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this note belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.team",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Team Note",
|
||||
"verbose_name_plural": "Team Notes",
|
||||
"db_table": "access_team_centurionmodelnote",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionmodelnote", models.Model),
|
||||
)
|
||||
]
|
@ -0,0 +1,102 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-06 01:43
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0011_remove_team_is_global_model_notes_and_more"),
|
||||
("core", "0028_delete_history"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="teamusers",
|
||||
name="model_notes",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="Tid bits of information",
|
||||
null=True,
|
||||
verbose_name="Notes",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="teamusers",
|
||||
name="id",
|
||||
field=models.AutoField(
|
||||
help_text="ID of the item",
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
unique=True,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TeamUsersAuditHistory",
|
||||
fields=[
|
||||
(
|
||||
"centurionaudit_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionaudit",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this history belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="audit_history",
|
||||
to="access.teamusers",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Team User History",
|
||||
"verbose_name_plural": "Team User Histories",
|
||||
"db_table": "access_teamusers_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit", models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TeamUsersCenturionModelNote",
|
||||
fields=[
|
||||
(
|
||||
"centurionmodelnote_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionmodelnote",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this note belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.teamusers",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Team User Note",
|
||||
"verbose_name_plural": "Team User Notes",
|
||||
"db_table": "access_teamusers_centurionmodelnote",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionmodelnote", models.Model),
|
||||
),
|
||||
]
|
16
app/access/migrations/0013_delete_teamusersaudithistory.py
Normal file
16
app/access/migrations/0013_delete_teamusersaudithistory.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-06 05:22
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="TeamUsersAuditHistory",
|
||||
),
|
||||
]
|
@ -0,0 +1,16 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-07 09:05
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0013_delete_teamusersaudithistory"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="TeamUsersCenturionModelNote",
|
||||
),
|
||||
]
|
@ -0,0 +1,75 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-07 10:10
|
||||
|
||||
import access.models.team
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0014_delete_teamuserscenturionmodelnote"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="teamcenturionmodelnote",
|
||||
name="centurionmodelnote_ptr",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="teamcenturionmodelnote",
|
||||
name="model",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="teamusers",
|
||||
name="model_notes",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="team",
|
||||
name="is_global",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Is this a global object?",
|
||||
verbose_name="Global Object",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="team",
|
||||
name="model_notes",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="Tid bits of information",
|
||||
null=True,
|
||||
verbose_name="Notes",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="team",
|
||||
name="organization",
|
||||
field=models.ForeignKey(
|
||||
help_text="Tenant this belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="access.tenant",
|
||||
validators=[access.models.team.Team.validatate_organization_exists],
|
||||
verbose_name="Tenant",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="teamusers",
|
||||
name="id",
|
||||
field=models.AutoField(
|
||||
help_text="ID of this Team User",
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
unique=True,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="TeamAuditHistory",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="TeamCenturionModelNote",
|
||||
),
|
||||
]
|
@ -0,0 +1,112 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-08 04:18
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"access",
|
||||
"0015_remove_teamcenturionmodelnote_centurionmodelnote_ptr_and_more",
|
||||
),
|
||||
("core", "0031_remove_ticketcategory_is_global_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="tenant",
|
||||
name="slug",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="tenant",
|
||||
name="manager",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Manager for this Tenancy",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Manager",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="tenant",
|
||||
name="model_notes",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="Tid bits of information",
|
||||
null=True,
|
||||
verbose_name="Notes",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TenantAuditHistory",
|
||||
fields=[
|
||||
(
|
||||
"centurionaudit_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionaudit",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this history belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="audit_history",
|
||||
to="access.tenant",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Tenant History",
|
||||
"verbose_name_plural": "Tenant Histories",
|
||||
"db_table": "access_tenant_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit", models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="TenantCenturionModelNote",
|
||||
fields=[
|
||||
(
|
||||
"centurionmodelnote_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionmodelnote",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this note belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.tenant",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Tenant Note",
|
||||
"verbose_name_plural": "Tenant Notes",
|
||||
"db_table": "access_tenant_centurionmodelnote",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionmodelnote", models.Model),
|
||||
),
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-17 07:27
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0016_remove_tenant_slug_alter_tenant_manager_and_more"),
|
||||
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name="EntityHistory",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="EntityNotes",
|
||||
),
|
||||
]
|
@ -0,0 +1,253 @@
|
||||
# Generated by Django 5.1.9 on 2025-06-17 07:32
|
||||
|
||||
import access.models.tenancy_abstract
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0017_remove_entitynotes_model_and_more"),
|
||||
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="entity",
|
||||
name="is_global",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="entity",
|
||||
name="id",
|
||||
field=models.AutoField(
|
||||
help_text="ID of the item",
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
unique=True,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="entity",
|
||||
name="model_notes",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="Tid bits of information",
|
||||
null=True,
|
||||
verbose_name="Notes",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="entity",
|
||||
name="organization",
|
||||
field=models.ForeignKey(
|
||||
help_text="Tenant this belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.tenant",
|
||||
validators=[
|
||||
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
|
||||
],
|
||||
verbose_name="Tenant",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ContactAuditHistory",
|
||||
fields=[
|
||||
(
|
||||
"centurionaudit_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionaudit",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this history belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.contact",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Contact History",
|
||||
"verbose_name_plural": "Contact Histories",
|
||||
"db_table": "access_contact_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="ContactCenturionModelNote",
|
||||
fields=[
|
||||
(
|
||||
"centurionmodelnote_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionmodelnote",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this note belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.contact",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Contact Note",
|
||||
"verbose_name_plural": "Contact Notes",
|
||||
"db_table": "access_contact_centurionmodelnote",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionmodelnote",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EntityAuditHistory",
|
||||
fields=[
|
||||
(
|
||||
"centurionaudit_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionaudit",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this history belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="audit_history",
|
||||
to="access.entity",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Entity History",
|
||||
"verbose_name_plural": "Entity Histories",
|
||||
"db_table": "access_entity_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EntityCenturionModelNote",
|
||||
fields=[
|
||||
(
|
||||
"centurionmodelnote_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionmodelnote",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this note belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.entity",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Entity Note",
|
||||
"verbose_name_plural": "Entity Notes",
|
||||
"db_table": "access_entity_centurionmodelnote",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionmodelnote",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PersonAuditHistory",
|
||||
fields=[
|
||||
(
|
||||
"centurionaudit_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionaudit",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this history belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.person",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Person History",
|
||||
"verbose_name_plural": "Person Histories",
|
||||
"db_table": "access_person_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PersonCenturionModelNote",
|
||||
fields=[
|
||||
(
|
||||
"centurionmodelnote_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="core.centurionmodelnote",
|
||||
),
|
||||
),
|
||||
(
|
||||
"model",
|
||||
models.ForeignKey(
|
||||
help_text="Model this note belongs to",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="+",
|
||||
to="access.person",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Person Note",
|
||||
"verbose_name_plural": "Person Notes",
|
||||
"db_table": "access_person_centurionmodelnote",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionmodelnote",),
|
||||
),
|
||||
]
|
@ -1,438 +0,0 @@
|
||||
|
||||
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
|
||||
|
||||
|
||||
class OrganizationMixin():
|
||||
"""Base Organization class"""
|
||||
|
||||
parent_model: str = None
|
||||
""" Parent Model
|
||||
|
||||
This attribute defines the parent model for the model in question. The parent model when defined
|
||||
will be used as the object to obtain the permissions from.
|
||||
"""
|
||||
|
||||
parent_model_pk_kwarg: str = 'pk'
|
||||
"""Parent Model kwarg
|
||||
|
||||
This value is used to define the kwarg that is used as the parent objects primary key (pk).
|
||||
"""
|
||||
|
||||
request = None
|
||||
|
||||
user_groups = []
|
||||
|
||||
|
||||
def get_parent_obj(self):
|
||||
""" Get the Parent Model Object
|
||||
|
||||
Use in views where the the model has no organization and the organization should be fetched from the parent model.
|
||||
|
||||
Requires attribute `parent_model` within the view with the value of the parent's model class
|
||||
|
||||
Returns:
|
||||
parent_model (Model): with PK from kwargs['pk']
|
||||
"""
|
||||
|
||||
return self.parent_model.objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
|
||||
|
||||
|
||||
def object_organization(self) -> int:
|
||||
|
||||
id = None
|
||||
|
||||
if hasattr(self, '_object_organization'):
|
||||
|
||||
return int(self._object_organization)
|
||||
|
||||
try:
|
||||
|
||||
if hasattr(self, 'get_queryset'):
|
||||
self.get_queryset()
|
||||
|
||||
|
||||
if self.parent_model:
|
||||
obj = self.get_parent_obj()
|
||||
|
||||
id = obj.get_organization().id
|
||||
|
||||
if obj.is_global:
|
||||
|
||||
id = 0
|
||||
|
||||
|
||||
if hasattr(self, 'get_object') and id is None:
|
||||
|
||||
obj = self.get_object()
|
||||
|
||||
id = obj.get_organization().id
|
||||
|
||||
if hasattr(obj, 'is_global'):
|
||||
|
||||
if obj.is_global:
|
||||
|
||||
id = 0
|
||||
|
||||
if hasattr(self, 'instance') and id is None: # Form Instance
|
||||
|
||||
id = self.instance.get_organization()
|
||||
|
||||
|
||||
except AttributeError:
|
||||
|
||||
if self.request.method == 'POST':
|
||||
|
||||
if self.request.POST.get("organization", ""):
|
||||
|
||||
id = int(self.request.POST.get("organization", ""))
|
||||
|
||||
for field in self.request.POST.dict(): # cater for fields prefixed '<prefix>-<field name>'
|
||||
|
||||
a_field = str(field).split('-')
|
||||
|
||||
if len(a_field) == 2:
|
||||
|
||||
if a_field[1] == 'organization':
|
||||
|
||||
id = int(self.request.POST.get(field))
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
if id is not None:
|
||||
|
||||
self._object_organization = id
|
||||
|
||||
|
||||
return id
|
||||
|
||||
|
||||
def is_member(self, organization: int) -> bool:
|
||||
"""Returns true if the current user is a member of the organization
|
||||
|
||||
iterates over the user_organizations list and returns true if the user is a member
|
||||
|
||||
Returns:
|
||||
bool: _description_
|
||||
"""
|
||||
|
||||
is_member = False
|
||||
|
||||
if organization is None:
|
||||
|
||||
return False
|
||||
|
||||
if int(organization) in self.user_organizations():
|
||||
|
||||
is_member = True
|
||||
|
||||
return is_member
|
||||
|
||||
|
||||
def get_permission_required(self):
|
||||
"""
|
||||
Override of 'PermissionRequiredMixin' method so that this mixin can obtain the required permission.
|
||||
"""
|
||||
|
||||
if not hasattr(self, 'permission_required'):
|
||||
|
||||
return []
|
||||
|
||||
if self.permission_required is None:
|
||||
raise ImproperlyConfigured(
|
||||
f"{self.__class__.__name__} is missing the "
|
||||
f"permission_required attribute. Define "
|
||||
f"{self.__class__.__name__}.permission_required, or override "
|
||||
f"{self.__class__.__name__}.get_permission_required()."
|
||||
)
|
||||
if isinstance(self.permission_required, str):
|
||||
perms = (self.permission_required,)
|
||||
else:
|
||||
perms = self.permission_required
|
||||
return perms
|
||||
|
||||
|
||||
@cached_property
|
||||
def is_manager(self) -> bool:
|
||||
""" Returns true if the current user is a member of the organization"""
|
||||
is_manager = False
|
||||
|
||||
return is_manager
|
||||
|
||||
|
||||
def user_organizations(self) -> list():
|
||||
"""Current Users organizations
|
||||
|
||||
Fetches the Organizations the user is apart of.
|
||||
|
||||
Get All groups the user is part of, fetch the associated team,
|
||||
iterate over the results adding the organization ID to a list to be returned.
|
||||
|
||||
Returns:
|
||||
_type_: User Organizations.
|
||||
"""
|
||||
|
||||
user_organizations = []
|
||||
|
||||
if hasattr(self, '_user_organizations'):
|
||||
|
||||
return self._user_organizations
|
||||
|
||||
teams = Team.objects
|
||||
|
||||
for group in self.request.user.groups.all():
|
||||
|
||||
team = teams.get(pk=group.id)
|
||||
|
||||
self.user_groups = self.user_groups + [group.id]
|
||||
|
||||
user_organizations = user_organizations + [team.organization.id]
|
||||
|
||||
if len(user_organizations) > 0:
|
||||
|
||||
self._user_organizations = user_organizations
|
||||
|
||||
|
||||
return user_organizations
|
||||
|
||||
|
||||
# ToDo: Ensure that the group has access to item
|
||||
def has_organization_permission(self, organization: int = None, permissions_required: list = None) -> bool:
|
||||
""" Check if user has permission within organization.
|
||||
|
||||
Args:
|
||||
organization (int, optional): Organization to check. Defaults to None.
|
||||
permissions_required (list, optional): if doing object level permissions, pass in required permission. Defaults to None.
|
||||
|
||||
Returns:
|
||||
bool: True for yes.
|
||||
"""
|
||||
|
||||
has_permission = False
|
||||
|
||||
if permissions_required is None:
|
||||
|
||||
permissions_required = self.get_permission_required()
|
||||
|
||||
if not organization:
|
||||
|
||||
organization = self.object_organization()
|
||||
|
||||
else:
|
||||
|
||||
organization = int(organization)
|
||||
|
||||
|
||||
if self.is_member(organization) or organization == 0:
|
||||
|
||||
groups = Group.objects.filter(pk__in=self.user_groups)
|
||||
|
||||
for group in groups:
|
||||
|
||||
team = Team.objects.filter(pk=group.id)
|
||||
team = team.values('organization_id').get()
|
||||
|
||||
for permission in group.permissions.values('content_type__app_label', 'codename').all():
|
||||
|
||||
assembled_permission = str(permission["content_type__app_label"]) + '.' + str(permission["codename"])
|
||||
|
||||
if assembled_permission in permissions_required and (team['organization_id'] == organization or organization == 0):
|
||||
|
||||
return True
|
||||
|
||||
return has_permission
|
||||
|
||||
|
||||
def permission_check(self, request, permissions_required: list = None) -> bool:
|
||||
|
||||
self.request = request
|
||||
|
||||
if permissions_required:
|
||||
|
||||
self.permission_required = permissions_required
|
||||
|
||||
organization_manager_models = [
|
||||
'access.organization',
|
||||
'access.team',
|
||||
'access.teamusers',
|
||||
]
|
||||
|
||||
is_organization_manager = False
|
||||
|
||||
queryset = None
|
||||
|
||||
if hasattr(self, 'get_queryset'):
|
||||
|
||||
queryset = self.get_queryset()
|
||||
|
||||
obj = None
|
||||
|
||||
if hasattr(self, 'get_object'):
|
||||
|
||||
|
||||
try:
|
||||
|
||||
obj = self.get_object()
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
if self.model._meta.label_lower in organization_manager_models:
|
||||
|
||||
organization = Organization.objects.get(pk=self.object_organization())
|
||||
|
||||
if organization.manager == request.user:
|
||||
|
||||
is_organization_manager = True
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if request.user.is_superuser:
|
||||
|
||||
return True
|
||||
|
||||
if permissions_required:
|
||||
|
||||
perms = permissions_required
|
||||
|
||||
else:
|
||||
|
||||
perms = self.get_permission_required()
|
||||
|
||||
if self.has_organization_permission(permissions_required = perms):
|
||||
|
||||
return True
|
||||
|
||||
if self.request.user.has_perms(perms) and str(self.request.method).lower() == 'get':
|
||||
|
||||
if len(self.kwargs) == 0 or (len(self.kwargs) == 1 and 'ticket_type' in self.kwargs):
|
||||
|
||||
return True
|
||||
|
||||
for required_permission in self.permission_required:
|
||||
|
||||
if required_permission.replace(
|
||||
'view_', ''
|
||||
) == 'access.organization' and len(self.kwargs) == 0:
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
class OrganizationPermission(AccessMixin, OrganizationMixin):
|
||||
"""## Permission Checking
|
||||
|
||||
The base django permissions have not been modified with this app providing Multi-Tenancy. This is done by a mixin, that checks if the item is apart of an organization, if it is; confirmation is made that the user is part of the same organization and as long as they have the correct permission within the organization, access is granted.
|
||||
|
||||
|
||||
### How it works
|
||||
|
||||
The overall permissions system of django has not been modified with it remaining fully functional. The multi-tenancy has been setup based off of an organization with teams. A team to the underlying django system is an extension of the django auth group and for every team created a django auth group is created. THe group name is set using the following format: `<organization>_<team name>` and contains underscores `_` instead of spaces.
|
||||
|
||||
A User who is added to an team as a "Manager" can modify the team members or if they have permission `access.change_team` which also allows the changing of team permissions. Modification of an organization can be done by the django administrator (super user) or any user with permission `access._change_organization`.
|
||||
|
||||
Items can be set as `Global`, meaning that all users who have the correct permission regardless of organization will be able to take action against the object.
|
||||
|
||||
Permissions that can be modified for a team have been limited to application permissions only unless adjust the permissions from the django admin site.
|
||||
|
||||
|
||||
### Multi-Tenancy workflow
|
||||
|
||||
The workflow is conducted as part of the view and has the following flow:
|
||||
|
||||
1. Checks if user is member of organization the object the action is being performed on. Will also return true if the object has field `is_global` set to `true`.
|
||||
|
||||
1. Fetches all teams the user is part of.
|
||||
|
||||
1. obtains all permissions that are linked to the team.
|
||||
|
||||
1. checks if user has the required permission for the action.
|
||||
|
||||
1. confirms that the team the permission came from is part of the same organization as the object the action is being conducted on.
|
||||
|
||||
1. ONLY on success of the above items, grants access.
|
||||
"""
|
||||
|
||||
permission_required: list = []
|
||||
""" Permission required for the view
|
||||
|
||||
Not specifying this property adjusts the permission check logic so that you can
|
||||
use the `permission_check()` function directly.
|
||||
|
||||
An example of a get request....
|
||||
|
||||
``` py
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not self.permission_check(request, [ 'access.view_organization' ]):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
```
|
||||
this example details manual usage of the `permission_check()` function for a get request.
|
||||
"""
|
||||
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
if len(self.permission_required) == 0:
|
||||
|
||||
if hasattr(self, 'get_dynamic_permissions'):
|
||||
|
||||
self.permission_required = self.get_dynamic_permissions()
|
||||
|
||||
if len(self.permission_required) > 0:
|
||||
|
||||
non_organization_models = [
|
||||
'TaskResult'
|
||||
]
|
||||
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
|
||||
if hasattr(self.model, '__name__'):
|
||||
|
||||
if self.model.__name__ in non_organization_models:
|
||||
|
||||
if hasattr(self, 'get_object'):
|
||||
|
||||
self.get_object()
|
||||
|
||||
perms = self.get_permission_required()
|
||||
|
||||
|
||||
if not self.request.user.has_perms(perms):
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
return super().dispatch(self.request, *args, **kwargs)
|
||||
|
||||
|
||||
if not self.permission_check(request):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().dispatch(self.request, *args, **kwargs)
|
@ -1,8 +1,10 @@
|
||||
from django.contrib.auth.models import User, Group
|
||||
import django
|
||||
|
||||
from django.db import models
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.tenant import Tenant as Organization
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
@ -89,7 +91,7 @@ class OrganizationMixin:
|
||||
|
||||
self._obj_organization = obj.organization
|
||||
|
||||
elif str(self.model._meta.verbose_name).lower() == 'organization':
|
||||
elif str(self.model._meta.verbose_name).lower() == 'tenant':
|
||||
|
||||
self._obj_organization = obj
|
||||
|
||||
@ -130,7 +132,7 @@ class OrganizationMixin:
|
||||
parent_model (Model): with PK from kwargs['pk']
|
||||
"""
|
||||
|
||||
return self.parent_model.objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
|
||||
return self.get_parent_model().objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
|
||||
|
||||
|
||||
|
||||
|
@ -1,23 +1,21 @@
|
||||
import traceback
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
|
||||
from access.models.tenancy import Organization, TenancyObject
|
||||
from access.models.tenancy import Tenant
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
from core.mixins.centurion import Centurion
|
||||
|
||||
|
||||
|
||||
class OrganizationPermissionMixin(
|
||||
DjangoObjectPermissions,
|
||||
):
|
||||
"""Organization Permission Mixin
|
||||
"""Tenant Permission Mixin
|
||||
|
||||
This class is to be used as the permission class for API `Views`/`ViewSets`.
|
||||
In combination with the `OrganizationPermissionsMixin`, permission checking
|
||||
In combination with the `TenantPermissionsMixin`, permission checking
|
||||
will be done to ensure the user has the correct permissions to perform the
|
||||
CRUD operation.
|
||||
|
||||
@ -60,11 +58,11 @@ class OrganizationPermissionMixin(
|
||||
|
||||
if hasattr(view, 'model'):
|
||||
|
||||
self._is_tenancy_model = issubclass(view.model, TenancyObject)
|
||||
self._is_tenancy_model = issubclass(view.model, Centurion)
|
||||
|
||||
if view.get_parent_model():
|
||||
|
||||
self._is_tenancy_model = issubclass(view.get_parent_model(), TenancyObject)
|
||||
self._is_tenancy_model = issubclass(view.get_parent_model(), Centurion)
|
||||
|
||||
return self._is_tenancy_model
|
||||
|
||||
@ -113,6 +111,12 @@ class OrganizationPermissionMixin(
|
||||
|
||||
raise centurion_exceptions.NotAuthenticated()
|
||||
|
||||
|
||||
if request.method not in view.allowed_methods:
|
||||
|
||||
raise centurion_exceptions.MethodNotAllowed(method = request.method)
|
||||
|
||||
|
||||
try:
|
||||
|
||||
if (
|
||||
@ -156,17 +160,12 @@ class OrganizationPermissionMixin(
|
||||
has_permission_required: bool = permission_required in user_permissions
|
||||
|
||||
|
||||
if request.method not in view.allowed_methods:
|
||||
|
||||
raise centurion_exceptions.MethodNotAllowed(method = request.method)
|
||||
|
||||
|
||||
elif not has_permission_required and not request.user.is_superuser:
|
||||
if not has_permission_required and not request.user.is_superuser:
|
||||
|
||||
raise centurion_exceptions.PermissionDenied()
|
||||
|
||||
|
||||
obj_organization: Organization = view.get_obj_organization(
|
||||
obj_organization: Tenant = view.get_obj_organization(
|
||||
request = request
|
||||
)
|
||||
|
||||
|
@ -1,2 +1,5 @@
|
||||
from . import contact
|
||||
from . import person
|
||||
from .organization_history import OrganizationHistory # pylint: disable=W0611:unused-import
|
||||
from .role_history import RoleHistory # pylint: disable=W0611:unused-import
|
||||
|
||||
from .organization_notes import OrganizationNotes # pylint: disable=W0611:unused-import
|
||||
from .role_notes import RoleNotes # pylint: disable=W0611:unused-import
|
||||
|
97
app/access/models/company_base.py
Normal file
97
app/access/models/company_base.py
Normal file
@ -0,0 +1,97 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
|
||||
|
||||
class Company(
|
||||
Entity
|
||||
):
|
||||
# This model is intended to be called `Organization`, however at the time of
|
||||
# creation this was not possible as Tenant (ne Organization) still has
|
||||
# references in code to `organization` witch clashes with the intended name of
|
||||
# this model.
|
||||
|
||||
_is_submodel = True
|
||||
|
||||
documentation = ''
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'name',
|
||||
]
|
||||
|
||||
sub_model_type = 'company'
|
||||
|
||||
verbose_name = 'Company'
|
||||
|
||||
verbose_name_plural = 'Companies'
|
||||
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'The name of this entity',
|
||||
max_length = 80,
|
||||
unique = False,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return self.name
|
||||
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'name',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tickets",
|
||||
"slug": "tickets",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "tickets",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
table_fields: list = [
|
||||
'name',
|
||||
'organization',
|
||||
'created',
|
||||
]
|
@ -8,6 +8,10 @@ class Contact(
|
||||
Person
|
||||
):
|
||||
|
||||
documentation = ''
|
||||
|
||||
_is_submodel = True
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -42,10 +46,6 @@ class Contact(
|
||||
|
||||
return self.f_name + ' ' + self.l_name
|
||||
|
||||
documentation = ''
|
||||
|
||||
history_model_name = 'contact'
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
|
@ -1,18 +1,23 @@
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
from access.fields import AutoLastModifiedField
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
from core.lib.feature_not_used import FeatureNotUsed
|
||||
from core.models.centurion import CenturionModel
|
||||
|
||||
|
||||
|
||||
class Entity(
|
||||
TenancyObject
|
||||
CenturionModel
|
||||
):
|
||||
|
||||
model_tag = 'entity'
|
||||
|
||||
documentation = ''
|
||||
|
||||
kb_model_name = 'entity'
|
||||
|
||||
url_model_name = 'entity'
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -29,31 +34,20 @@ class Entity(
|
||||
verbose_name_plural = 'Entities'
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'Primary key of the entry',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
entity_type = models.CharField(
|
||||
blank = False,
|
||||
default = Meta.verbose_name.lower(),
|
||||
help_text = 'Type this entity is',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Entity Type'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
@ -64,22 +58,8 @@ class Entity(
|
||||
return str( related_model )
|
||||
|
||||
|
||||
|
||||
# app_namespace = 'access'
|
||||
|
||||
history_app_label = 'access'
|
||||
|
||||
history_model_name = 'entity'
|
||||
|
||||
kb_model_name = 'entity'
|
||||
|
||||
note_basename = '_api_v2_entity_note'
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'entity_type',
|
||||
@ -89,6 +69,23 @@ class Entity(
|
||||
]
|
||||
|
||||
|
||||
|
||||
def clean_fields(self, exclude = None ):
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
if self.entity_type != str(related_model._meta.verbose_name).lower().replace(' ', '_'):
|
||||
|
||||
self.entity_type = str(related_model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
super().clean_fields( exclude = exclude )
|
||||
|
||||
|
||||
|
||||
def get_related_field_name(self) -> str:
|
||||
|
||||
meta = getattr(self, '_meta')
|
||||
@ -101,13 +98,12 @@ class Entity(
|
||||
|
||||
if getattr(self, related_object.name, None):
|
||||
|
||||
if(
|
||||
if(
|
||||
not str(related_object.name).endswith('history')
|
||||
and not str(related_object.name).endswith('notes')
|
||||
):
|
||||
|
||||
return related_object.name
|
||||
break
|
||||
|
||||
|
||||
return ''
|
||||
@ -145,105 +141,3 @@ class Entity(
|
||||
|
||||
|
||||
return related_model
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
if len(self._meta.parents) == 0 and model is None:
|
||||
|
||||
return {
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
kwargs = {
|
||||
'entity_model': str(model._meta.verbose_name).lower().replace(' ', '_'),
|
||||
}
|
||||
|
||||
if model.pk:
|
||||
|
||||
kwargs.update({
|
||||
'pk': model.id
|
||||
})
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
"""Fetch the models URL
|
||||
|
||||
If URL kwargs are required to generate the URL, define a `get_url_kwargs` that returns them.
|
||||
|
||||
Args:
|
||||
request (object, optional): The request object that was made by the end user. Defaults to None.
|
||||
|
||||
Returns:
|
||||
str: Canonical URL of the model if the `request` object was provided. Otherwise the relative URL.
|
||||
"""
|
||||
|
||||
model = None
|
||||
|
||||
if getattr(self, 'get_related_model', None):
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
|
||||
sub_entity = ''
|
||||
if model._meta.model_name != 'entity':
|
||||
|
||||
sub_entity = '_sub'
|
||||
|
||||
|
||||
kwargs = self.get_url_kwargs()
|
||||
|
||||
view = 'list'
|
||||
if 'pk' in kwargs:
|
||||
|
||||
view = 'detail'
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, request=request, kwargs = kwargs )
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, kwargs = kwargs )
|
||||
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
if self.entity_type != str(related_model._meta.verbose_name).lower().replace(' ', '_'):
|
||||
|
||||
self.entity_type = str(related_model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.entity_history import EntityHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = EntityHistory
|
||||
)
|
||||
|
||||
return history
|
||||
|
@ -1,155 +1 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.fields import (
|
||||
AutoCreatedField,
|
||||
AutoLastModifiedField,
|
||||
AutoSlugField
|
||||
)
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
|
||||
|
||||
class Organization(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Organization"
|
||||
verbose_name_plural = "Organizations"
|
||||
ordering = ['name']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if self.slug == '_':
|
||||
self.slug = self.name.lower().replace(' ', '_')
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of this item',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this Organization',
|
||||
max_length = 50,
|
||||
unique = True,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(
|
||||
User,
|
||||
blank = False,
|
||||
help_text = 'Manager for this organization',
|
||||
null = True,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name = 'Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'Tid bits of information',
|
||||
null= True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
slug = AutoSlugField()
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
def get_organization(self):
|
||||
return self
|
||||
|
||||
def __int__(self):
|
||||
|
||||
return self.id
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
table_fields: list = [
|
||||
'nbsp',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
'nbsp'
|
||||
]
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'name',
|
||||
'manager',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Teams",
|
||||
"slug": "teams",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "teams"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
|
||||
if request:
|
||||
|
||||
return reverse("v2:_api_v2_organization-detail", request=request, kwargs={'pk': self.id})
|
||||
|
||||
return reverse("v2:_api_v2_organization-detail", kwargs={'pk': self.id})
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.organization_history import OrganizationHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = OrganizationHistory
|
||||
)
|
||||
|
||||
|
||||
return history
|
||||
from .tenant import Tenant as Organization # pylint: disable=W0611:unused-import
|
||||
|
@ -2,7 +2,7 @@ from django.db import models
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ class OrganizationHistory(
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Organization,
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
@ -46,8 +46,8 @@ class OrganizationHistory(
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
model = OrganizationBaseSerializer(self.model, context = serializer_context)
|
||||
model = TenantBaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
@ -23,7 +23,7 @@ class OrganizationNotes(
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Organization,
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
|
@ -10,6 +10,10 @@ class Person(
|
||||
Entity
|
||||
):
|
||||
|
||||
_is_submodel = True
|
||||
|
||||
documentation = ''
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -36,7 +40,6 @@ class Person(
|
||||
|
||||
m_name = models.CharField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'The persons middle name(s)',
|
||||
max_length = 100,
|
||||
null = True,
|
||||
@ -54,7 +57,6 @@ class Person(
|
||||
|
||||
dob = models.DateField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'The Persons Date of Birth (DOB)',
|
||||
null = True,
|
||||
unique = False,
|
||||
@ -65,10 +67,6 @@ class Person(
|
||||
|
||||
return self.f_name + ' ' + self.l_name + f' (DOB: {self.dob})'
|
||||
|
||||
documentation = ''
|
||||
|
||||
history_model_name = 'person'
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
table_fields: list = [
|
||||
@ -104,7 +102,7 @@ class Person(
|
||||
|
||||
|
||||
for entry in duplicate_entry:
|
||||
|
||||
|
||||
if(
|
||||
entry.f_name == self.f_name
|
||||
and entry.m_name == self.m_name
|
||||
@ -114,8 +112,8 @@ class Person(
|
||||
|
||||
raise ValidationError(
|
||||
detail = {
|
||||
'dob': f'Person {self.f_name} {self.l_name} already exists with this birthday {entry.dob}'
|
||||
'dob': f'Person {self.f_name} {self.l_name}' \
|
||||
f'already exists with this birthday {entry.dob}'
|
||||
},
|
||||
code = 'duplicate_person_on_dob'
|
||||
)
|
||||
|
||||
|
@ -130,6 +130,37 @@ class Role(
|
||||
]
|
||||
|
||||
|
||||
_permissions: list[ Permission ] = None
|
||||
|
||||
_permissions_int: list[ int ] = None
|
||||
|
||||
def get_permissions(self, as_int_list = False ):
|
||||
|
||||
if self._permissions is None:
|
||||
|
||||
permissions = []
|
||||
permissions_int = []
|
||||
|
||||
for permission in self.permissions: # pylint: disable=E1133:not-an-iterable
|
||||
|
||||
if permission in _permissions:
|
||||
continue
|
||||
|
||||
permissions += [ permission ]
|
||||
permissions_int += [ permission.id ]
|
||||
|
||||
self._permissions = permissions
|
||||
self._permissions_int = permissions_int
|
||||
|
||||
if as_int_list:
|
||||
return self._permissions_int
|
||||
|
||||
return self._permissions_int
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.role_history import RoleHistory
|
||||
|
@ -8,7 +8,7 @@ from access.fields import (
|
||||
AutoLastModifiedField
|
||||
)
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
@ -55,13 +55,13 @@ class Team(Group, TenancyObject):
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Organization this belongs to',
|
||||
help_text = 'Tenant this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Organization'
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
@ -179,12 +179,12 @@ class Team(Group, TenancyObject):
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.team_history import TeamHistory
|
||||
from access.models.team_history import TeamAuditHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = TeamHistory
|
||||
history_model = TeamAuditHistory
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import django
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
@ -9,11 +11,13 @@ from access.fields import (
|
||||
AutoLastModifiedField
|
||||
)
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
from access.models.team import Team
|
||||
|
||||
from core.lib.feature_not_used import FeatureNotUsed
|
||||
from core.mixin.history_save import SaveHistory
|
||||
from core.mixins.history_save import SaveHistory
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
@ -95,7 +99,7 @@ class TeamUsers(SaveHistory):
|
||||
user.groups.remove(group)
|
||||
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
def get_organization(self) -> Tenant:
|
||||
return self.team.organization
|
||||
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
# from django.conf import settings
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
# from django.contrib.auth.models import User, Group
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
# from .fields import *
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
from core.middleware.get_request import get_request
|
||||
from core.mixin.history_save import SaveHistory
|
||||
from core.mixins.history_save import SaveHistory
|
||||
|
||||
|
||||
|
||||
@ -53,6 +51,10 @@ class TenancyManager(models.Manager):
|
||||
|
||||
user_organizations: list(str()) = []
|
||||
|
||||
has_tenant_field = False
|
||||
if getattr(self.model, 'organization', None) is not None:
|
||||
has_tenant_field = True
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
@ -71,29 +73,33 @@ class TenancyManager(models.Manager):
|
||||
|
||||
if team.organization.id not in user_organizations:
|
||||
|
||||
if not user_organizations:
|
||||
# if not user_organizations:
|
||||
|
||||
self.user_organizations = []
|
||||
# self.user_organizations = []
|
||||
|
||||
user_organizations += [ team.organization.id ]
|
||||
|
||||
|
||||
# if len(user_organizations) > 0 and not user.is_superuser and self.model.is_global is not None:
|
||||
if len(user_organizations) > 0 and not user.is_superuser:
|
||||
|
||||
if getattr(self.model, 'is_global', False) is True:
|
||||
if has_tenant_field:
|
||||
|
||||
return super().get_queryset().filter(
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
|
|
||||
models.Q(is_global = True)
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
return super().get_queryset().filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
)
|
||||
# return super().get_queryset().filter(
|
||||
# models.Q(organization__in=user_organizations)
|
||||
# )
|
||||
|
||||
return super().get_queryset().filter()
|
||||
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
@ -137,14 +143,14 @@ class TenancyObject(SaveHistory):
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Organization this belongs to',
|
||||
help_text = 'Tenancy this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = '+',
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Organization'
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
is_global = models.BooleanField(
|
||||
@ -162,7 +168,7 @@ class TenancyObject(SaveHistory):
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
def get_organization(self) -> Tenant:
|
||||
return self.organization
|
||||
|
||||
app_namespace: str = None
|
||||
@ -193,6 +199,16 @@ class TenancyObject(SaveHistory):
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
_log: logging.Logger = None
|
||||
|
||||
def get_log(self):
|
||||
|
||||
if self._log is None:
|
||||
|
||||
self._log = logging.getLogger('centurion.' + self._meta.app_label)
|
||||
|
||||
return self._log
|
||||
|
||||
page_layout: list = None
|
||||
|
||||
note_basename: str = None
|
||||
@ -220,7 +236,7 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
if self.app_namespace:
|
||||
|
||||
app_namespace = self.app_namespace + ':'
|
||||
app_namespace = self.app_namespace
|
||||
|
||||
return str(app_namespace)
|
||||
|
||||
@ -239,12 +255,17 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
model_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
|
||||
|
||||
namespace = f'v2'
|
||||
|
||||
if self.get_app_namespace():
|
||||
namespace = namespace + ':' + self.get_app_namespace()
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:" + self.get_app_namespace() + f"_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
return reverse(f"{namespace}:_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
|
||||
return reverse(f"v2:" + self.get_app_namespace() + f"_api_v2_{model_name}-detail", kwargs = self.get_url_kwargs() )
|
||||
return reverse(f"{namespace}:_api_v2_{model_name}-detail", kwargs = self.get_url_kwargs() )
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
@ -282,7 +303,7 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
raise centurion_exceptions.ValidationError(
|
||||
detail = {
|
||||
'organization': 'Organization is required'
|
||||
'organization': 'Tenant is required'
|
||||
},
|
||||
code = 'required'
|
||||
)
|
||||
|
126
app/access/models/tenancy_abstract.py
Normal file
126
app/access/models/tenancy_abstract.py
Normal file
@ -0,0 +1,126 @@
|
||||
from django.core.exceptions import (
|
||||
ValidationError,
|
||||
)
|
||||
from django.db import models
|
||||
|
||||
from access.models.tenancy import (
|
||||
TenancyManager as TenancyManagerDepreciated
|
||||
)
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
class TenancyManager(
|
||||
models.Manager
|
||||
):
|
||||
"""Multi-Tennant Object Manager
|
||||
|
||||
This manager specifically caters for the multi-tenancy features of Centurion ERP.
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
""" Fetch the data
|
||||
|
||||
When the model contains the user data, the query is filtered to their
|
||||
and the globally defined Tenancy only.
|
||||
|
||||
Returns:
|
||||
(queryset): **super user**: return unfiltered data.
|
||||
(queryset): **not super user**: return data from the stored unique organizations.
|
||||
"""
|
||||
|
||||
user = None # When CenturionUser in use
|
||||
|
||||
if hasattr(self.model, 'context'):
|
||||
|
||||
user = self.model.context['user']
|
||||
|
||||
|
||||
has_tenant_field = False
|
||||
if getattr(self.model, 'organization', None) is not None:
|
||||
has_tenant_field = True
|
||||
|
||||
|
||||
if user:
|
||||
|
||||
tenancies = user.get_tenancies(int_list = True)
|
||||
|
||||
if len(tenancies) > 0 and not request.user.is_superuser:
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in = tenancies)
|
||||
)
|
||||
|
||||
|
||||
return super().get_queryset().filter()
|
||||
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
|
||||
class TenancyAbstractModel(
|
||||
models.Model,
|
||||
):
|
||||
""" Tenancy Model Abstract class.
|
||||
|
||||
This class is for inclusion within **every** model within Centurion ERP.
|
||||
Provides the required fields, functions and methods for multi tennant objects.
|
||||
Unless otherwise stated, **no** object within this class may be overridden.
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization
|
||||
"""
|
||||
|
||||
objects = TenancyManagerDepreciated()
|
||||
""" ~~Multi-Tenant Manager~~
|
||||
|
||||
**Note:** ~~This manager relies upon the model class having `context['user']`
|
||||
set. without a user the manager can not perform multi-tenant queries.~~
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
def validatate_organization_exists(self):
|
||||
"""Ensure that the user did provide an organization
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
raise ValidationError(
|
||||
code = 'required',
|
||||
message = 'You must provide an organization'
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Tenant this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = '+',
|
||||
validators = [
|
||||
validatate_organization_exists
|
||||
],
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
|
||||
|
||||
def get_tenant(self) -> Tenant:
|
||||
""" Return the models Tenancy
|
||||
|
||||
This model can be safely over-ridden as long as it returns the models
|
||||
tenancy
|
||||
"""
|
||||
return self.organization
|
143
app/access/models/tenant.py
Normal file
143
app/access/models/tenant.py
Normal file
@ -0,0 +1,143 @@
|
||||
import django
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from access.fields import (
|
||||
AutoCreatedField,
|
||||
AutoLastModifiedField,
|
||||
)
|
||||
|
||||
from core.mixins.centurion import Centurion
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
class Tenant(
|
||||
Centurion,
|
||||
):
|
||||
|
||||
@property
|
||||
def organization(self):
|
||||
return self
|
||||
|
||||
model_tag = 'tenant'
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = "Tenant"
|
||||
|
||||
verbose_name_plural = "Tenants"
|
||||
|
||||
ordering = [
|
||||
'name'
|
||||
]
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank = False,
|
||||
help_text = 'ID of this item',
|
||||
primary_key = True,
|
||||
unique = True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this Tenancy',
|
||||
max_length = 50,
|
||||
unique = True,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank = True,
|
||||
help_text = 'Manager for this Tenancy',
|
||||
null = True,
|
||||
on_delete = models.PROTECT,
|
||||
verbose_name = 'Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
help_text = 'Tid bits of information',
|
||||
null = True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
def __int__(self):
|
||||
|
||||
return self.id
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
def get_organization(self):
|
||||
return self
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'nbsp',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
'nbsp'
|
||||
]
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'name',
|
||||
'manager',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Teams",
|
||||
"slug": "teams",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "teams"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
Organization = Tenant
|
197
app/access/models/user_proxy.py
Normal file
197
app/access/models/user_proxy.py
Normal file
@ -0,0 +1,197 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
class CenturionUser(
|
||||
User,
|
||||
):
|
||||
"""Centurion User
|
||||
|
||||
A Multi-Tenant User wirh permission Checking.
|
||||
|
||||
ToDo:
|
||||
- Add to Roles user field `related_name = roles`
|
||||
- Add to Roles group field `related_name = roles`
|
||||
# - have group lookup prefetch related roles__permissions
|
||||
- have user lookup prefetch related roles__permissions and groups__roles__permissions
|
||||
|
||||
Args:
|
||||
User (Model): Django Base User
|
||||
"""
|
||||
|
||||
_tenancies: list[Tenant] = None
|
||||
|
||||
_tenancies_int: list[int] = None
|
||||
|
||||
_permissions: list[Permission] = None
|
||||
|
||||
_permissions_by_tenancy: dict[ str, list[ Permission ] ] = None
|
||||
"""Permissions by Tenancy
|
||||
|
||||
`{ 'tenancy_{id}': [ Permission ] }`
|
||||
"""
|
||||
|
||||
# EMAIL_FIELD = 'email' # Update contact email field name so it's different to the user model.
|
||||
|
||||
# REQUIRED_FIELDS = [
|
||||
# EMAIL_FIELD,
|
||||
# 'f_name',
|
||||
# 'l_name',
|
||||
# ]
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
proxy = True # User will be linked to Employee/Customer entity via related_name from the entity.
|
||||
# ToDo: refactory Employee/Customer to inherit from a new model. entity_user
|
||||
|
||||
verbose_name = 'Centurion User'
|
||||
|
||||
verbose_name_plural = 'Centurion Users'
|
||||
|
||||
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
return f'{self.entity_user.f_name} {self.entity_user.l_name}'
|
||||
|
||||
|
||||
|
||||
def get_group_permissions(self, tenancy: bool = True) -> dict[ str, list[ Permission ] ] | list[ Permission ]:
|
||||
""" Get the Users Permissions
|
||||
|
||||
Args:
|
||||
tenancy (bool, optional): Return permission in list. Defaults to True.
|
||||
|
||||
Returns:
|
||||
dict[ str, list[ Permission ] ]: Permissions listed by tenancy
|
||||
list[ Permission ]: All Permissions
|
||||
"""
|
||||
|
||||
for group in self.groups: # pylint: disable=E1133:not-an-iterable
|
||||
|
||||
for role in group.roles:
|
||||
pass
|
||||
|
||||
# role.get_permissions()
|
||||
|
||||
|
||||
|
||||
def get_permissions(self, tenancy: bool = True) -> dict[ str, list[ Permission ] ] | list[ Permission ]:
|
||||
""" Get the Users Permissions
|
||||
|
||||
Args:
|
||||
tenancy (bool, optional): Return permission in list. Defaults to True.
|
||||
|
||||
Returns:
|
||||
dict[ str, list[ Permission ] ]: Permissions listed by tenancy
|
||||
list[ Permission ]: All Permissions
|
||||
"""
|
||||
|
||||
# also get group permissions. self.get_group_permissions()
|
||||
|
||||
for role in self.roles:
|
||||
pass
|
||||
|
||||
# role.get_permissions()
|
||||
|
||||
# also populate `self._tenancies` and `self._tenancies_int`
|
||||
|
||||
return []
|
||||
|
||||
|
||||
|
||||
def get_short_name() -> str:
|
||||
return self.entity_user.f_name
|
||||
|
||||
|
||||
|
||||
def get_tenancies(self, int_list = False) -> list[ Tenant ] | list[ int ]:
|
||||
"""Get the Tenancies the user is in.
|
||||
|
||||
Args:
|
||||
int_list (bool, optional): Return Tenancy list as int values. Defaults to False.
|
||||
|
||||
Returns:
|
||||
list[ Tenant ] | list[ int ]: All Tenancies the user is in.
|
||||
"""
|
||||
|
||||
if self._tenancies is None:
|
||||
|
||||
if self._permissions is None:
|
||||
self.get_permissions
|
||||
|
||||
tenancies: list = []
|
||||
tenancies_int: list = []
|
||||
|
||||
for role in self.roles:
|
||||
|
||||
if role.organization in tenancies:
|
||||
continue
|
||||
|
||||
tenancies += [ role.organization ]
|
||||
tenancies_int += [ role.organization.id ]
|
||||
|
||||
self._tenancies = tenancies
|
||||
self._tenancies_int = tenancies_int
|
||||
|
||||
|
||||
if as_int_list:
|
||||
return self._tenancies_int
|
||||
|
||||
return self._tenancies
|
||||
|
||||
|
||||
|
||||
def has_module_perms(self, app_label): # is this needed?
|
||||
|
||||
# if has app_label in perms
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
|
||||
def has_perm(self, permission: Permission, obj = None, tenancy: Tenant = None) -> bool:
|
||||
|
||||
if(
|
||||
obj is None
|
||||
and tenancy is None
|
||||
):
|
||||
raise ValueError('Both obj and tenancy cant be None')
|
||||
|
||||
if tenancy is None:
|
||||
tenancy = obj.organization
|
||||
|
||||
# if self.has_tenancy_permission(perm, tenancy):
|
||||
# for tenancy, permissions in self.get_permissions().items()
|
||||
|
||||
if tenancy is None:
|
||||
raise ValueError('tenancy cant be None')
|
||||
|
||||
permissions = self.get_permissions()
|
||||
|
||||
if f'tenancy_{tenancy.id}' not in permissions:
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
for tenancy, permissions in self.get_permissions().items():
|
||||
|
||||
if(
|
||||
tenancy == f'tenancy_{tenancy.id}'
|
||||
and perm in permissions
|
||||
):
|
||||
return True
|
||||
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
|
||||
def has_perms(self, permission_list: list[ Permission ], obj = None, tenancy: Tenant = None):
|
||||
|
||||
for perm in perm_list:
|
||||
|
||||
self.has_perm( perm, obj )
|
||||
|
||||
return True
|
56
app/access/serializers/centurionaudit_company.py
Normal file
56
app/access/serializers/centurionaudit_company.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import CompanyAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = CompanyAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_contact.py
Normal file
56
app/access/serializers/centurionaudit_contact.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import ContactAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ContactAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_entity.py
Normal file
56
app/access/serializers/centurionaudit_entity.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import EntityAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_person.py
Normal file
56
app/access/serializers/centurionaudit_person.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import PersonAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PersonAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_tenant.py
Normal file
56
app/access/serializers/centurionaudit_tenant.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import TenantAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TenantAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TenantAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TenantAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
87
app/access/serializers/centurionmodelnote_company.py
Normal file
87
app/access/serializers/centurionmodelnote_company.py
Normal file
@ -0,0 +1,87 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import CompanyCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = CompanyCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
87
app/access/serializers/centurionmodelnote_contact.py
Normal file
87
app/access/serializers/centurionmodelnote_contact.py
Normal file
@ -0,0 +1,87 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import ContactCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ContactCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
87
app/access/serializers/centurionmodelnote_entity.py
Normal file
87
app/access/serializers/centurionmodelnote_entity.py
Normal file
@ -0,0 +1,87 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import EntityCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
87
app/access/serializers/centurionmodelnote_person.py
Normal file
87
app/access/serializers/centurionmodelnote_person.py
Normal file
@ -0,0 +1,87 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import PersonCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PersonCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
87
app/access/serializers/centurionmodelnote_tenant.py
Normal file
87
app/access/serializers/centurionmodelnote_tenant.py
Normal file
@ -0,0 +1,87 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import TenantCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TeamModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TenantCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TeamModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
@ -6,7 +6,7 @@ from access.models.entity import Entity
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -66,7 +66,6 @@ class ModelSerializer(
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
@ -87,4 +86,4 @@ class ModelSerializer(
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Entity Base View Model"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
69
app/access/serializers/entity_company.py
Normal file
69
app/access/serializers/entity_company.py
Normal file
@ -0,0 +1,69 @@
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.company_base import Company
|
||||
|
||||
from access.serializers.entity import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
class BaseSerializer(
|
||||
BaseBaseSerializer,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyEntityModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseSerializer,
|
||||
BaseModelSerializer,
|
||||
):
|
||||
"""Company Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Company
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'entity_ptr_id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'name',
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Company View Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
@ -6,7 +6,7 @@ from access.serializers.entity_person import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -46,7 +46,6 @@ class ModelSerializer(
|
||||
'email',
|
||||
'directory',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
@ -72,4 +71,4 @@ class ViewSerializer(
|
||||
This model inherits from the Person model.
|
||||
"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
@ -1,41 +0,0 @@
|
||||
from core.serializers.model_notes import (
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
from access.models.entity_notes import EntityNotes
|
||||
|
||||
|
||||
|
||||
class EntityNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EntityNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class EntityNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
EntityNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -6,7 +6,7 @@ from access.serializers.entity import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -44,7 +44,6 @@ class ModelSerializer(
|
||||
'l_name',
|
||||
'dob',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
@ -70,4 +69,4 @@ class ViewSerializer(
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
@ -2,15 +2,16 @@ from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
from centurion.serializers.user import UserBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
Organization = Tenant
|
||||
|
||||
|
||||
class OrganizationBaseSerializer(serializers.ModelSerializer):
|
||||
class TenantBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
@ -19,12 +20,12 @@ class OrganizationBaseSerializer(serializers.ModelSerializer):
|
||||
return str( item )
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v2:_api_v2_organization-detail", format="html"
|
||||
view_name="v2:_api_tenant-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Organization
|
||||
model = Tenant
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
@ -42,8 +43,8 @@ class OrganizationBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
|
||||
class OrganizationModelSerializer(
|
||||
OrganizationBaseSerializer
|
||||
class TenantModelSerializer(
|
||||
TenantBaseSerializer
|
||||
):
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
@ -60,13 +61,13 @@ class OrganizationModelSerializer(
|
||||
'model_pk': item.pk
|
||||
}
|
||||
),
|
||||
'notes': reverse(
|
||||
"v2:_api_v2_organization_note-list",
|
||||
request=self._context['view'].request,
|
||||
kwargs={
|
||||
'model_id': item.pk
|
||||
}
|
||||
),
|
||||
# 'notes': reverse(
|
||||
# "v2:_api_v2_organization_note-list",
|
||||
# request=self._context['view'].request,
|
||||
# kwargs={
|
||||
# 'model_id': item.pk
|
||||
# }
|
||||
# ),
|
||||
'teams': reverse("v2:_api_v2_organization_team-list", request=self._context['view'].request, kwargs={'organization_id': item.pk}),
|
||||
}
|
||||
|
||||
@ -74,7 +75,7 @@ class OrganizationModelSerializer(
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Organization
|
||||
model = Tenant
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
@ -98,7 +99,7 @@ class OrganizationModelSerializer(
|
||||
]
|
||||
|
||||
|
||||
class OrganizationViewSerializer(OrganizationModelSerializer):
|
||||
class TenantViewSerializer(TenantModelSerializer):
|
||||
pass
|
||||
|
||||
manager = UserBaseSerializer(many=False, read_only = True)
|
||||
|
@ -1,48 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.organization_notes import OrganizationNotes
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNotes,
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class OrganizationNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = OrganizationNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class OrganizationNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
OrganizationNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -5,11 +5,11 @@ from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.functions.permissions import permission_queryset
|
||||
from access.models.role import Role
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.permission import PermissionBaseSerializer
|
||||
from centurion.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -109,6 +109,6 @@ class ModelSerializer(
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Role Base View Model"""
|
||||
|
||||
organization = OrganizationBaseSerializer( many=False, read_only=True )
|
||||
organization = TenantBaseSerializer( many=False, read_only=True )
|
||||
|
||||
permissions = PermissionBaseSerializer( many=True, read_only=True )
|
||||
|
@ -1,48 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.role_notes import RoleNotes
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNotes,
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class RoleNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RoleNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = RoleNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class RoleNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
RoleNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -1,48 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.team_notes import TeamNotes
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNotes,
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TeamNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TeamNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TeamNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
TeamNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -1,12 +1,10 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
from centurion.serializers.user import UserBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -55,7 +53,7 @@ class TeamUserModelSerializer(
|
||||
|
||||
get_url = super().get_url( item = item )
|
||||
|
||||
del get_url['history']
|
||||
# del get_url['history']
|
||||
|
||||
del get_url['knowledge_base']
|
||||
|
||||
|
@ -7,9 +7,9 @@ from access.models.team import Team
|
||||
from api.serializers import common
|
||||
|
||||
from access.functions.permissions import permission_queryset
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
from app.serializers.permission import Permission, PermissionBaseSerializer
|
||||
from centurion.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
@ -94,7 +94,6 @@ class TeamModelSerializer(
|
||||
'model_notes',
|
||||
'permissions',
|
||||
'organization',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
@ -127,6 +126,6 @@ class TeamModelSerializer(
|
||||
|
||||
class TeamViewSerializer(TeamModelSerializer):
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
||||
permissions = PermissionBaseSerializer(many = True)
|
||||
|
@ -1,22 +0,0 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block content_header_icon %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
</tr>
|
||||
{% for org in organization_list %}
|
||||
<tr>
|
||||
<td><a href="/organization/{{ org.id }}/">{{ org.name }}</a></td>
|
||||
<td>{{ org.created }}</td>
|
||||
<td>{{ org.modified }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endblock %}
|
@ -1,106 +0,0 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}Organization - {{ organization.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
form div .helptext {
|
||||
background-color: rgb(0, 140, 255);
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.detail-view-field {
|
||||
display:unset;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0px 20px 40px 20px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field label {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
width: 200px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field span {
|
||||
display: inline-block;
|
||||
width: 340px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
||||
<div style="display: inline; width: 40%; margin: 30px;">
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.name.label }}</label>
|
||||
<span>{{ form.name.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.manager.label }}</label>
|
||||
<span>{{ organization.manager }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.created.label }}</label>
|
||||
<span>{{ form.created.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.modified.label }}</label>
|
||||
<span>{{ form.modified.value }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
|
||||
<div>
|
||||
<label style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">{{ form.model_notes.label }}</label>
|
||||
|
||||
<div style="display: inline-block; text-align: left;">{{ form.model_notes.value | markdown | safe }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: block;">
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:Organizations' %}';">
|
||||
<input type="button" value="New Team" onclick="window.location='{% url 'Access:_team_add' organization.id %}';">
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Team Name</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for field in teams %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Access:_team_view' organization_id=organization.id pk=field.id %}">{{ field.team_name }}</a></td>
|
||||
<td>{{ field.created }}</td>
|
||||
<td>{{ field.modified }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
@ -1,48 +0,0 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}Team - {{ team.team_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form.as_div }}
|
||||
|
||||
<input style="display:unset;" type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:_organization_view' pk=organization.id %}';">
|
||||
<input type="button" value="Delete Team"
|
||||
onclick="window.location='{% url 'Access:_team_delete' organization_id=organization.id pk=team.id %}';">
|
||||
<input type="button" value="Assign User"
|
||||
onclick="window.location='{% url 'Access:_team_user_add' organization_id=organization.id pk=team.id %}';">
|
||||
{{ formset.management_form }}
|
||||
|
||||
<table id="formset" class="form">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Manager</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for field in teamusers %}
|
||||
<tr>
|
||||
<td>{{ field.user }}</td>
|
||||
<td><input type="checkbox" {% if field.manager %}checked{% endif %} disabled></td>
|
||||
<td>{{ field.created }}</td>
|
||||
<td>{{ field.modified }}</td>
|
||||
<td><a
|
||||
href="{% url 'Access:_team_user_delete' organization_id=organization.id team_id=field.team_id pk=field.id %}">Delete</a></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
@ -1,32 +0,0 @@
|
||||
|
||||
|
||||
|
||||
class TenancyObject:
|
||||
""" Tests for checking TenancyObject """
|
||||
|
||||
model = None
|
||||
""" Model to be tested """
|
||||
|
||||
should_model_history_be_saved: bool = True
|
||||
""" Should model history be saved.
|
||||
|
||||
By default this should always be 'True', however in special
|
||||
circumstances, this may not be desired.
|
||||
"""
|
||||
|
||||
|
||||
# def test_history_save(self):
|
||||
# """Confirm the desired intent for saving model history."""
|
||||
|
||||
# assert self.model.save_model_history == self.should_model_history_be_saved
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_edit_no_organization_fails(self):
|
||||
# """ Devices must be assigned an organization
|
||||
|
||||
# Must not be able to edit an item without an organization
|
||||
# """
|
||||
# pass
|
||||
|
24
app/access/tests/functional/company/conftest.py
Normal file
24
app/access/tests/functional/company/conftest.py
Normal file
@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from access.models.company_base import Company
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Company
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity_company import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
@ -0,0 +1,72 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.company_base import Company
|
||||
from access.tests.functional.entity.test_functional_entity_metadata import (
|
||||
EntityMetadataInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class CompanyMetadataTestCases(
|
||||
EntityMetadataInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'name': 'Ian1'
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'name': 'Ian2',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'name': 'Ian3',
|
||||
}
|
||||
|
||||
model = Company
|
||||
|
||||
|
||||
|
||||
|
||||
class CompanyMetadataInheritedCases(
|
||||
CompanyMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
# self.url_kwargs = {
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
# self.url_view_kwargs = {
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class CompanyMetadataTest(
|
||||
CompanyMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
pass
|
@ -0,0 +1,43 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_permission import (
|
||||
EntityPermissionsAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class CompanyPermissionsAPITestCases(
|
||||
EntityPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'name': 'Ian1',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'name': 'Ian2',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'name': 'Ian3',
|
||||
}
|
||||
|
||||
|
||||
|
||||
class CompanyPermissionsAPIInheritedCases(
|
||||
CompanyPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
|
||||
class CompanyPermissionsAPIPyTest(
|
||||
CompanyPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,46 @@
|
||||
import pytest
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_serializer import (
|
||||
MockView,
|
||||
EntitySerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class CompanySerializerTestCases(
|
||||
EntitySerializerInheritedCases
|
||||
):
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"name": {
|
||||
'will_create': False,
|
||||
'exception_key': 'required'
|
||||
},
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'name': 'Ian',
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class CompanySerializerInheritedCases(
|
||||
CompanySerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class CompanySerializerPyTest(
|
||||
CompanySerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
@ -0,0 +1,58 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.company_base import Company
|
||||
from access.tests.functional.entity.test_functional_entity_viewset import (
|
||||
EntityViewSetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
EntityViewSetInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'name': 'Ian',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'name': 'Ian2',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'name': 'Ian3',
|
||||
}
|
||||
|
||||
model = Company
|
||||
|
||||
|
||||
|
||||
class CompanyViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class CompanyViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
pass
|
24
app/access/tests/functional/contact/conftest.py
Normal file
24
app/access/tests/functional/contact/conftest.py
Normal file
@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Contact
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity_contact import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
@ -1,60 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.functional.person.test_functional_person_history import (
|
||||
PersonHistoryInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactTestCases(
|
||||
PersonHistoryInheritedCases,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
|
||||
|
||||
class ContactHistoryInheritedCases(
|
||||
ContactTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactHistoryTest(
|
||||
ContactTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,65 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
from access.tests.functional.person.test_functional_person_metadata import (
|
||||
PersonMetadataInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactMetadataTestCases(
|
||||
PersonMetadataInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'email': 'ipstrange@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
|
||||
|
||||
|
||||
class ContactMetadataInheritedCases(
|
||||
ContactMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactMetadataTest(
|
||||
ContactMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
pass
|
@ -0,0 +1,70 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.person.test_functional_person_permission import (
|
||||
PersonPermissionsAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPITestCases(
|
||||
PersonPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'email': 'ipstrange@unit.test',
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPIInheritedCases(
|
||||
ContactPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
# url_name = '_api_entity_sub'
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class')
|
||||
# def inherited_var_setup(self, request):
|
||||
|
||||
# request.cls.url_kwargs.update({
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
# request.cls.url_view_kwargs.update({
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class', autouse = True)
|
||||
# def class_setup(self, request, django_db_blocker,
|
||||
# model,
|
||||
# var_setup,
|
||||
# prepare,
|
||||
# inherited_var_setup,
|
||||
# diff_org_model,
|
||||
# create_model,
|
||||
# ):
|
||||
|
||||
# pass
|
||||
|
||||
|
||||
class ContactPermissionsAPIPyTest(
|
||||
ContactPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,129 +1,46 @@
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.serializers.entity_contact import (
|
||||
Contact,
|
||||
ModelSerializer
|
||||
)
|
||||
from access.tests.functional.person.test_functional_person_serializer import (
|
||||
MockView,
|
||||
PersonSerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class SerializerTestCases(
|
||||
PersonSerializerInheritedCases,
|
||||
class ContactSerializerTestCases(
|
||||
PersonSerializerInheritedCases
|
||||
):
|
||||
|
||||
duplicate_f_name_l_name_dob = {
|
||||
'email': 'contactentityduplicateone@unit.test',
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"email": {
|
||||
'will_create': False,
|
||||
'exception_key': 'required'
|
||||
}
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_duplicate_f_name_l_name_dob = {
|
||||
'email': 'contactentityduplicatetwo@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
"""Model to test"""
|
||||
|
||||
create_model_serializer = ModelSerializer
|
||||
"""Serializer to test"""
|
||||
|
||||
valid_data: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
'email': 'contactentityduplicatetwo@unit.test',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_email_exception(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and field email is missing
|
||||
a validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['email']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['email'][0] == 'required'
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class ContactSerializerInheritedCases(
|
||||
SerializerTestCases,
|
||||
ContactSerializerTestCases,
|
||||
):
|
||||
|
||||
create_model_serializer = None
|
||||
"""Serializer to test"""
|
||||
|
||||
duplicate_f_name_l_name_dob: dict = None
|
||||
""" Duplicate model serializer dict
|
||||
|
||||
used for testing for duplicate f_name, l_name and dob fields.
|
||||
"""
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
""" Model kwargs to create item"""
|
||||
|
||||
kwargs_create_item_duplicate_f_name_l_name_dob: dict = None
|
||||
"""model kwargs to create object
|
||||
|
||||
**None:** Ensure that the fields of sub-model to person do not match
|
||||
`self.duplicate_f_name_l_name_dob`. if they do the wrong exception will be thrown.
|
||||
|
||||
used for testing for duplicate f_name, l_name and dob fields.
|
||||
"""
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.duplicate_f_name_l_name_dob.update(
|
||||
super().duplicate_f_name_l_name_dob
|
||||
)
|
||||
|
||||
self.kwargs_create_item_duplicate_f_name_l_name_dob.update(
|
||||
super().kwargs_create_item_duplicate_f_name_l_name_dob
|
||||
)
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.valid_data.update(
|
||||
super().valid_data
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactSerializerTest(
|
||||
SerializerTestCases,
|
||||
TestCase,
|
||||
class ContactSerializerPyTest(
|
||||
ContactSerializerTestCases,
|
||||
):
|
||||
|
||||
pass
|
||||
parameterized_test_data: dict = None
|
||||
|
@ -2,91 +2,28 @@ from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.functional.person.test_functional_person_viewset import (
|
||||
PersonMetadataInheritedCases,
|
||||
PersonPermissionsAPIInheritedCases,
|
||||
PersonViewSetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org = {
|
||||
'email': 'ipstrange@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
class PermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
PersonPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPIInheritedCases(
|
||||
PermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.add_data.update(
|
||||
super().add_data
|
||||
)
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPITest(
|
||||
PermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
ViewSetBase,
|
||||
PersonViewSetInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
add_data: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'email': 'ipstrange@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
|
||||
|
||||
@ -96,21 +33,19 @@ class ContactViewSetInheritedCases(
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
@ -120,50 +55,4 @@ class ContactViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
ViewSetBase,
|
||||
PersonMetadataInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ContactMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactMetadataTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
pass
|
||||
|
24
app/access/tests/functional/entity/conftest.py
Normal file
24
app/access/tests/functional/entity/conftest.py
Normal file
@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Entity
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
@ -1,78 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_history import Entity, EntityHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class HistoryTestCases(
|
||||
HistoryEntriesCommon,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
history_model = EntityHistory
|
||||
|
||||
kwargs_create_obj: dict = {}
|
||||
|
||||
kwargs_delete_obj: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = self.field_value_original,
|
||||
**self.kwargs_create_obj,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'another note',
|
||||
**self.kwargs_delete_obj,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
||||
|
||||
|
||||
|
||||
class EntityHistoryInheritedCases(
|
||||
HistoryTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityHistoryTest(
|
||||
HistoryTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,260 @@
|
||||
import django
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from accounting.models.asset_base import AssetBase
|
||||
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTestCases(
|
||||
MetadataAttributesFunctional,
|
||||
):
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
base_model = Entity
|
||||
"""Base model for this sub model
|
||||
don't change or override this value
|
||||
"""
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({
|
||||
'organization': self.organization.id,
|
||||
})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = self.different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
def test_sanity_is_entity_sub_model(self):
|
||||
"""Sanity Test
|
||||
|
||||
This test ensures that the model being tested `self.model` is a
|
||||
sub-model of `self.base_model`.
|
||||
This test is required as the same viewset is used for all sub-models
|
||||
of `AssetBase`
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, self.base_model)
|
||||
|
||||
|
||||
|
||||
class EntityMetadataInheritedCases(
|
||||
EntityMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
url_name = '_api_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTest(
|
||||
EntityMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
url_name = '_api_entity'
|
@ -0,0 +1,89 @@
|
||||
import pytest
|
||||
|
||||
from api.tests.functional.test_functional_api_permissions import (
|
||||
APIPermissionsInheritedCases,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPITestCases(
|
||||
APIPermissionsInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
change_data = {}
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_entity'
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a tenancy model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPIInheritedCases(
|
||||
EntityPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_entity_sub'
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def inherited_var_setup(self, request):
|
||||
|
||||
request.cls.url_kwargs.update({
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
})
|
||||
|
||||
request.cls.url_view_kwargs.update({
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
})
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='class', autouse = True)
|
||||
def class_setup(self, request, django_db_blocker,
|
||||
model,
|
||||
var_setup,
|
||||
prepare,
|
||||
inherited_var_setup,
|
||||
diff_org_model,
|
||||
create_model,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EntityPermissionsAPIPyTest(
|
||||
EntityPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,94 +1,171 @@
|
||||
import django
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.serializers.entity import (
|
||||
Entity,
|
||||
ModelSerializer
|
||||
)
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class SerializerTestCases:
|
||||
class MockView:
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
""" Model kwargs to create item"""
|
||||
_has_import: bool = False
|
||||
"""User Permission
|
||||
|
||||
model = Entity
|
||||
"""Model to test"""
|
||||
get_permission_required() sets this to `True` when user has import permission.
|
||||
"""
|
||||
|
||||
create_model_serializer = ModelSerializer
|
||||
"""Serializer to test"""
|
||||
_has_purge: bool = False
|
||||
"""User Permission
|
||||
|
||||
valid_data: dict = {}
|
||||
get_permission_required() sets this to `True` when user has purge permission.
|
||||
"""
|
||||
|
||||
_has_triage: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has triage permission.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class EntitySerializerTestCases:
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"model_notes": {
|
||||
'will_create': True,
|
||||
}
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'model_notes': 'model notes field'
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.kwargs_create_item.update({
|
||||
'model_notes': 'model notes field'
|
||||
})
|
||||
@pytest.fixture( scope = 'class')
|
||||
def setup_data(self,
|
||||
request,
|
||||
model,
|
||||
django_db_blocker,
|
||||
organization_one,
|
||||
):
|
||||
|
||||
self.valid_data.update({
|
||||
'organization': self.organization.pk,
|
||||
'model_notes': 'model notes field'
|
||||
})
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
**self.kwargs_create_item,
|
||||
)
|
||||
request.cls.organization = organization_one
|
||||
|
||||
valid_data = {}
|
||||
|
||||
for base in reversed(request.cls.__mro__):
|
||||
|
||||
if hasattr(base, 'valid_data'):
|
||||
|
||||
if base.valid_data is None:
|
||||
|
||||
continue
|
||||
|
||||
valid_data.update(**base.valid_data)
|
||||
|
||||
|
||||
if len(valid_data) > 0:
|
||||
|
||||
request.cls.valid_data = valid_data
|
||||
|
||||
|
||||
if 'organization' not in request.cls.valid_data:
|
||||
|
||||
request.cls.valid_data.update({
|
||||
'organization': request.cls.organization.pk
|
||||
})
|
||||
|
||||
|
||||
request.cls.view_user = User.objects.create_user(username="cafs_test_user_view", password="password")
|
||||
|
||||
|
||||
yield
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.view_user.delete()
|
||||
|
||||
del request.cls.valid_data
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data(self):
|
||||
@pytest.fixture( scope = 'class', autouse = True)
|
||||
def class_setup(self,
|
||||
setup_data,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test_serializer_valid_data(self, create_serializer):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with valid data, no validation
|
||||
error occurs.
|
||||
"""
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
view_set = MockView()
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
def test_serializer_validation_no_model_notes(self):
|
||||
|
||||
def test_serializer_valid_data_missing_field_is_valid(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_will_create,
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no model_notes is provided no validation
|
||||
error occurs
|
||||
Ensure that when creating an object with a user with import permission
|
||||
and with valid data, no validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['model_notes']
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
view_set._has_import = True
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
is_valid = serializer.is_valid(raise_exception = False)
|
||||
|
||||
assert (
|
||||
(
|
||||
not param_will_create
|
||||
and param_will_create == is_valid
|
||||
)
|
||||
or param_will_create == is_valid
|
||||
)
|
||||
|
||||
|
||||
|
||||
class EntitySerializerInheritedCases(
|
||||
SerializerTestCases,
|
||||
EntitySerializerTestCases,
|
||||
):
|
||||
|
||||
create_model_serializer = None
|
||||
"""Serializer to test"""
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
""" Model kwargs to create item"""
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
@ -97,10 +174,40 @@ class EntitySerializerInheritedCases(
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
def test_serializer_valid_data_missing_field_raises_exception(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_exception_key,
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
class EntitySerializerTest(
|
||||
SerializerTestCases,
|
||||
TestCase,
|
||||
Ensure that when creating an object with a user with import permission
|
||||
and with valid data, no validation error occurs.
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
is_valid = serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()[param_value][0] == param_exception_key
|
||||
|
||||
|
||||
|
||||
class EntitySerializerPyTest(
|
||||
EntitySerializerTestCases,
|
||||
):
|
||||
|
||||
pass
|
||||
parameterized_test_data: dict = None
|
||||
|
@ -1,37 +1,49 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
import django
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
|
||||
from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data: dict = None
|
||||
add_data: dict = {
|
||||
'model_notes': 'added model note'
|
||||
}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
base_model = Entity
|
||||
"""Base model for this sub model
|
||||
don't change or override this value
|
||||
"""
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
kwargs_create_item: dict = {
|
||||
'model_notes': 'added model note'
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'model_notes': 'added model note'
|
||||
}
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = None
|
||||
|
||||
@ -53,16 +65,15 @@ class ViewSetBase:
|
||||
|
||||
self.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
model_notes = 'some notes',
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
model_notes = 'some more notes',
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
@ -71,7 +82,9 @@ class ViewSetBase:
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({'organization': self.organization.id})
|
||||
self.add_data.update({
|
||||
'organization': self.organization.id,
|
||||
})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
@ -144,7 +157,6 @@ class ViewSetBase:
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
@ -190,98 +202,16 @@ class ViewSetBase:
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
):
|
||||
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
change_data = {'model_notes': 'device'}
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.add_data.update({ 'model_note': 'added model note' })
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a tenancy model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
def test_sanity_is_asset_sub_model(self):
|
||||
"""Sanity Test
|
||||
|
||||
This test ensures that the model being tested `self.model` is a
|
||||
sub-model of `self.base_model`.
|
||||
This test is required as the same viewset is used for all sub-models
|
||||
of `Entity`
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPIInheritedCases(
|
||||
PermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPITest(
|
||||
PermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
assert issubclass(self.model, self.base_model)
|
||||
|
||||
|
||||
|
||||
@ -290,17 +220,7 @@ class ViewSetTestCases(
|
||||
SerializersTestCases,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
model = Entity
|
||||
|
||||
|
||||
|
||||
@ -310,22 +230,28 @@ class EntityViewSetInheritedCases(
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
url_name = '_api_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
@ -337,168 +263,4 @@ class EntityViewSetTest(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
ViewSetBase,
|
||||
MetadataAttributesFunctional,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
|
||||
class EntityMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
# def test_method_options_request_detail_data_has_key_urls_back(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data returned has key `urls.back`
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# response = client.options(
|
||||
# reverse(
|
||||
# self.app_namespace + ':' + self.url_name + '-detail',
|
||||
# kwargs=self.url_view_kwargs
|
||||
# ),
|
||||
# content_type='application/json'
|
||||
# )
|
||||
|
||||
# assert 'back' in response.data['urls']
|
||||
|
||||
|
||||
# def test_method_options_request_detail_data_key_urls_back_is_str(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data key `urls.back` is str
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# response = client.options(
|
||||
# reverse(
|
||||
# self.app_namespace + ':' + self.url_name + '-detail',
|
||||
# kwargs=self.url_view_kwargs
|
||||
# ),
|
||||
# content_type='application/json'
|
||||
# )
|
||||
|
||||
# assert type(response.data['urls']['back']) is str
|
||||
|
||||
|
||||
|
||||
# def test_method_options_request_list_data_has_key_urls_return_url(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data returned has key `urls.return_url`
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# if self.url_kwargs:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
# else:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
# response = client.options( url, content_type='application/json' )
|
||||
|
||||
# assert 'return_url' in response.data['urls']
|
||||
|
||||
|
||||
# def test_method_options_request_list_data_key_urls_return_url_is_str(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data key `urls.return_url` is str
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# if self.url_kwargs:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
# else:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
# response = client.options( url, content_type='application/json' )
|
||||
|
||||
# assert type(response.data['urls']['return_url']) is str
|
||||
|
||||
|
||||
url_name = '_api_entity'
|
||||
|
@ -1,81 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_notes import Entity, EntityNotes
|
||||
|
||||
from core.tests.abstract.model_notes_api_fields import ModelNotesNotesAPIFields
|
||||
|
||||
|
||||
|
||||
class NotesAPITestCases(
|
||||
ModelNotesNotesAPIFields,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
kwargs_model_create: dict = None
|
||||
|
||||
# url_view_kwargs: dict = None
|
||||
|
||||
view_name: str = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Call parent setup
|
||||
2. Create a model note
|
||||
3. add url kwargs
|
||||
4. make the API request
|
||||
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.entity_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_model_create
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.pk
|
||||
}
|
||||
|
||||
self.make_request()
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPIInheritedCases(
|
||||
NotesAPITestCases,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
kwargs_model_create = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPITest(
|
||||
NotesAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
entity_model = Entity
|
||||
|
||||
kwargs_model_create = {}
|
@ -1,162 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.viewsets.entity_notes import ViewSet
|
||||
|
||||
from core.tests.abstract.test_functional_notes_viewset import (
|
||||
ModelNotesViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
ModelNotesPermissionsAPI,
|
||||
ModelNotesSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase(
|
||||
ModelNotesViewSetBase
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
kwargs_create_model_item: dict = {}
|
||||
|
||||
kwargs_create_model_item_other_org: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.item = self.viewset.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.other_org_item = self.viewset.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item_other_org
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class NotesPermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesPermissionsAPI,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a global model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPIInheritedCases(
|
||||
NotesPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPITest(
|
||||
NotesPermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesSerializerTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesSerializer,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerInheritedCases(
|
||||
NotesSerializerTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerTest(
|
||||
NotesSerializerTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesMetadataTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataInheritedCases(
|
||||
NotesMetadataTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataTest(
|
||||
NotesMetadataTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
@ -1,32 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.organization_history import Organization, OrganizationHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class History(
|
||||
HistoryEntriesCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Organization
|
||||
|
||||
history_model = OrganizationHistory
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
name = self.field_value_original,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
name = self.field_value_delete,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
@ -1,22 +1,25 @@
|
||||
import django
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.serializers.organization import (
|
||||
Organization,
|
||||
OrganizationModelSerializer
|
||||
Tenant,
|
||||
TenantModelSerializer
|
||||
)
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip( reason = 'to be refactored' )
|
||||
class OrganizationValidationAPI(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Organization
|
||||
model = Tenant
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -45,7 +48,7 @@ class OrganizationValidationAPI(
|
||||
Ensure that if creating and no name is provided a validation error occurs
|
||||
"""
|
||||
|
||||
serializer = OrganizationModelSerializer(
|
||||
serializer = TenantModelSerializer(
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
@ -65,7 +68,7 @@ class OrganizationValidationAPI(
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = OrganizationModelSerializer(
|
||||
serializer = TenantModelSerializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
@ -85,7 +88,7 @@ class OrganizationValidationAPI(
|
||||
|
||||
del data['manager']
|
||||
|
||||
serializer = OrganizationModelSerializer(
|
||||
serializer = TenantModelSerializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
import django
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, Permission, User
|
||||
from django.contrib.auth.models import AnonymousUser, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
@ -17,15 +18,18 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip( reason = 'to be refactored' )
|
||||
class ViewSetBase:
|
||||
|
||||
model = Organization
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
url_name = '_api_v2_organization'
|
||||
url_name = '_api_tenant'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
@ -315,4 +319,4 @@ class OrganizationMetadata(
|
||||
|
||||
menu_id = 'access'
|
||||
|
||||
menu_entry_id = 'organization'
|
||||
menu_entry_id = 'tenant'
|
@ -1,116 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.viewsets.organization_notes import ViewSet
|
||||
|
||||
from core.tests.abstract.test_functional_notes_viewset import (
|
||||
ModelNotesViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
ModelNotesPermissionsAPI,
|
||||
ModelNotesSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase(
|
||||
ModelNotesViewSetBase
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
url_name = '_api_v2_organization_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.item = self.viewset.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.other_org_item = self.viewset.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.different_organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.global_org_item = self.viewset.model.objects.create(
|
||||
organization = self.global_organization,
|
||||
content = 'a random comment global_organization',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.global_organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesPermissionsAPI(
|
||||
ViewSetBase,
|
||||
ModelNotesPermissionsAPI,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a global model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesSerializer(
|
||||
ViewSetBase,
|
||||
ModelNotesSerializer,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesMetadata(
|
||||
ViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
pass
|
@ -1,53 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from core.tests.abstract.model_notes_api_fields import ModelNotesNotesAPIFields
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.organization_notes import OrganizationNotes
|
||||
|
||||
|
||||
class OrganizationNotesAPI(
|
||||
ModelNotesNotesAPIFields,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = OrganizationNotes
|
||||
|
||||
view_name: str = '_api_v2_organization_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Call parent setup
|
||||
2. Create a model note
|
||||
3. add url kwargs
|
||||
4. make the API request
|
||||
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = Organization.objects.create(
|
||||
name = 'dev'
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.pk
|
||||
}
|
||||
|
||||
self.make_request()
|
24
app/access/tests/functional/person/conftest.py
Normal file
24
app/access/tests/functional/person/conftest.py
Normal file
@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Person
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity_person import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
@ -1,65 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.functional.entity.test_functional_entity_history import (
|
||||
EntityHistoryInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonTestCases(
|
||||
EntityHistoryInheritedCases,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
|
||||
|
||||
class PersonHistoryInheritedCases(
|
||||
PersonTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonHistoryTest(
|
||||
PersonTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,83 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.functional.entity.test_functional_entity_metadata import (
|
||||
EntityMetadataInheritedCases
|
||||
)
|
||||
|
||||
from accounting.models.asset_base import AssetBase
|
||||
|
||||
|
||||
|
||||
class PersonMetadataTestCases(
|
||||
EntityMetadataInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Strange',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
|
||||
|
||||
|
||||
|
||||
class PersonMetadataInheritedCases(
|
||||
PersonMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
# self.url_kwargs = {
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
# self.url_view_kwargs = {
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonMetadataTest(
|
||||
PersonMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
pass
|
@ -0,0 +1,91 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_permission import (
|
||||
EntityPermissionsAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonPermissionsAPITestCases(
|
||||
EntityPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Strange',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
# app_namespace = 'v2'
|
||||
|
||||
# change_data = {}
|
||||
|
||||
# delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
# url_kwargs: dict = {}
|
||||
|
||||
# url_name = '_api_entity'
|
||||
|
||||
# url_view_kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
class PersonPermissionsAPIInheritedCases(
|
||||
PersonPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
# url_name = '_api_entity_sub'
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class')
|
||||
# def inherited_var_setup(self, request):
|
||||
|
||||
# request.cls.url_kwargs.update({
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
# request.cls.url_view_kwargs.update({
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class', autouse = True)
|
||||
# def class_setup(self, request, django_db_blocker,
|
||||
# model,
|
||||
# var_setup,
|
||||
# prepare,
|
||||
# inherited_var_setup,
|
||||
# diff_org_model,
|
||||
# create_model,
|
||||
# ):
|
||||
|
||||
# pass
|
||||
|
||||
|
||||
class PersonPermissionsAPIPyTest(
|
||||
PersonPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user