Compare commits
1055 Commits
1.15.1
...
access-tes
Author | SHA1 | Date | |
---|---|---|---|
ee31b9348f | |||
c252717bb7 | |||
4b7549b87b | |||
2806f6bc8e | |||
b0b7208eaa | |||
c089b6fdf4 | |||
b9a9346b3f | |||
d49f71d3fe | |||
b63fd35c5b | |||
14a3df16c8 | |||
4dbef179c9 | |||
18c1c4029e | |||
52001959d9 | |||
b78c0e3045 | |||
2e0953d0ed | |||
bca0f0a3e2 | |||
bd51c55a15 | |||
bc0180b246 | |||
c7b16874d3 | |||
47daac3a1b | |||
590535516b | |||
1fbbbfba88 | |||
767957ad66 | |||
82bbf88c6f | |||
daf68103ad | |||
8159799afc | |||
25b8993462 | |||
3fc2b538a8 | |||
ca938d2b61 | |||
6e65802932 | |||
cbaaefa87d | |||
6b28dbf346 | |||
a84676c227 | |||
8191785134 | |||
6e1d0c747d | |||
482014004c | |||
3d45e0ddfc | |||
36303ec28f | |||
5eb0931495 | |||
7392c9700d | |||
68084d8a05 | |||
76bf78d3f5 | |||
a749862dbc | |||
25d3c5cb5a | |||
7c7bfaf26a | |||
39c09f066d | |||
bc7344c757 | |||
971e1d61ea | |||
f6c1b77c43 | |||
c8fade9e4d | |||
31a03b4226 | |||
de87364b9c | |||
a382561245 | |||
0b9eeea12c | |||
7800b1abb0 | |||
049cf6577e | |||
c154cef6d5 | |||
132765833e | |||
842fc38a98 | |||
35d2e869b0 | |||
f0fdf4ed15 | |||
41576f9862 | |||
4ffbf81b71 | |||
cf06783aec | |||
80ab996851 | |||
13cf3f8152 | |||
9d78d112ec | |||
27e842a57f | |||
8cc8fdad8b | |||
35c5c7ca66 | |||
645baa24d9 | |||
739cefbf1c | |||
15cf7096f2 | |||
fd5b223632 | |||
42ea8121a8 | |||
d3741e74e2 | |||
b4f61223d6 | |||
1efa932fb9 | |||
a04c963431 | |||
815075174a | |||
9df5a5898e | |||
ceabd56e1b | |||
61a1792e4c | |||
3baed3f8f5 | |||
9a516fbebc | |||
be708b1244 | |||
2f7865bb83 | |||
b643347c01 | |||
957052da84 | |||
b2e0982b4c | |||
ca63e4d6fc | |||
3b7e88969e | |||
953dce2984 | |||
e0c8ef46f5 | |||
548051df6d | |||
d6eb33141a | |||
4f36769fba | |||
018004ccb9 | |||
b851388d37 | |||
38b4542a58 | |||
3b5673ae4b | |||
e4e7ad915b | |||
de7c52d733 | |||
d741992b6d | |||
9b3c8d2225 | |||
4ff457a58c | |||
46b4df5dc1 | |||
2482148466 | |||
2c1c769988 | |||
38f3a3bf05 | |||
965bde51bc | |||
303cc31f6a | |||
97f99abe2c | |||
93642ee6a1 | |||
eeaf3ab5fc | |||
5b6f9bec00 | |||
cf7fb57583 | |||
45abdc2e00 | |||
22ad79386e | |||
baabf84234 | |||
c54de5c627 | |||
803d7f3223 | |||
b5f55c1eea | |||
888fc68180 | |||
5d8062fcef | |||
e99ab1b0c6 | |||
7c85d9653f | |||
9ebf9bf3de | |||
57936073db | |||
638e2ba986 | |||
6af615f918 | |||
37a69888b6 | |||
c2255a5834 | |||
77cf598e4d | |||
1f4ce92623 | |||
882ae90d83 | |||
ea88076d7f | |||
6e925e8456 | |||
ffaac107f4 | |||
b282162a86 | |||
05fd82459a | |||
b2383a2236 | |||
bf1bb23dd5 | |||
73a704d5e3 | |||
7b22541002 | |||
1ba7661f07 | |||
2eb0ba674e | |||
6ae3b193fa | |||
2b7eaace4d | |||
4f8fca9aff | |||
53a474fa2a | |||
b549059d15 | |||
e7eb1442b7 | |||
6d2406ae1c | |||
aa86f04aa2 | |||
6d33653db0 | |||
bc3545d53e | |||
3ea44aaabe | |||
d7442cfff4 | |||
23832fff6a | |||
c5f8412d46 | |||
a582cbbe59 | |||
8c48fb6c37 | |||
3c6589f07c | |||
d1fbead077 | |||
3025c397b5 | |||
43490b7407 | |||
e15bb11a76 | |||
1823cb4ec1 | |||
9de99e07f7 | |||
c9c8cf6700 | |||
af868fcf4f | |||
e9fe6a2bdc | |||
595b232db9 | |||
87d0a5174f | |||
f074abefa7 | |||
b6dae72a07 | |||
847e4f93ea | |||
3943171534 | |||
ef5d132ab3 | |||
874ce0c4f4 | |||
ae20451033 | |||
fefb7e67a0 | |||
41da9fb6a0 | |||
5234051538 | |||
ebea2da906 | |||
e233bf4487 | |||
ea1d6f4a20 | |||
0a9a26a43b | |||
8f984e6c21 | |||
7e3ae4c0bc | |||
ef6ebcdcf4 | |||
0b8b0bc850 | |||
e3804b4f9b | |||
ded80d4a8c | |||
2027f3fe97 | |||
239540c9ab | |||
164aa21fad | |||
78539f2551 | |||
095239f199 | |||
ea8c81c64d | |||
80770deafd | |||
05a32711b5 | |||
1fe669e932 | |||
801bac9e6d | |||
4a3f2ae6db | |||
fdbf9f81db | |||
b1fcc58741 | |||
278b9e5c08 | |||
cec4b39f5a | |||
c264ab2cd4 | |||
44688c35df | |||
f023901dc4 | |||
c53a488a27 | |||
51f7b94360 | |||
f7e71be426 | |||
39bcd74c56 | |||
763836dd79 | |||
3667ed76d3 | |||
f62fa6d166 | |||
caf2fd0ad3 | |||
c7dda4110a | |||
33bba65b44 | |||
c7595f71ea | |||
3e6570045c | |||
461b3f4933 | |||
4c53c92a22 | |||
fbe5675bc9 | |||
f1f70e9a22 | |||
e5946cecbc | |||
1af06bbaa6 | |||
ff77aaac13 | |||
c07b208a63 | |||
cc82c80639 | |||
ba26a18682 | |||
c9a21bc3e9 | |||
60b38cc61b | |||
936c6b4cd6 | |||
6a7dd54c58 | |||
3cbe4201d0 | |||
253ee3822a | |||
bf7fa83e39 | |||
6c1eee1d98 | |||
dc3d02eb66 | |||
afb688a396 | |||
b378565ff6 | |||
a9cde74013 | |||
27f35e6fde | |||
f377ad102e | |||
fa7c70f2ce | |||
1f6a36ec8e | |||
33617bd608 | |||
b83a087081 | |||
8fd243e691 | |||
b20cab4f8e | |||
f25c7274c6 | |||
1e339a1f0d | |||
00c3eff30e | |||
6163b4ad2d | |||
8452e678da | |||
30c19031eb | |||
d0e8b58176 | |||
a192659c80 | |||
44489b308b | |||
1b875233da | |||
93eb65316e | |||
1611537d86 | |||
5da4e2338a | |||
07ef410c31 | |||
067aa8714f | |||
8c61c4e8a6 | |||
234e85368a | |||
915101820e | |||
93992dbef5 | |||
81e7d21d6f | |||
ae4b2a3c04 | |||
cb0d095eb7 | |||
b491ba45e5 | |||
610e4665a6 | |||
c2ad9e3189 | |||
0d2b2d2183 | |||
851bf553b0 | |||
d6da2cf0fb | |||
ecc544ce19 | |||
7542521866 | |||
df43ee9e7a | |||
bdbcc46db5 | |||
f8e659ddd5 | |||
e87e3992aa | |||
61672c04e3 | |||
8ca2513952 | |||
0b1d658db9 | |||
e60081fea6 | |||
7ee6970615 | |||
580df01cc5 | |||
4a38f73ddf | |||
c29e5890eb | |||
175ab02a7d | |||
434992323f | |||
7cbe13ffe4 | |||
5fc4d7da91 | |||
1a598fc917 | |||
465c0d8679 | |||
d7e70c1e62 | |||
48cf7b56c4 | |||
fbc2bf081e | |||
4aaf2f87ef | |||
32ffe34cf3 | |||
281e233a7a | |||
b5de17441b | |||
731c069c0b | |||
729bcf9a57 | |||
3332674cb6 | |||
306d4b5b7a | |||
69e6177700 | |||
5e12ea7a57 | |||
5d56837f1e | |||
dabf663651 | |||
1841023896 | |||
d48c19a8ed | |||
4f5a4c4124 | |||
f44fb28b8d | |||
a241ba61b0 | |||
dbf67f7737 | |||
da865fd5a8 | |||
446ea7c910 | |||
d4de500593 | |||
f81133f96c | |||
40e240c15b | |||
c5823152ca | |||
b17fcf0eac | |||
9a737ac1c4 | |||
b764ebf6e1 | |||
6193fe0418 | |||
18a4158f3e | |||
82d1e9995f | |||
e7942a79ed | |||
7378b103e1 | |||
07b6a2f4e2 | |||
8f0a5386af | |||
2cb334f25f | |||
b4daa61b36 | |||
0e5b5db7b9 | |||
0daf4a5316 | |||
74420f13cc | |||
192c768f9a | |||
f6dc4ff512 | |||
ec1457f3f8 | |||
2ffb93a630 | |||
4a484c31a2 | |||
87c5768945 | |||
bff14dc21a | |||
6b54eebbd2 | |||
9a250c988d | |||
d2d46e7efd | |||
2da0fe4e3c | |||
d43a1bc213 | |||
53726e3eba | |||
dff529ba5e | |||
0d149edee5 | |||
80c1915598 | |||
32d296885d | |||
7480b371aa | |||
2d3e496e32 | |||
5817ff723b | |||
abdcea07a6 | |||
f234b338ee | |||
f2de2dc4ab | |||
5bc7daf202 | |||
ebbb7e8e6e | |||
14891990f8 | |||
48e20a366d | |||
0c147768ba | |||
6025750637 | |||
b9701757e0 | |||
d71eee7178 | |||
f7c6be71ba | |||
5d09bf04ff | |||
05c9864d68 | |||
1e0539f2d6 | |||
f7c581333a | |||
ac7af08598 | |||
4930523af0 | |||
277c96f3a3 | |||
aa525407c3 | |||
cf3725f12c | |||
4740248e7f | |||
666511b7f4 | |||
a7a1d85978 | |||
35b3710562 | |||
708c2ae92c | |||
a959e4cbb5 | |||
0a3f2257e5 | |||
dac8e41546 | |||
dbdfe4e6c0 | |||
7ad1851cc0 | |||
2bdffb947f | |||
77c47c12b4 | |||
162910d9ec | |||
2eed0bbf4f | |||
d97dfa4df1 | |||
0f575652f9 | |||
cebc6b668a | |||
2fe75ba369 | |||
2f59a34b69 | |||
613ad7e4e4 | |||
f5ef97ab4a | |||
a9e0749ad3 | |||
9ed6bcf55c | |||
aa9a6e09db | |||
5bdabe0793 | |||
40a70020a5 | |||
d129256c4e | |||
7753c98f43 | |||
496c5d57ac | |||
90fc516722 | |||
d1f003820a | |||
7a614c80f7 | |||
5e0604a44b | |||
b5cac23f9d | |||
96b83309d1 | |||
c4c4f8eca5 | |||
a836e0de47 | |||
7444b9bacf | |||
4a9de878ed | |||
48ea6c4491 | |||
b3c9f00a08 | |||
8200b47025 | |||
c70dd867c9 | |||
725782af6e | |||
2e41922d9c | |||
d3b4958a01 | |||
7326598f6c | |||
35b72a048e | |||
ab68b56056 | |||
7b55d37117 | |||
39d3d0786b | |||
41fde61184 | |||
18036ee8d0 | |||
ec95f4b45d | |||
520008acff | |||
a3b9815891 | |||
dc56ced765 | |||
8c4e5d5c1d | |||
0e604228aa | |||
4fda640832 | |||
9fec7f69f5 | |||
71108f6089 | |||
39208edf93 | |||
2118e56808 | |||
d86a719f24 | |||
5c27115991 | |||
52235e2e82 | |||
d714476c97 | |||
274347ebc1 | |||
276e322eef | |||
f56ee55347 | |||
ad1f92cdb4 | |||
7cc8192ec2 | |||
9509765ba5 | |||
8b694aabf5 | |||
8f0d0fbe16 | |||
8c406e1d59 | |||
9ab4e4ec7e | |||
2e5d6d4894 | |||
bcd66fd657 | |||
8b8ee525f7 | |||
3e63f896a5 | |||
7173d9f554 | |||
20b173bf1c | |||
ac6f580153 | |||
d06bf78d0a | |||
e75bc124c9 | |||
fc60d7f3be | |||
cff3bc5b2c | |||
8202a37576 | |||
5186b2f173 | |||
e9a8ad1c48 | |||
143dd2af35 | |||
09beeb46af | |||
883c0961f6 | |||
3d9cde3b25 | |||
1d80cdd5c3 | |||
86da111b85 | |||
38b1746f26 | |||
f6e3390d26 | |||
cf8feb2637 | |||
f2a2352a3c | |||
78334da031 | |||
398b78855d | |||
7691ad5b0b | |||
a8e2536bd2 | |||
dd9a906073 | |||
82bfacbab3 | |||
728db3f740 | |||
8a390b9caf | |||
3155794931 | |||
f79a9d628b | |||
bf39631db8 | |||
3c114abb11 | |||
a30ba02def | |||
aa7de88f8d | |||
24c960dc86 | |||
0ded2bcebd | |||
82f0f87878 | |||
0792dd1604 | |||
baa9a6206d | |||
bf7179b4f9 | |||
b8db09d9b8 | |||
9ff93976f2 | |||
b535aa4703 | |||
074aa4832a | |||
cc483e2cb5 | |||
f8faa7f040 | |||
764e631be6 | |||
882c2afc9f | |||
6aa7953c2a | |||
25d510f086 | |||
97b07e1663 | |||
3d961ecc9e | |||
05921057fd | |||
90f68d7fe5 | |||
642ecc4e6c | |||
b1530b3c19 | |||
c64add9ce7 | |||
3915678675 | |||
f42e255f37 | |||
c0455af2fa | |||
4d211a2cb7 | |||
3205f9e946 | |||
0c62172f03 | |||
c8a5bbcfb6 | |||
cf5f43f33c | |||
3f05100cde | |||
b6bc39e784 | |||
d4350dc444 | |||
c3905360f4 | |||
71e2d2f112 | |||
d0f3923226 | |||
a53f4caa81 | |||
d18c9006d2 | |||
388f9a6039 | |||
0a1166f1df | |||
74e54343b9 | |||
2cec1be952 | |||
b21b0f0b5b | |||
731df89b40 | |||
072cda66bf | |||
d3f367f4b9 | |||
391a2f7767 | |||
40ffdd86ba | |||
223aae2eb4 | |||
22e35fbf1c | |||
ba6f09c9b5 | |||
2509743597 | |||
b76bc5a416 | |||
a6d781be47 | |||
69bbe94b06 | |||
be3ce40471 | |||
98fd342343 | |||
e0a4526d0c | |||
2b6d421b91 | |||
a3fda45055 | |||
0a0577aa24 | |||
78ed699463 | |||
6d0f655c97 | |||
2220dcf851 | |||
a5a391fba6 | |||
de9358c943 | |||
f5b1738fd2 | |||
b80cc3e839 | |||
734c29f41b | |||
507182983b | |||
0a413a6581 | |||
c476f2a12c | |||
0f3da21aa4 | |||
d5b465ea0e | |||
caeabe0793 | |||
9dc2114258 | |||
91b33be834 | |||
6f66b4cf95 | |||
0abb416620 | |||
3a010e166e | |||
616f37fca2 | |||
c042725dc7 | |||
026c408754 | |||
ae42147f69 | |||
764ae1133d | |||
1d31a259df | |||
66d14ea5bb | |||
2f22d0f135 | |||
56bb1863f2 | |||
9790ee51f3 | |||
8892ba7e3f | |||
9e93c12860 | |||
cf3c177715 | |||
db93903119 | |||
4414f585a6 | |||
f6634bab78 | |||
ef294cc081 | |||
2d1ec6a84a | |||
b37a284a0a | |||
1fa3278b73 | |||
5bf9c30c58 | |||
637a76f1fe | |||
9afca66ad6 | |||
031fd79f13 | |||
5429c17f98 | |||
473ffb0ec0 | |||
62e7eb9c47 | |||
695bfa7f51 | |||
a5227f0608 | |||
1d77247e40 | |||
bb774bc2c9 | |||
ce96d71083 | |||
c905f8c010 | |||
084bdf61bd | |||
21b3aa3b4a | |||
8c6e863403 | |||
f890fb6d52 | |||
0babe48208 | |||
4290136b69 | |||
121a0c3f2c | |||
e04869cc65 | |||
dc6d34de71 | |||
0b7fcbbc26 | |||
1cc378c69a | |||
959b8ac51a | |||
f4f9729c7d | |||
54195c271a | |||
79b8cdd26b | |||
f53531fbbd | |||
6141ad1d5a | |||
428770a8eb | |||
11e12ec70f | |||
ba64fdd36e | |||
416b6f5a75 | |||
adc1c8fba7 | |||
ade35d0e2d | |||
212af84d28 | |||
7ed75fc3e9 | |||
e554923c0b | |||
17f4295763 | |||
8e81bc06db | |||
0b30f3b70f | |||
827894ef23 | |||
7a2e77cba6 | |||
187ad724a2 | |||
9e2587d6c3 | |||
21e7d7b444 | |||
26b5aa7f45 | |||
37ff9d38cb | |||
f440788dae | |||
279ea1991d | |||
6b899e477d | |||
9a3e17a6d3 | |||
851d6e8b2f | |||
114b2e1ae3 | |||
2b6a15cf8f | |||
a46187f36e | |||
fc256445db | |||
60f6269a71 | |||
660da485cd | |||
d86b07b4fa | |||
ea7cea2aa9 | |||
06e8e277c1 | |||
df4ffa5e93 | |||
15ab6a7f70 | |||
8e0906eb73 | |||
685de22ad2 | |||
62ec58d9d8 | |||
ee79dafd6b | |||
d31b70a2a0 | |||
766b4272be | |||
76daab8e1f | |||
82e617afbe | |||
197cf4eb73 | |||
5a80e48b21 | |||
bd66b8ec94 | |||
d8b69e3ff2 | |||
cff519d199 | |||
2e88dfc5c1 | |||
f5a2168958 | |||
35a46c90d3 | |||
bc1f12c47f | |||
7a1e9e10a3 | |||
9425793190 | |||
d3347082d1 | |||
12ec0f34f9 | |||
5e169eb7f8 | |||
105d89ee61 | |||
64e53f6980 | |||
0c6a15d22d | |||
db41c73300 | |||
da3a471d04 | |||
b5147e2448 | |||
0a35a32136 | |||
8b62014d88 | |||
16bbb3328b | |||
d0ce759635 | |||
6f0ef8ab31 | |||
28117bb2bb | |||
4d93ba7339 | |||
d098a09e83 | |||
678bc18cb6 | |||
8fd418787e | |||
85d8d74cbf | |||
3e6d9233ca | |||
06ad1fec5e | |||
95281d35eb | |||
5aae562caf | |||
49e66df006 | |||
504ded9047 | |||
a7f84924db | |||
6647ddbc99 | |||
73fdaee33f | |||
0884bc314a | |||
15b4446775 | |||
af2bc77920 | |||
9cb507286f | |||
db97b574da | |||
31e200bb01 | |||
0192f2f3a8 | |||
8f097fb44e | |||
daf2811be7 | |||
2c33fa6f62 | |||
9d7a3e2e79 | |||
23dfdfe0b3 | |||
473763af36 | |||
47c12f5d55 | |||
64ea376962 | |||
bd7ff92ab3 | |||
bff1049414 | |||
a1b9ecb0fc | |||
262e883a26 | |||
17c7980e03 | |||
3d2d759d6b | |||
b73de03d27 | |||
a9e953812c | |||
4344265ed5 | |||
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 | |||
975a71bdd6 | |||
f9e59835c1 | |||
6ea66f7379 | |||
fb768d1432 | |||
baa61155f7 | |||
c773fbc3a5 | |||
8c84ff7c52 | |||
85d4cc8220 | |||
70a2d502ef | |||
393c8ce0ce | |||
92bc4aec43 | |||
4c46681d39 | |||
a898549469 | |||
4e75faafb8 | |||
1718a1cf14 | |||
64757826da | |||
927776b9a7 | |||
4ea5ddf122 | |||
847a5550e8 | |||
ea38665dab | |||
440adc09f4 | |||
9fcb1a8a44 | |||
22808cf6c8 | |||
f978839ccc | |||
54af17d75a | |||
c7eef1a97c | |||
59741e4197 | |||
2518873e23 | |||
029ab6bc06 | |||
a583db4b65 | |||
9f8789f390 | |||
c0b27c2886 | |||
cdacb19c30 | |||
471a1249c0 | |||
53abf219a3 | |||
51c238734a | |||
efb0f52e8a | |||
39dc8ad39f | |||
db47d0e160 | |||
da13343b27 | |||
8100030870 | |||
5097db6f94 | |||
b729842a1c | |||
bc1d5ffc03 | |||
ae1f600147 | |||
edc3f9fd3c | |||
8dc922a8bb | |||
9461cdaf9d | |||
a923a21660 | |||
dcd60a5152 | |||
50882a4732 | |||
9cd59e8446 | |||
9905edc28c | |||
98479f39e6 | |||
bdfd3f42c4 | |||
ca022e0697 | |||
1719b6c417 | |||
bb0f9b56e6 | |||
107a3aa17f | |||
1c9be27e1f | |||
dc25d483fc | |||
33362a28d5 | |||
b476bd458e | |||
c152dd13f1 | |||
677cc55e04 | |||
b6146b7d14 | |||
dbc849d3f1 | |||
8d1098ec9e | |||
63923e8a9b | |||
7261f60e3b | |||
fef14b8be8 | |||
17e4cb4148 | |||
99211b7505 | |||
c4e81ffbed | |||
683b134053 | |||
2a5c86d961 | |||
ca28a67194 | |||
d904ce7100 | |||
daf30de835 | |||
7c9819efd1 | |||
42bea64ff5 | |||
537f167b9e | |||
0a630cf51a | |||
29e07e6cac | |||
0f21f8daf0 | |||
6289fad8ad | |||
705b245514 | |||
9e1a426240 | |||
c80a5b5232 | |||
eddd2534ea | |||
9dc1ae2d05 | |||
4b8cb9633e | |||
8016c4b4c8 | |||
3efadfa75f | |||
66bbace8be | |||
7ff638ed58 | |||
ddebf50e88 | |||
64144ea76f | |||
633df58964 | |||
f280f408ee | |||
d1715f4457 | |||
e80647ffd1 | |||
1a5f328a24 | |||
4872c6d1c8 | |||
a695b4563d | |||
b89993f89d | |||
8f69e6ec79 | |||
8984aeebb8 | |||
a0d750b1f5 | |||
75618de977 | |||
2e1358da83 | |||
8ecb1fe01a | |||
0ff8f8650b | |||
529a7fa385 | |||
6258fb2a33 | |||
0a2ede62b7 | |||
559e65f990 | |||
e71f7a6942 | |||
f12a2e37f3 | |||
c1614b1182 | |||
6b4bd4db35 | |||
163d7bcd6c | |||
d131d2499b | |||
4e03c2bf31 | |||
9e05410f28 | |||
9c88c23bb3 | |||
e4588771e8 | |||
507c42faea | |||
9302627c6f |
2
.cz.yaml
2
.cz.yaml
@ -17,5 +17,5 @@ commitizen:
|
||||
prerelease_offset: 1
|
||||
tag_format: $version
|
||||
update_changelog_on_bump: false
|
||||
version: 1.15.1
|
||||
version: 1.18.0
|
||||
version_scheme: semver
|
||||
|
68
.github/ISSUE_TEMPLATE/new_model.md
vendored
68
.github/ISSUE_TEMPLATE/new_model.md
vendored
@ -29,33 +29,26 @@ Describe in detail the following:
|
||||
|
||||
<!-- Don't remove tasks strike them out. use `~~` before and after the item. i.e. `- ~~[ ] Model Created~~` note: don't include the list dash-->
|
||||
|
||||
- [ ] 🆕 Model Created
|
||||
- [ ] 🆕 [Model Created](https://nofusscomputing.com/projects/centurion_erp/development/models/)
|
||||
|
||||
- [ ] 🛠️ Migrations added
|
||||
|
||||
- [ ] 🏷️ Model tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()` function
|
||||
- [ ] ♻️ Serializer Created
|
||||
|
||||
- [ ] 🔄 [ViewSet Created](https://nofusscomputing.com/projects/centurion_erp/development/views/)
|
||||
|
||||
- [ ] 🔗 URL Route Added
|
||||
|
||||
- [ ] 🏷️ [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
|
||||
|
||||
- [ ] 📓 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
|
||||
|
||||
---
|
||||
@ -66,16 +59,17 @@ Describe in detail the following:
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- [ ] Unit Test Model
|
||||
- [ ] Unit Test Tenancy Object
|
||||
- [ ] Unit Test Serializer
|
||||
- [ ] Unit Test ViewSet
|
||||
- [ ] Function Test ViewSet
|
||||
- [ ] Function Test API Metadata
|
||||
- [ ] Function Test API Permissions
|
||||
- [ ] Function Test API Render (fields)
|
||||
- [ ] Function Test History Entries
|
||||
- [ ] Function Test History API Render (fields)
|
||||
- Unit Tests
|
||||
- [ ] [Model](https://nofusscomputing.com/projects/centurion_erp/development/models/#tests)
|
||||
- [ ] ViewSet
|
||||
- [ ] Serializer
|
||||
- Function Test
|
||||
- [ ] API Metadata
|
||||
- [ ] API Permissions
|
||||
- [ ] API Render (fields)
|
||||
- [ ] Model
|
||||
- [ ] Serializer
|
||||
- [ ] ViewSet
|
||||
|
||||
|
||||
## ✅ Requirements
|
||||
@ -84,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 -->
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,9 +1,10 @@
|
||||
venv/**
|
||||
*/static/**
|
||||
__pycache__
|
||||
**.sqlite3
|
||||
**.sqlite*
|
||||
**.sqlite
|
||||
**.coverage
|
||||
.coverage*
|
||||
artifacts/
|
||||
**.tmp.*
|
||||
volumes/
|
||||
@ -15,4 +16,8 @@ node_modules/
|
||||
package-lock.json
|
||||
package.json
|
||||
**.junit.xml
|
||||
**.JUnit.xml
|
||||
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",
|
||||
]
|
||||
}
|
60
.vscode/launch.json
vendored
60
.vscode/launch.json
vendored
@ -4,7 +4,6 @@
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Centurion",
|
||||
"type": "debugpy",
|
||||
@ -18,7 +17,6 @@
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
|
||||
"name": "Debug: Gunicorn",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
@ -31,8 +29,7 @@
|
||||
"3",
|
||||
"--bind",
|
||||
"0.0.0.0:8002",
|
||||
"app.wsgi:application",
|
||||
|
||||
"centurion.wsgi:application",
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
@ -53,7 +50,29 @@
|
||||
"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": "Make Migrations",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"makemigrations"
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Migrate",
|
||||
"type": "debugpy",
|
||||
@ -64,7 +83,6 @@
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
|
||||
},
|
||||
{
|
||||
"name": "Debug: Celery",
|
||||
@ -82,6 +100,34 @@
|
||||
"debug-itsm@%h"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/app"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug pytest (collect)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "pytest",
|
||||
"args": [
|
||||
"--override-ini", "addopts=",
|
||||
"--collect-only",
|
||||
"app",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false
|
||||
},
|
||||
{
|
||||
"name": "Python Debugger: Local Attach",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "localhost",
|
||||
"port": 5678
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "."
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
@ -5,19 +5,25 @@
|
||||
"!python"
|
||||
],
|
||||
"python.testing.pytestArgs": [
|
||||
// "-v",
|
||||
// "--cov",
|
||||
// "--cov-report xml",
|
||||
"-s",
|
||||
"--override-ini", "addopts=",
|
||||
"--no-migrations",
|
||||
"app",
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"testing.coverageToolbarEnabled": true,
|
||||
"cSpell.words": [
|
||||
"ITSM"
|
||||
],
|
||||
"cSpell.language": "en-AU",
|
||||
"jest.enable": false,
|
||||
"pylint.enabled": true,
|
||||
"testing.showCoverageInExplorer": true,
|
||||
"testing.coverageToolbarEnabled": true,
|
||||
"testing.coverageBarThresholds": {
|
||||
"red": 0,
|
||||
"yellow": 60,
|
||||
"green": 90
|
||||
},
|
||||
"telemetry.feedback.enabled": false,
|
||||
"python.languageServer": "None",
|
||||
}
|
302
CHANGELOG.md
302
CHANGELOG.md
@ -1,3 +1,305 @@
|
||||
## 1.18.0 (2025-07-03)
|
||||
|
||||
### feat
|
||||
|
||||
- **python**: upgrade django 5.1.9 -> 5.1.10
|
||||
|
||||
### Fixes
|
||||
|
||||
- **itim**: Correct config that is in the incorrect format
|
||||
|
||||
## 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
|
||||
|
||||
- **core**: Add ViewSet for Ticket Comments
|
||||
- **project_management**: Depreciate Project Task Ticket Endpoint
|
||||
- **itim**: Depreciate Problem Ticket Endpoint
|
||||
- **itim**: Depreciate Incident Ticket Endpoint
|
||||
- **itim**: Depreciate Change Ticket Endpoint
|
||||
- **assistance**: Depreciate Ticket Comment
|
||||
- **assistance**: Depreciate Request Ticket Endpoint
|
||||
- **core**: Add routes for Ticket Comments
|
||||
- **core**: update ticket serializer to use new comment base url
|
||||
- **core**: Add permissions `import`, `purge` and `triage` to model TicketCommentSolution
|
||||
- **core**: Add permissions `import`, `purge` and `triage` to model TicketCommentBase
|
||||
- **core**: Filter ticket_comment_model routes to those defined in `Meta.sub_model_type`
|
||||
- **core**: Filter ticket_model routes to those defined in `Meta.sub_model_type`
|
||||
- **access**: Filter entity_model routes to thos defined in `Meta.sub_model_type`
|
||||
- **core**: Serializer for TicketCommentBase
|
||||
- **core**: Serializer for TicketCommentSolution
|
||||
- **core**: Ticket Comment Get URL functions
|
||||
- **core**: Ticket Comment Validation for comment_type
|
||||
- **core**: Update choices fields for TicketCommentBase model
|
||||
- **core**: init for model TicketCommentSolution
|
||||
- **core**: Migrations for choice fields for TicketBase model
|
||||
- **core**: Migrations for model TicketCommentSolution
|
||||
- **core**: Update choice fields for TicketBase model
|
||||
- **core**: New model TicketCommentSolution
|
||||
- **api**: when fetching related_object, default to base_model for SubModelViewSet
|
||||
- Add field `Meta.sub_model_type` to sub-models
|
||||
- **core**: New interim model TicketCommentSolution
|
||||
- **core**: add ticket routes
|
||||
- **itim**: serializer for SLMTicketBase
|
||||
- **itim**: Serializer for RequestTicket
|
||||
- **itim**: migrations for RequestTicket
|
||||
- **itim**: New Model RequestTicket
|
||||
- **itim**: migration for SLMTicketBase
|
||||
- **itim**: New Model SLMTicketBase
|
||||
- **core**: migrations for TicketCommentBase
|
||||
- **core**: New Model TicketCommentBase
|
||||
- **core**: viewset for TicketBase
|
||||
- **core**: serializer for TicketBase
|
||||
- **core**: migrations for TicketBase
|
||||
- **core**: New Model TicketBase
|
||||
- **project_management**: add estimation field to project api fields
|
||||
- **human_resources**: nav menu entries for Employee
|
||||
- **human_resources**: Serializer for Employee
|
||||
- **human_resources**: Migration for Employee
|
||||
- **human_resources**: New model Employee
|
||||
- **devops**: add missing api index menu entry for devops
|
||||
- **access**: add missing nav menu entries for entities
|
||||
- **human_resources**: add module to perms selector
|
||||
|
||||
### Fixes
|
||||
|
||||
- **test**: correct typo in attribute parameterized_
|
||||
- **core**: Ticktet comment can have empty body
|
||||
- **core**: If model does not save history, dont attempt to cache before
|
||||
- **itam**: provide return_url as part of software version meta
|
||||
- **itim**: correct ticket_slm serializer
|
||||
- **itim**: correct ticket_request serializer
|
||||
- **api**: SubModelViewSet.related_objects must be the same class as the base model
|
||||
- **access**: Ensure related model is a sub-model
|
||||
- **human_resources**: Correct history link generation and add docs
|
||||
- **human_resources**: Correct history link generation
|
||||
- **access**: add missing attribute to Tenancy object
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **test**: rewrite model unit tests to use PyTest
|
||||
- **test**: Update test parameterization
|
||||
- **api**: SubModelViewSet must inherit from ModelViewSet as it's an extension
|
||||
- **core**: rename ticket model filename in preparation for base ticket model
|
||||
- **access**: migrate sub-model viewset logic to common
|
||||
- **project_management**: add duration field to project api fields
|
||||
- **human_resources**: Move employee details to its own section
|
||||
|
||||
### Tests
|
||||
|
||||
- **core**: Serializer Validation for ticket status change for TicketBase model
|
||||
- **core**: Prevent Closing / Solving of TicketBase Model if not ready
|
||||
- **itim**: Incomplete Model Unit Tests for RequestTicket
|
||||
- **itim**: Incomplete Model Unit Tests for SLMTicketBase
|
||||
- **core**: Incomplete Model Unit Tests for TicketBase
|
||||
- **itim**: RequestTicket Updated, yet incomplete Test Suite for Serializer
|
||||
- **itim**: SLMTicketBase Updated, yet incomplete Test Suite for Serializer
|
||||
- **core**: TicketBase Updated, yet incomplete Test Suite for Serializer
|
||||
- Correct Test Suite for Serializer for models TicketBase, TicketRequest and TicketSLM
|
||||
- **itim**: RequestTicket Initial Test Suite for Serializer
|
||||
- **itim**: SLMTicket Initial Test Suite for Serializer
|
||||
- **core**: TicketBase Initial Test Suite for Serializer
|
||||
- **core**: SLMTicket Test Suite for ViewSet
|
||||
- **core**: SLMTicket Test Suite for Metadata
|
||||
- **core**: Request Test Suite for ViewSet
|
||||
- **core**: Request Test Suite for Metadata
|
||||
- **core**: TicketBase Test Suite for ViewSet
|
||||
- **core**: TicketBase Test Suite for Metadata
|
||||
- **api**: update test cases for SubModelViewSet Base Test Class
|
||||
- **itim**: RequestTicket ViewSet Test Suite
|
||||
- **core**: TicketBase ViewSet Test Suite
|
||||
- **api**: Incomplete SubModelViewSet Test Cases
|
||||
- **api**: SubModelViewSet Test Suite Setup
|
||||
- correct tests from Meta.sub_model_type changes
|
||||
- correct serializer imports from recent file renames
|
||||
- Fixture for creating model with random data
|
||||
- **itim**: API Field checks for TicketSLMBase
|
||||
- **itim**: API Field checks for TicketRequest
|
||||
- **core**: API fields Tests for TicketBase
|
||||
- **core**: API fields Unit Test Suite
|
||||
- **core**: Correct model notes test suite
|
||||
- **core**: API Permission Test Cases for ticket_base model
|
||||
- **api**: add API Permission Test Cases
|
||||
- **access**: Correct history link test cases
|
||||
- **project_management**: Add test cases for api field render for model fields `estimation_project` and `duration_project`
|
||||
- **human_resources**: History Serializer and ViewSet Functional test suites for employee
|
||||
- **human_resources**: APIv2, History, Model and ViewSet Unit test suites for employee
|
||||
- Migrate models to use refactored model tests
|
||||
- Consolidate All model tests to remove duplicates and to simplify
|
||||
|
||||
## 1.15.1 (2025-04-10)
|
||||
|
||||
### Fixes
|
||||
|
@ -1,3 +1,74 @@
|
||||
## 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.
|
||||
|
||||
!!! note
|
||||
Permission migration from the old history models to the new Audit History models are not migrated. As such users whom used to be able to access history models will need to be granted the required permission to view the new Audit History models
|
||||
|
||||
- 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.
|
||||
|
||||
!!! note
|
||||
Permission migration from the old history models to the new Centurion Notes models are not migrated. As such users whom used to be able to access notes models will need to be granted the required permission to view the new Centurion Notes models
|
||||
|
||||
- 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.
|
||||
|
||||
- Ticket and Ticket Comment added behind feature flag `2025-00006` and will remain behind this flag until production ready.
|
||||
|
||||
- In preparation of the [Ticket and Ticket Comment model re-write](https://github.com/nofusscomputing/centurion_erp/issues/564)
|
||||
|
||||
- Depreciated Change Ticket
|
||||
|
||||
- Depreciated Ticket Comment Endpoint
|
||||
|
||||
- Depreciated Request Ticket
|
||||
|
||||
- Depreciated Incident Ticket
|
||||
|
||||
- Depreciated Problem Ticket
|
||||
|
||||
- Depreciated Project Task Ticket
|
||||
|
||||
These endpoints still work and will remain so until the new Ticket and Ticket Comment Models are production ready.
|
||||
|
||||
|
||||
## Version 1.15.0
|
||||
|
||||
- Entities model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
||||
|
@ -1,17 +0,0 @@
|
||||
[run]
|
||||
source = .
|
||||
omit =
|
||||
*migrations/*
|
||||
*tests/*/*
|
||||
|
||||
[report]
|
||||
omit =
|
||||
*/tests/*/*
|
||||
*/migrations/*
|
||||
*apps.py
|
||||
*manage.py
|
||||
*__init__.py
|
||||
*asgi*
|
||||
*wsgi*
|
||||
*admin.py
|
||||
*urls.py
|
@ -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',
|
||||
]
|
@ -1,4 +1,10 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.models import (
|
||||
ContentType,
|
||||
Permission
|
||||
)
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def permission_queryset():
|
||||
"""Filter Permissions to those used within the application
|
||||
@ -7,13 +13,15 @@ def permission_queryset():
|
||||
list: Filtered queryset that only contains the used permissions
|
||||
"""
|
||||
|
||||
apps = [
|
||||
centurion_apps = [
|
||||
'access',
|
||||
'accounting',
|
||||
'assistance',
|
||||
'config_management',
|
||||
'core',
|
||||
'devops',
|
||||
'django_celery_results',
|
||||
'human_resources',
|
||||
'itam',
|
||||
'itim',
|
||||
'project_management',
|
||||
@ -35,22 +43,65 @@ 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',
|
||||
]
|
||||
|
||||
|
||||
if not settings.RUNNING_TESTS:
|
||||
|
||||
models = apps.get_models()
|
||||
|
||||
for model in models:
|
||||
|
||||
if(
|
||||
not str(model._meta.object_name).endswith('AuditHistory')
|
||||
and not str(model._meta.model_name).lower().endswith('history')
|
||||
):
|
||||
# check `endswith('history')` can be removed when the old history models are removed
|
||||
continue
|
||||
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = model._meta.app_label,
|
||||
model = model._meta.model_name
|
||||
)
|
||||
|
||||
permissions = Permission.objects.filter(
|
||||
content_type = content_type,
|
||||
)
|
||||
|
||||
for permission in permissions:
|
||||
|
||||
if(
|
||||
not permission.codename == 'view_' + str(model._meta.model_name)
|
||||
and str(model._meta.object_name).endswith('AuditHistory')
|
||||
):
|
||||
exclude_permissions += [ permission.codename ]
|
||||
|
||||
elif(
|
||||
not str(model._meta.object_name).endswith('AuditHistory')
|
||||
and str(model._meta.model_name).lower().endswith('history')
|
||||
):
|
||||
# This `elif` can be removed when the old history models are removed
|
||||
|
||||
exclude_permissions += [ permission.codename ]
|
||||
|
||||
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in=apps,
|
||||
content_type__app_label__in = centurion_apps,
|
||||
).exclude(
|
||||
content_type__model__in=exclude_models
|
||||
content_type__model__in = exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
@ -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",),
|
||||
),
|
||||
]
|
@ -0,0 +1,81 @@
|
||||
# Generated by Django 5.1.10 on 2025-07-06 10:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0018_remove_entity_is_global_alter_entity_id_and_more"),
|
||||
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="CompanyAuditHistory",
|
||||
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.company",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Company History",
|
||||
"verbose_name_plural": "Company Histories",
|
||||
"db_table": "access_company_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="CompanyCenturionModelNote",
|
||||
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.company",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Company Note",
|
||||
"verbose_name_plural": "Company Notes",
|
||||
"db_table": "access_company_centurionmodelnote",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionmodelnote",),
|
||||
),
|
||||
]
|
@ -0,0 +1,56 @@
|
||||
# Generated by Django 5.1.10 on 2025-07-12 07:20
|
||||
|
||||
import access.models.tenancy_abstract
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0019_companyaudithistory_companycenturionmodelnote"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="role",
|
||||
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="role",
|
||||
name="model_notes",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="Tid bits of information",
|
||||
null=True,
|
||||
verbose_name="Notes",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="role",
|
||||
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.DeleteModel(
|
||||
name="RoleHistory",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="RoleNotes",
|
||||
),
|
||||
]
|
@ -0,0 +1,81 @@
|
||||
# Generated by Django 5.1.10 on 2025-07-12 08:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0020_remove_rolenotes_model_and_more"),
|
||||
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="RoleAuditHistory",
|
||||
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.role",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Role History",
|
||||
"verbose_name_plural": "Role Histories",
|
||||
"db_table": "access_role_audithistory",
|
||||
"managed": True,
|
||||
},
|
||||
bases=("core.centurionaudit",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="RoleCenturionModelNote",
|
||||
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.role",
|
||||
verbose_name="Model",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Role Note",
|
||||
"verbose_name_plural": "Role Notes",
|
||||
"db_table": "access_role_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
|
||||
)
|
||||
|
||||
@ -291,6 +290,11 @@ class OrganizationPermissionMixin(
|
||||
view.model.__name__ == 'AuthToken'
|
||||
and request._user.id == int(view.kwargs.get('model_id', 0))
|
||||
)
|
||||
or ( # org=None is the application wide settings.
|
||||
view.model.__name__ == 'AppSettings'
|
||||
and request.user.is_superuser
|
||||
and obj.organization is None
|
||||
)
|
||||
):
|
||||
|
||||
return True
|
||||
|
@ -1,2 +1,3 @@
|
||||
from . import contact
|
||||
from . import person
|
||||
from .organization_history import OrganizationHistory # pylint: disable=W0611:unused-import
|
||||
|
||||
from .organization_notes import OrganizationNotes # 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:
|
||||
|
||||
@ -15,6 +19,8 @@ class Contact(
|
||||
'email',
|
||||
]
|
||||
|
||||
sub_model_type = 'contact'
|
||||
|
||||
verbose_name = 'Contact'
|
||||
|
||||
verbose_name_plural = 'Contacts'
|
||||
@ -40,8 +46,6 @@ class Contact(
|
||||
|
||||
return self.f_name + ' ' + self.l_name
|
||||
|
||||
documentation = ''
|
||||
|
||||
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:
|
||||
|
||||
@ -22,36 +27,27 @@ class Entity(
|
||||
'organization',
|
||||
]
|
||||
|
||||
sub_model_type = 'entity'
|
||||
|
||||
verbose_name = '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:
|
||||
@ -62,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',
|
||||
@ -87,21 +69,41 @@ 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')
|
||||
|
||||
for related_object in getattr(meta, 'related_objects', []):
|
||||
|
||||
if not issubclass(related_object.related_model, Entity):
|
||||
|
||||
continue
|
||||
|
||||
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 ''
|
||||
@ -125,117 +127,17 @@ class Entity(
|
||||
|
||||
if related_model_name == '':
|
||||
|
||||
return None
|
||||
related_model = None
|
||||
|
||||
elif related_model is None:
|
||||
|
||||
return self
|
||||
related_model = self
|
||||
|
||||
elif related_model.get_related_field_name() != '':
|
||||
elif hasattr(related_model, 'get_related_field_name'):
|
||||
|
||||
related_model = related_model.get_related_model()
|
||||
if related_model.get_related_field_name() != '':
|
||||
|
||||
related_model = related_model.get_related_model()
|
||||
|
||||
|
||||
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,55 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from devops.models.feature_flag import FeatureFlag
|
||||
|
||||
|
||||
|
||||
class EntityHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_entity_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Entity History'
|
||||
|
||||
verbose_name_plural = 'Entity History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Entity,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.entity import BaseSerializer
|
||||
|
||||
model = BaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
@ -1,45 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class EntityNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_entity_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Entity Note'
|
||||
|
||||
verbose_name_plural = 'Entity Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Entity,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
return {
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
@ -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:
|
||||
|
||||
@ -20,6 +24,8 @@ class Person(
|
||||
'dob',
|
||||
]
|
||||
|
||||
sub_model_type = 'person'
|
||||
|
||||
verbose_name = 'Person'
|
||||
|
||||
verbose_name_plural = 'People'
|
||||
@ -34,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,
|
||||
@ -52,7 +57,6 @@ class Person(
|
||||
|
||||
dob = models.DateField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'The Persons Date of Birth (DOB)',
|
||||
null = True,
|
||||
unique = False,
|
||||
@ -63,8 +67,6 @@ class Person(
|
||||
|
||||
return self.f_name + ' ' + self.l_name + f' (DOB: {self.dob})'
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
table_fields: list = [
|
||||
@ -100,7 +102,7 @@ class Person(
|
||||
|
||||
|
||||
for entry in duplicate_entry:
|
||||
|
||||
|
||||
if(
|
||||
entry.f_name == self.f_name
|
||||
and entry.m_name == self.m_name
|
||||
@ -110,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'
|
||||
)
|
||||
|
||||
|
@ -1,15 +1,20 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import models
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
from access.models.tenancy import TenancyObject
|
||||
from access.fields import AutoLastModifiedField
|
||||
|
||||
from core.models.centurion import CenturionModel
|
||||
|
||||
|
||||
|
||||
class Role(
|
||||
TenancyObject
|
||||
CenturionModel
|
||||
):
|
||||
|
||||
documentation = ''
|
||||
|
||||
model_tag = 'role'
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -28,14 +33,6 @@ class Role(
|
||||
verbose_name_plural = 'Roles'
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'Primary key of the entry',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this role',
|
||||
@ -53,21 +50,15 @@ class Role(
|
||||
verbose_name = 'Permissions'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
is_global = None
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
|
||||
return str( self.organization ) + ' / ' + self.name
|
||||
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
@ -130,14 +121,29 @@ class Role(
|
||||
]
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
_permissions: list[ Permission ] = None
|
||||
|
||||
from access.models.role_history import RoleHistory
|
||||
_permissions_int: list[ int ] = None
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = RoleHistory
|
||||
)
|
||||
def get_permissions(self, as_int_list = False ):
|
||||
|
||||
return history
|
||||
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
|
||||
|
@ -1,53 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from access.models.role import Role
|
||||
|
||||
|
||||
|
||||
class RoleHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_role_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Role History'
|
||||
|
||||
verbose_name_plural = 'Role History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Role,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.role import BaseSerializer
|
||||
|
||||
model = BaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
@ -1,45 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.role import Role
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class RoleNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_role_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Role Note'
|
||||
|
||||
verbose_name_plural = 'Role Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Role,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
return {
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
@ -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
|
||||
@ -27,7 +27,9 @@ class Team(Group, TenancyObject):
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
self.name = self.organization.name.lower().replace(' ', '_') + '_' + self.team_name.lower().replace(' ', '_')
|
||||
if self.organization_id:
|
||||
|
||||
self.name = self.organization.name.lower().replace(' ', '_') + '_' + self.team_name.lower().replace(' ', '_')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
@ -53,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()
|
||||
@ -177,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,18 @@ 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
|
||||
"""URL BaseName for the notes endpoint.
|
||||
|
||||
@ -218,7 +236,7 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
if self.app_namespace:
|
||||
|
||||
app_namespace = self.app_namespace + ':'
|
||||
app_namespace = self.app_namespace
|
||||
|
||||
return str(app_namespace)
|
||||
|
||||
@ -237,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:
|
||||
@ -280,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_role.py
Normal file
56
app/access/serializers/centurionaudit_role.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 RoleAuditHistory # 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 = 'RoleAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = RoleAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleAuditHistoryViewSerializer')
|
||||
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_role.py
Normal file
87
app/access/serializers/centurionmodelnote_role.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 RoleCenturionModelNote # 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 = 'RoleModelNoteModelSerializer')
|
||||
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 = RoleCenturionModelNote
|
||||
|
||||
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 = 'RoleModelNoteViewSerializer')
|
||||
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)
|
@ -2,11 +2,11 @@ from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
from access.serializers.person import (
|
||||
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,101 +0,0 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from access.models.tenancy import TenancyManager
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_has_attr_get_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has function get_organization
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'get_organization')
|
||||
|
||||
|
||||
def test_has_attr_is_global(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has field is_global
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'is_global')
|
||||
|
||||
|
||||
|
||||
def test_has_attr_model_notes(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has field model_notes
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'model_notes')
|
||||
|
||||
|
||||
|
||||
def test_has_attr_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has field organization
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'organization')
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_create_no_organization_fails(self):
|
||||
""" Devices must be assigned an organization
|
||||
|
||||
Must not be able to create an item without an organization
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
def test_has_attr_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has function objects
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'objects')
|
||||
|
||||
|
||||
def test_attribute_is_type_objects(self):
|
||||
""" Attribute Check
|
||||
|
||||
attribute `objects` must be set to `access.models.TenancyManager()`
|
||||
"""
|
||||
|
||||
assert type(self.model.objects) is TenancyManager
|
@ -0,0 +1,204 @@
|
||||
import pytest
|
||||
import random
|
||||
|
||||
from django.test import Client
|
||||
|
||||
|
||||
class AdditionalTestCases:
|
||||
|
||||
|
||||
def test_permission_add(self, model_instance, api_request_permissions,
|
||||
model_kwargs, kwargs_api_create
|
||||
):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['add'] )
|
||||
|
||||
the_model = model_instance( kwargs_create = self.kwargs_create_item )
|
||||
|
||||
url = the_model.get_url( many = True )
|
||||
|
||||
the_model.delete()
|
||||
|
||||
kwargs = kwargs_api_create.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
response = client.post(
|
||||
path = url,
|
||||
data = kwargs,
|
||||
content_type = 'application/json'
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 201, response.content
|
||||
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self, model_instance, model_kwargs, api_request_permissions):
|
||||
"""Returned results check
|
||||
|
||||
Ensure that a query to the viewset endpoint does not return
|
||||
items that are not part of the users organizations.
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
]
|
||||
|
||||
if getattr(self, 'global_organization', None):
|
||||
# Cater for above test that also has global org
|
||||
|
||||
viewable_organizations += [ api_request_permissions['tenancy']['global'] ]
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
# if response.status_code == 405:
|
||||
# pytest.xfail( reason = 'ViewSet does not have this request method.' )
|
||||
|
||||
# elif IsAuthenticatedOrReadOnly in response.renderer_context['view'].permission_classes:
|
||||
|
||||
# pytest.xfail( reason = 'ViewSet is public viewable, test is N/A' )
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
contains_different_org: bool = False
|
||||
|
||||
for item in response.data['results']:
|
||||
|
||||
if 'organization' not in item:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if(
|
||||
int(item['organization']['id']) not in viewable_organizations
|
||||
and
|
||||
int(item['organization']['id']) != api_request_permissions['tenancy']['global'].id
|
||||
):
|
||||
|
||||
contains_different_org = True
|
||||
print(f'Failed returned row was: {item}')
|
||||
|
||||
assert not contains_different_org
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(
|
||||
self, model_instance, model_kwargs, api_request_permissions
|
||||
):
|
||||
"""Check items returned
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
client = Client()
|
||||
|
||||
only_from_user_org: bool = True
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
api_request_permissions['tenancy']['global'].id
|
||||
]
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
assert len(response.data['results']) >= 2 # fail if only one item extist.
|
||||
|
||||
|
||||
for row in response.data['results']:
|
||||
|
||||
if 'organization' not in row:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if row['organization']['id'] not in viewable_organizations:
|
||||
|
||||
only_from_user_org = False
|
||||
|
||||
print(f"Users org: {api_request_permissions['tenancy']['user'].id}")
|
||||
print(f"global org: {api_request_permissions['tenancy']['global'].id}")
|
||||
print(f'Failed returned row was: {row}')
|
||||
|
||||
assert only_from_user_org
|
204
app/access/tests/functional/additional_person_permissions_api.py
Normal file
204
app/access/tests/functional/additional_person_permissions_api.py
Normal file
@ -0,0 +1,204 @@
|
||||
import pytest
|
||||
import random
|
||||
|
||||
from django.test import Client
|
||||
|
||||
|
||||
class AdditionalTestCases:
|
||||
|
||||
|
||||
def test_permission_add(self, model_instance, api_request_permissions,
|
||||
model_kwargs, kwargs_api_create
|
||||
):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['add'] )
|
||||
|
||||
the_model = model_instance( kwargs_create = self.kwargs_create_item )
|
||||
|
||||
url = the_model.get_url( many = True )
|
||||
|
||||
the_model.delete()
|
||||
|
||||
kwargs = kwargs_api_create.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
response = client.post(
|
||||
path = url,
|
||||
data = kwargs,
|
||||
content_type = 'application/json'
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 201, response.content
|
||||
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self, model_instance, model_kwargs, api_request_permissions):
|
||||
"""Returned results check
|
||||
|
||||
Ensure that a query to the viewset endpoint does not return
|
||||
items that are not part of the users organizations.
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
]
|
||||
|
||||
if getattr(self, 'global_organization', None):
|
||||
# Cater for above test that also has global org
|
||||
|
||||
viewable_organizations += [ api_request_permissions['tenancy']['global'] ]
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
# if response.status_code == 405:
|
||||
# pytest.xfail( reason = 'ViewSet does not have this request method.' )
|
||||
|
||||
# elif IsAuthenticatedOrReadOnly in response.renderer_context['view'].permission_classes:
|
||||
|
||||
# pytest.xfail( reason = 'ViewSet is public viewable, test is N/A' )
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
contains_different_org: bool = False
|
||||
|
||||
for item in response.data['results']:
|
||||
|
||||
if 'organization' not in item:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if(
|
||||
int(item['organization']['id']) not in viewable_organizations
|
||||
and
|
||||
int(item['organization']['id']) != api_request_permissions['tenancy']['global'].id
|
||||
):
|
||||
|
||||
contains_different_org = True
|
||||
print(f'Failed returned row was: {item}')
|
||||
|
||||
assert not contains_different_org
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(
|
||||
self, model_instance, model_kwargs, api_request_permissions
|
||||
):
|
||||
"""Check items returned
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
client = Client()
|
||||
|
||||
only_from_user_org: bool = True
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
api_request_permissions['tenancy']['global'].id
|
||||
]
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
assert len(response.data['results']) >= 2 # fail if only one item extist.
|
||||
|
||||
|
||||
for row in response.data['results']:
|
||||
|
||||
if 'organization' not in row:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if row['organization']['id'] not in viewable_organizations:
|
||||
|
||||
only_from_user_org = False
|
||||
|
||||
print(f"Users org: {api_request_permissions['tenancy']['user'].id}")
|
||||
print(f"global org: {api_request_permissions['tenancy']['global'].id}")
|
||||
print(f'Failed returned row was: {row}')
|
||||
|
||||
assert only_from_user_org
|
@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
|
||||
from django.test import Client
|
||||
|
||||
|
||||
class AdditionalTestCases:
|
||||
|
||||
|
||||
def test_permission_add(self, model_instance, api_request_permissions,
|
||||
model_kwargs, kwargs_api_create
|
||||
):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['add'] )
|
||||
|
||||
the_model = model_instance( kwargs_create = model_kwargs )
|
||||
|
||||
url = the_model.get_url( many = True )
|
||||
|
||||
response = client.post(
|
||||
path = url,
|
||||
data = kwargs_api_create,
|
||||
content_type = 'application/json'
|
||||
)
|
||||
|
||||
assert response.status_code == 201, response.content
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(
|
||||
self
|
||||
):
|
||||
"""Check items returned
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
|
||||
pytest.mark.xfail( reason = 'model is not for global use' )
|
35
app/access/tests/functional/company/conftest.py
Normal file
35
app/access/tests/functional/company/conftest.py
Normal file
@ -0,0 +1,35 @@
|
||||
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
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_company):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_company.copy()
|
||||
|
||||
yield kwargs_company.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
@ -0,0 +1,36 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_api_fields import (
|
||||
EntityAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.model_company
|
||||
class CompanyAPITestCases(
|
||||
EntityAPIInheritedCases,
|
||||
):
|
||||
|
||||
@property
|
||||
def parameterized_api_fields(self):
|
||||
|
||||
return {
|
||||
'name': {
|
||||
'expected': str
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CompanyAPIInheritedCases(
|
||||
CompanyAPITestCases,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.module_access
|
||||
class CompanyAPIPyTest(
|
||||
CompanyAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -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
|
34
app/access/tests/functional/contact/conftest.py
Normal file
34
app/access/tests/functional/contact/conftest.py
Normal file
@ -0,0 +1,34 @@
|
||||
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
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_contact):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_contact.copy()
|
||||
|
||||
yield kwargs_contact.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
@ -0,0 +1,41 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.person.test_functional_person_api_fields import (
|
||||
PersonAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.model_contact
|
||||
class ContactAPITestCases(
|
||||
PersonAPIInheritedCases,
|
||||
):
|
||||
|
||||
property
|
||||
def parameterized_api_fields(self):
|
||||
|
||||
return {
|
||||
'email': {
|
||||
'expected': str
|
||||
},
|
||||
'directory': {
|
||||
'expected': bool
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ContactAPIInheritedCases(
|
||||
ContactAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.module_access
|
||||
class ContactAPIPyTest(
|
||||
ContactAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -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.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
|
||||
|
34
app/access/tests/functional/entity/conftest.py
Normal file
34
app/access/tests/functional/entity/conftest.py
Normal file
@ -0,0 +1,34 @@
|
||||
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
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_entity):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_entity.copy()
|
||||
|
||||
yield kwargs_entity.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
@ -0,0 +1,48 @@
|
||||
import pytest
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from api.tests.functional.test_functional_api_fields import (
|
||||
APIFieldsInheritedCases,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.model_entity
|
||||
class EntityAPITestCases(
|
||||
APIFieldsInheritedCases,
|
||||
):
|
||||
|
||||
base_model = Entity
|
||||
|
||||
|
||||
@property
|
||||
def parameterized_api_fields(self):
|
||||
|
||||
return {
|
||||
'entity_type': {
|
||||
'expected': str
|
||||
},
|
||||
'_urls.history': {
|
||||
'expected': str
|
||||
},
|
||||
'_urls.knowledge_base': {
|
||||
'expected': str
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class EntityAPIInheritedCases(
|
||||
EntityAPITestCases,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.module_access
|
||||
class EntityAPIPyTest(
|
||||
EntityAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user