Compare commits
1163 Commits
Author | SHA1 | Date | |
---|---|---|---|
aaaf902759 | |||
c6f382bb0f | |||
ea58248be2 | |||
e89b4f39b2 | |||
59a79519c0 | |||
9581791fd6 | |||
48adbd7021 | |||
fcb2c39e4d | |||
b391bcbb9f | |||
1b004bee2d | |||
594e77fc77 | |||
89970dc4e2 | |||
190e4b4a98 | |||
8d6d1d258d | |||
b32d7f302e | |||
089480620e | |||
bc9d6b74fd | |||
525da2fbe0 | |||
ecc16e6cbf | |||
e1f8ba0c2b | |||
037fbabeae | |||
6732315b96 | |||
3131258491 | |||
6ff4779cfb | |||
1c83547aaa | |||
40f8e4d8b1 | |||
7b009f7378 | |||
de3934a761 | |||
1b7d108a29 | |||
ca7a9baab8 | |||
274c32c673 | |||
e806a5652e | |||
703b6a67b2 | |||
0159fd6ed8 | |||
2b8513abf0 | |||
146dd508d6 | |||
20f5c3b5d7 | |||
d340fb3375 | |||
bc3f1e8a68 | |||
44adc6c8ab | |||
6cb66db46f | |||
a08ea057cc | |||
30605d7998 | |||
121be79e03 | |||
c4ca06fba4 | |||
9271702a62 | |||
861bef0ce1 | |||
3b86ab0e88 | |||
9f62e2a458 | |||
bd393e3dd4 | |||
9c73885f1b | |||
dbac9326b3 | |||
dd0a6a01b9 | |||
c294f9c8f5 | |||
71a510170d | |||
d577b12a33 | |||
fb090b6f63 | |||
75cda05579 | |||
97e63f1daa | |||
dce169109c | |||
4c6473a7b0 | |||
204a20b793 | |||
bf1a60439f | |||
006d7ab0c2 | |||
cca693d02d | |||
bb88a7025d | |||
8da10a147b | |||
653e29ffe5 | |||
b13610ef0e | |||
8739eba655 | |||
1b338e4c19 | |||
f525411ace | |||
f5f4eb3ff2 | |||
b7c68694a6 | |||
363d044851 | |||
b66adb2ebe | |||
4037127c39 | |||
1640910305 | |||
d0effd582a | |||
ad175f4f7d | |||
6dc42d4cdb | |||
3f08bf532f | |||
9cde656450 | |||
62da0f8c7a | |||
4045f0cffb | |||
6eb4cb12ba | |||
2d720032f6 | |||
bb7cf9d462 | |||
d53eb7101d | |||
0edfaef79a | |||
0c508a7e0b | |||
2bdb12ed7a | |||
187f82feaf | |||
91082b205d | |||
b7d190218c | |||
2daf7dbb27 | |||
f98d2f2884 | |||
fa0b703877 | |||
1b7d92e300 | |||
697d375d06 | |||
3726f3aee9 | |||
28cd56d9a0 | |||
017f5de78a | |||
d8fa00b530 | |||
3765113445 | |||
8f1a9966e9 | |||
e3f6b3f9ad | |||
422acd64fa | |||
dc01c6b0f9 | |||
e24a135616 | |||
6f82a054cd | |||
ecafbcf830 | |||
1c8f641dd4 | |||
a3f4299299 | |||
173a47d942 | |||
24c4607c2d | |||
d68afd8992 | |||
38c9cccf30 | |||
c2344e95c5 | |||
6c1542f572 | |||
10550495d8 | |||
d241a9a683 | |||
1b8be14f12 | |||
4c49bb0bd2 | |||
6578d67250 | |||
0f9cbfdc13 | |||
fc984c5034 | |||
7d47a9ca49 | |||
2890750d50 | |||
c1b44c03fe | |||
207ba2285a | |||
dd337b68bc | |||
42e191ba76 | |||
dfb1af1ff7 | |||
df8265dd55 | |||
e14780eb22 | |||
9104cd021f | |||
5db10a4b6d | |||
6faa185406 | |||
7fb56d87d6 | |||
cf09a15690 | |||
76dfcb8bce | |||
430cbe6385 | |||
bcce98e884 | |||
66706df727 | |||
838bc871c1 | |||
69f1c1c3b6 | |||
8d0d9240a8 | |||
d92f967bb9 | |||
7413a5686d | |||
3d4018e306 | |||
93dc76fc1a | |||
f461b7500f | |||
4b1548afa8 | |||
3fa8bdf468 | |||
8363c50b91 | |||
3bd673b2ae | |||
e9bc7e543a | |||
20694b950a | |||
095deacf65 | |||
5e1ebc949e | |||
9b3340f44c | |||
c93dbf48c9 | |||
6414f70468 | |||
9e24bf4eed | |||
565996bc2a | |||
3bc35777ca | |||
0bc313c198 | |||
ff1085df24 | |||
b6743ebe4c | |||
701193ea82 | |||
55408f1280 | |||
b84c3d1b14 | |||
b0597d354a | |||
c32cc6c954 | |||
b84571bd44 | |||
3a5630995b | |||
ee5055950d | |||
9e6322b5f1 | |||
a805660f0f | |||
7bce005a05 | |||
811c92ada6 | |||
e3b9cba23c | |||
6f58f2985c | |||
98891be181 | |||
bb93b4ebed | |||
a82ff5a41f | |||
224a69319e | |||
854b295807 | |||
fc0b645d2d | |||
4b5caad7d5 | |||
5f5d7923c6 | |||
099c70d466 | |||
2ff5c345e8 | |||
9f20fe9614 | |||
cf5b78b6b2 | |||
cc481f94e9 | |||
3248d5201d | |||
283051a4ad | |||
03d320f6a0 | |||
83ebf12978 | |||
09c1acbb1f | |||
4fc58c5ea1 | |||
c476548aa3 | |||
1812746a02 | |||
78cf8522e3 | |||
9e7c4a8191 | |||
fa1cee32cf | |||
ca3af15847 | |||
c0c65903f4 | |||
c19fe7f874 | |||
05d87c93e6 | |||
ca661c9ebb | |||
2a7bf9c57b | |||
5794d88ce4 | |||
73e3781fe6 | |||
f95639681d | |||
c99166fffa | |||
850e183f45 | |||
10569b6a46 | |||
464bf0da1d | |||
123828d96d | |||
a72e153aa3 | |||
3ed7471c15 | |||
c85b666a84 | |||
6baeb12be6 | |||
2769859db0 | |||
209c01f5a3 | |||
fc5490afe0 | |||
83b1ca577b | |||
04409ed2c8 | |||
7018c2d963 | |||
6c83d0f0d7 | |||
b0cc4b8691 | |||
d7f231c831 | |||
96ec149017 | |||
a623056325 | |||
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 |
2
.cz.yaml
2
.cz.yaml
@ -17,5 +17,5 @@ commitizen:
|
||||
prerelease_offset: 1
|
||||
tag_format: $version
|
||||
update_changelog_on_bump: false
|
||||
version: 1.16.0
|
||||
version: 1.19.0
|
||||
version_scheme: semver
|
||||
|
55
.github/ISSUE_TEMPLATE/new_model.md
vendored
55
.github/ISSUE_TEMPLATE/new_model.md
vendored
@ -33,39 +33,22 @@ Describe in detail the following:
|
||||
|
||||
- [ ] 🛠️ Migrations added
|
||||
|
||||
- [ ] ♻️ Serializer Created
|
||||
|
||||
- [ ] 🔄 [ViewSet Created](https://nofusscomputing.com/projects/centurion_erp/development/views/)
|
||||
|
||||
- [ ] 🔗 URL Route Added
|
||||
|
||||
- [ ] 🏷️ Model tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()` function
|
||||
- [ ] 🏷️ [Model tag]().
|
||||
|
||||
- [ ] 📘 Tag updated in the [docs](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
- [ ] tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()`
|
||||
- [ ] ⚒️ Migration _Ticket Linked Item item_type choices update_
|
||||
|
||||
>[!note]
|
||||
> Ensure that when creating the tag the following is adhered to:
|
||||
> - Two words are not to contain a space char, `\s`. It is to be replaced with an underscore `_`
|
||||
> - As much as practical, keep the tag as close to the model name as possible
|
||||
|
||||
- [ ] 📝 New [History model](https://nofusscomputing.com/projects/centurion_erp/development/core/model_history/) created
|
||||
|
||||
- Sub-Models **_ONLY_**
|
||||
|
||||
- [ ] Model class variable `history_app_label` set to correct application label
|
||||
|
||||
- [ ] Model class variable `history_model_name` set to correct model label
|
||||
|
||||
- [ ] 📓 New [Notes model](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/) created
|
||||
- [ ] 🆕 Model Created
|
||||
- [ ] 🛠️ Migrations added
|
||||
- [ ] Add `app_label` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().model_apps`
|
||||
- [ ] _(Notes not used/required) -_ Add `model_name` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().excluded_models`
|
||||
- [ ] 🧪 [Unit tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
- [ ] 🧪 [Functional tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
- [ ] tag added to class
|
||||
|
||||
- [ ] Admin Documentation added/updated _if applicable_
|
||||
|
||||
- [ ] Developer Documentation added/updated _if applicable_
|
||||
|
||||
- [ ] User Documentation added/updated
|
||||
|
||||
---
|
||||
@ -78,15 +61,15 @@ Describe in detail the following:
|
||||
|
||||
- Unit Tests
|
||||
- [ ] [Model](https://nofusscomputing.com/projects/centurion_erp/development/models/#tests)
|
||||
- [ ] ViewSet
|
||||
- [ ] Serializer
|
||||
- [ ] ViewSet
|
||||
- Function Test
|
||||
- [ ] ViewSet
|
||||
- [ ] API Metadata
|
||||
- [ ] API Permissions
|
||||
- [ ] API Render (fields)
|
||||
- [ ] History Entries
|
||||
- [ ] History API Render (fields)
|
||||
- [ ] Model
|
||||
- [ ] Serializer
|
||||
- [ ] ViewSet
|
||||
|
||||
|
||||
## ✅ Requirements
|
||||
@ -95,6 +78,24 @@ A Requirement is a must have. In addition will also be tested.
|
||||
|
||||
- [ ] Must have a [model_tag](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
|
||||
<!--
|
||||
|
||||
When detailing requirements the following must be taken into account:
|
||||
|
||||
- what the user should be able to do
|
||||
|
||||
- what the user should not be able to do
|
||||
|
||||
- what should occur when a user performs an action
|
||||
|
||||
-->
|
||||
|
||||
- Functional Requirements
|
||||
|
||||
|
||||
- Non-Functional Requirements
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- Add additional requirement here and as a check box list -->
|
||||
|
13
.github/workflows/ci.yaml
vendored
13
.github/workflows/ci.yaml
vendored
@ -46,6 +46,19 @@ jobs:
|
||||
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
|
||||
integration-test:
|
||||
name: 'Integration Test'
|
||||
uses: nofusscomputing/action_python/.github/workflows/python-integration.yaml@development
|
||||
needs:
|
||||
- docker
|
||||
with:
|
||||
POSTGRES_VERSIONS: '[ "13", "14", "15", "16", "17" ]'
|
||||
PYTHON_VERSION: '3.11'
|
||||
RABBITMQ_VERSIONS: '[ "3.12", "3.13", "4.0", "4.1" ]'
|
||||
secrets:
|
||||
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
|
||||
gitlab-mirror:
|
||||
if: ${{ github.repository == 'nofusscomputing/centurion_erp' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,9 +1,10 @@
|
||||
venv/**
|
||||
*/static/**
|
||||
__pycache__
|
||||
**.sqlite3
|
||||
**.sqlite*
|
||||
**.sqlite
|
||||
**.coverage
|
||||
.coverage*
|
||||
artifacts/
|
||||
**.tmp.*
|
||||
volumes/
|
||||
@ -19,3 +20,9 @@ package.json
|
||||
feature_flags.json
|
||||
coverage_*.json
|
||||
*-coverage.xml
|
||||
log/
|
||||
# Integration testing
|
||||
app/artifacts/
|
||||
app/pyproject.toml
|
||||
app/histogram_**
|
||||
app/counter_**
|
||||
|
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",
|
||||
]
|
||||
}
|
25
.vscode/launch.json
vendored
25
.vscode/launch.json
vendored
@ -29,7 +29,7 @@
|
||||
"3",
|
||||
"--bind",
|
||||
"0.0.0.0:8002",
|
||||
"app.wsgi:application",
|
||||
"centurion.wsgi:application",
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
@ -50,6 +50,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",
|
||||
|
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@ -6,6 +6,7 @@
|
||||
],
|
||||
"python.testing.pytestArgs": [
|
||||
"--override-ini", "addopts=",
|
||||
"--no-migrations",
|
||||
"app",
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
@ -23,4 +24,15 @@
|
||||
"yellow": 60,
|
||||
"green": 90
|
||||
},
|
||||
"telemetry.feedback.enabled": false,
|
||||
"python.languageServer": "None",
|
||||
"debug.javascript.enableNetworkView": false,
|
||||
"typescript.experimental.expandableHover": false,
|
||||
"ipynb.experimental.serialization": false,
|
||||
"notebook.experimental.generate": false,
|
||||
"extensions.experimental.issueQuickAccess": false,
|
||||
"workbench.commandPalette.experimental.enableNaturalLanguageSearch": false,
|
||||
"multiDiffEditor.experimental.enabled": false,
|
||||
"diffEditor.experimental.showEmptyDecorations": false,
|
||||
"editor.experimental.asyncTokenization": false,
|
||||
}
|
878
CHANGELOG.md
878
CHANGELOG.md
@ -1,3 +1,881 @@
|
||||
## 1.19.0 (2025-08-15)
|
||||
|
||||
### feat
|
||||
|
||||
- **core**: add to migration signal system user and use for inventory objects
|
||||
- **docker**: Adjust gunicorn works=4 100reqs/max and preload app
|
||||
- **api**: Ensure that serializer converts Django exceptions to rest_framework exceptions
|
||||
- **access**: Filter history permissions
|
||||
- **access**: Add Audit and notes tables for model Role
|
||||
- **access**: Add AuditHistory Serializer for Role model
|
||||
- **access**: Add Notes Serializer for Role model
|
||||
- **itam**: Add AuditHistory Serializer for ITAMAssetBase model
|
||||
- **itam**: Add Notes Serializer for ITAMAssetBase model
|
||||
- **accounting**: Add AuditHistory Serializer for AssetBase model
|
||||
- **accounting**: Add Notes Serializer for AssetBase model
|
||||
- When attempting to create and objetc must be unique and alrready exists, dont return error return existing object
|
||||
- **access**: History + Notes model migrations for Company Model
|
||||
- **api**: map notfound and perm denied django -> drf exceptions
|
||||
- **human_resources**: Add model tag for Employee model
|
||||
- **human_resources**: Add AuditHistory Serializer for Employee model
|
||||
- **human_resources**: Add Notes Serializer for Employee model
|
||||
- **human_resources**: Change model to inherit from `CenturionModel` for Employee model
|
||||
- **access**: Add model tag for Person model
|
||||
- **access**: Add AuditHistory Serializer for Person model
|
||||
- **access**: Add Notes Serializer for Person model
|
||||
- **access**: Change model to inherit from `CenturionModel` for Person model
|
||||
- **access**: Add model tag for Contact model
|
||||
- **access**: Add AuditHistory Serializer for Contact model
|
||||
- **access**: Add Notes Serializer for Contact model
|
||||
- **access**: Change model to inherit from `CenturionModel` for Contact model
|
||||
- **access**: Add AuditHistory Serializer for Company model
|
||||
- **access**: Add Notes Serializer for Entity model
|
||||
- **access**: Change model to inherit from `CenturionModel` for Company model
|
||||
- **access**: Change model to inherit from `CenturionModel` for Entity model
|
||||
- **access**: Add AuditHistory Serializer for Entity model
|
||||
- **access**: Add Notes Serializer for Entity model
|
||||
- **access**: Change model to inherit from `CenturionModel` for Entity model
|
||||
- **settings**: Add model tag for ExtrnalLink model
|
||||
- **settings**: Add AuditHistory Serializer for UserSettings model
|
||||
- **settings**: Add Notes Serializer for UserSettings model
|
||||
- **settings**: Change model to inherit from `CenturionModel` for UserSettings model
|
||||
- **settings**: Add model ExternalLink to migrate for history and notes
|
||||
- **settings**: Add AuditHistory Serializer for ExternalLink model
|
||||
- **settings**: Add Notes Serializer for ExternalLink model
|
||||
- **settings**: Change model to inherit from `CenturionModel` for ExternalSettings model
|
||||
- **settings**: Add model AppSettings to migrate for history and notes
|
||||
- **settings**: Add AuditHistory Serializer for AppSettings model
|
||||
- **settings**: Add Notes Serializer for AppSettings model
|
||||
- **settings**: Change model to inherit from `CenturionModel` for AppSettings model
|
||||
- **project_management**: Add model ProjectType to migrate for history and notes
|
||||
- **project_management**: Add AuditHistory Serializer for ProjectTYpe model
|
||||
- **project_management**: Add Notes Serializer for ProjectType model
|
||||
- **project_management**: Change model to inherit from `CenturionModel` for ProjectType model
|
||||
- **project_management**: Add model ProjectState to migrate for history and notes
|
||||
- **project_management**: Add AuditHistory Serializer for ProjectState model
|
||||
- **project_management**: Add Notes Serializer for ProjectState model
|
||||
- **project_management**: Change model to inherit from `CenturionModel` for ProjectState model
|
||||
- **project_management**: Add model ProjectMilestone to migrate for history and notes
|
||||
- **project_management**: Add AuditHistory Serializer for ProjectMilestone model
|
||||
- **project_management**: Add Notes Serializer for ProjectMilestone model
|
||||
- **project_management**: Change model to inherit from `CenturionModel` for ProjectManagement model
|
||||
- **project_management**: Add model Project to migrate for history and notes
|
||||
- **project_management**: Add AuditHistory Serializer for Project model
|
||||
- **project_management**: Add Notes Serializer for Project model
|
||||
- **project_management**: Change model to inherit from `CenturionModel` for Project model
|
||||
- **itim**: Add model Service to migrate for history and notes
|
||||
- **itim**: Add AuditHistory Serializer for Service model
|
||||
- **itim**: Add Notes Serializer for Service model
|
||||
- **itim**: Change model to inherit from `CenturionModel` for Service model
|
||||
- **itim**: Add model Port to migrate for history and notes
|
||||
- **itim**: Add AuditHistory Serializer for Port model
|
||||
- **itim**: Add Notes Serializer for Port model
|
||||
- **itim**: Change model to inherit from `CenturionModel` for Port model
|
||||
- **itim**: Add AuditHistory Serializer for ClusterType model
|
||||
- **itim**: Add Notes Serializer for ClusterType model
|
||||
- **itim**: Change model to inherit from `CenturionModel` for ClusterType model
|
||||
- **itim**: Add model Cluster to migrate for history and notes
|
||||
- **itim**: Add Notes Serializer for Cluster model
|
||||
- **itim**: Add AuditHistory Serializer for Cluster model
|
||||
- **itim**: Change model to inherit from `CenturionModel` for Cluster model
|
||||
- **itam**: Add model SoftwareVersion to migrate for history and notes
|
||||
- **itam**: Add Notes Serializer for SoftwareVersiony model
|
||||
- **itam**: Add AuditHistory Serializer for SoftwareVersion model
|
||||
- **itam**: Change model to inherit from `CenturionModel` for SoftwareVersion model
|
||||
- **itam**: Add model SoftwareCategory to migrate for history and notes
|
||||
- **itam**: Add Notes Serializer for SoftwareCategory model
|
||||
- **itam**: Add AuditHistory Serializer for SoftwareCategory model
|
||||
- **itam**: Change model to inherit from `CenturionModel` for SoftwareCategory model
|
||||
- **itam**: Add model Software to migrate for history and notes
|
||||
- **itam**: Add Notes Serializer for Software model
|
||||
- **itam**: Add AuditHistory Serializer for Software model
|
||||
- **itam**: Change model to inherit from `CenturionModel` for Software model
|
||||
- **itam**: Add model OperatingSystemVersion to migrate for history and notes
|
||||
- **itam**: Add Notes Serializer for OperatingSystemVersion model
|
||||
- **itam**: Add AuditHistory Serializer for OperatingSystemVersion model
|
||||
- **itam**: Change model to inherit from `CenturionModel` for OperatingSystemVersion model
|
||||
- **itam**: Add model OperatingSystem to migrate for history and notes
|
||||
- **itam**: Add Note Serializer for DeviceSoftware model
|
||||
- **itam**: Add AuditHistory Serializer for DeviceSoftware model
|
||||
- **itam**: Change model to inherit from `CenturionModel` for DeviceSoftware model
|
||||
- **itam**: Change model to inherit from `Centurion` for DeviceSoftware model
|
||||
- **itam**: Add model_tag to DeviceType model
|
||||
- **itam**: Add DeviceType for history and notes data migration
|
||||
- **itam**: Add DeviceModel for history and notes data migration
|
||||
- **itam**: Add DEvice for history and notes data migration
|
||||
- **devops**: Switch SoftwareEnabledFeatureFlag model to inherit from CenturionModel
|
||||
- **devops**: Update checkin model fixture so it creates the feature flag
|
||||
- **devops**: Add methods get_url and get_url_kwargs to CheckIn model
|
||||
- **devops**: Add migration to signal
|
||||
- **devops**: Add migration to signal
|
||||
- **devops**: Add migration to signal
|
||||
- **devops**: Add migration to signal
|
||||
- **devops**: Update URL route basename
|
||||
- **devops**: Migrations for switching GitLabRepository model to inherit from `CenturionModel`
|
||||
- **devops**: Migrations for switching GitRepository model to inherit from `CenturionModel`
|
||||
- **devops**: Migrations for switching GitRepository model to inherit from `CenturionModel`
|
||||
- **devops**: Serializers for GitRepository models notes and history
|
||||
- **devops**: Serializers for GitHubGitRepository models notes and history
|
||||
- **devops**: Serializers for GitLabGitRepository models notes and history
|
||||
- **devops**: Switch GitLabGitRepository model to inherit from `CenturionModel`
|
||||
- **devops**: Switch GitHubGitRepository model to inherit from `CenturionModel`
|
||||
- **devops**: Switch GitRepository model to inherit from `CenturionModel`
|
||||
- **devops**: Update Checkin model url route basename
|
||||
- **devops**: Add app_namespace Checkin model
|
||||
- **devops**: Add Checkin to migrate model history/notes
|
||||
- **devops**: Migrations for switching Checkin model to inherit from `CenturionModel`
|
||||
- **devops**: Switch Checkin model to inherit from `CenturionModel`
|
||||
- **core**: add TicketCommentCategory to history/notes migration
|
||||
- **core**: add model tag to ticket comment category
|
||||
- **core**: Migrations for TicketCategory
|
||||
- **core**: add TicketCategory to history/notes migration
|
||||
- **core**: add model tag to ticket category
|
||||
- **core**: add Manufacturer to history/notes migration
|
||||
- **core**: add model tag to manufacturer
|
||||
- **config_management**: add ConfigGroups to history/notes migration
|
||||
- **config_management**: add ConfigGroupSoftware to history/notes migration
|
||||
- **config_management**: add ConfigGroupHosts to history/notes migration
|
||||
- **access**: add tenant to history/notes migration
|
||||
- **access**: Migration for switching model inheritence to `CenturionModel`
|
||||
- **itam**: Update model methods
|
||||
- **access**: Migration for switching model inheritence to `CenturionMixin`
|
||||
- **access**: Switch model inheritence to `CenturionMixin`
|
||||
- **itam**: Update url basename
|
||||
- **itam**: Update url basename
|
||||
- **base**: add support for manytomany for model unit tests
|
||||
- **itam**: Update url basename
|
||||
- **core**: Update url basename
|
||||
- **core**: Update url basename
|
||||
- **core**: Update url basename
|
||||
- **core**: Update url basename
|
||||
- **core**: Update url basename
|
||||
- **core**: Update url basename
|
||||
- **access**: TeamUsers do not require notes
|
||||
- **config_management**: ConfigGroupHosts and ConfigGroupSoftware do not require notes
|
||||
- **config_management**: Add url_kwargs to ConfigGroupSoftware model
|
||||
- **access**: Add url_kwargs to Team model
|
||||
- **access**: Add url_kwargs to TeamUser model
|
||||
- **access**: Update TeamUser API basename
|
||||
- **access**: Update Team API basename
|
||||
- **itam**: switch model Device to inheirt from CenturionModel
|
||||
- **itam**: switch model DeviceType to inheirt from CenturionModel
|
||||
- **itam**: switch model DeviceModel to inheirt from CenturionModel
|
||||
- **core**: switch model TicketCategory to inheirt from CenturionModel
|
||||
- **core**: switch model TicktetCommentCategory to inheirt from CenturionModel
|
||||
- **core**: switch model Manufacturer to inheirt from CenturionModel
|
||||
- **config_management**: switch model ConfigGroupHosts to inheirt from CenturionModel
|
||||
- **config_management**: switch model ConfigGroupSoftware to inheirt from CenturionModel
|
||||
- **config_management**: switch model ConfigGroups to inheirt from CenturionModel
|
||||
- **access**: switch model TeamUsers to inheirt from CenturionModel
|
||||
- **access**: switch model Team to inheirt from CenturionModel
|
||||
- **core**: If user context not supplied, dont create audithistory for model
|
||||
- **access**: Add init to tenancy model to clear state
|
||||
- **core**: Ensure that model has user context
|
||||
- **core**: Add supprt to model_instance fixture for manytomany field
|
||||
- **core**: Add supprt to model create test for manytomany field
|
||||
- **assistance**: migrations for new history and notes models for KnowledgeBaseCategory model
|
||||
- **assistance**: migrations for new history and notes models for KnowledgeBase model
|
||||
- **assistance**: Model inheritance migrations
|
||||
- **core**: Migrate Centurion Model history and notes within a post_migrate signal
|
||||
- **core**: Add ability to CenturionModel `get_url` to be either detail/list
|
||||
- **core**: New Management command to list models
|
||||
- **devops**: Switch model FeatureFlag inheritance to CenturionModel
|
||||
- **core**: Disable Notes for model CenturionModelNote
|
||||
- **devops**: Enable Model notes for GitGroup
|
||||
- **core**: add Swagger docs for CenturionModelNotes ViewSet
|
||||
- **core**: Meta Model for CenturionModelNotes
|
||||
- **core**: Finalize Serializer for CenturionModelNotes
|
||||
- **api**: Add to common serializer meta notes model for notes url
|
||||
- **core**: Interim Meta model CenturionNotes
|
||||
- **core**: Interim ViewSet for model CenturionNotes
|
||||
- **core**: URL Route for model CenturionNotes
|
||||
- **core**: Serializer for model CenturionNotes
|
||||
- **core**: Migration for model CenturionNotes
|
||||
- **core**: Add model CenturionNotes
|
||||
- **devops**: dont allow deleting a git group if it has children
|
||||
- **devops**: Add model tag attribute to model
|
||||
- **core**: Add to Centurion Model an attribute to set the models tag
|
||||
- **core**: Add Context to model when ViewSet loads
|
||||
- **devops**: Add AuditHistory Serializer for GitGroup
|
||||
- **core**: Add AuditHistory Serializer
|
||||
- **core**: Add AuditHistory ViewSet
|
||||
- **core**: Add URL route for AuditHistory
|
||||
- **core**: Add audithistory URL to serializer for models with `_audit_enabled=True`
|
||||
- **core**: Models url kwarg helper
|
||||
- **core**: Support setting custom model name for url basename
|
||||
- **api**: Add sub-model filter to `get_queryset` method
|
||||
- **core**: Disable models audit history on model delete
|
||||
- **core**: Use Previous TenancyManager until UserModel rewrite done
|
||||
- **core**: Process a models history within AuditHistory
|
||||
- **core**: Enable AuditHistory signal to start when apps are ready
|
||||
- **core**: Add model instance to history object during history creation
|
||||
- **core**: Update Meta AuditModel `db_name` to be suffixed `_audithistory`
|
||||
- **core**: remove unnessecary method `clean_fields` from audit model
|
||||
- **core**: remove un-needed field `model_notes` from audit models
|
||||
- **core**: Run meta models create on Core module ready
|
||||
- **core**: New model core.CenturionAudit
|
||||
- **core**: cause sub-audit models to chuck a wobbler if clean_fields not re-implementated
|
||||
- **access**: remove mill-seconds from datetime auto fields
|
||||
- **core**: Centurion model Base
|
||||
- **core**: Centurion Audit model
|
||||
- **core**: permissions getter for role model
|
||||
- **core**: Audit History Signal for Delete/Save
|
||||
- **core**: Dynamic History model creation
|
||||
|
||||
### Fixes
|
||||
|
||||
- remove trailing slant from URLs
|
||||
- **access**: When creating permission QuerySet prevent app crash if db not setup
|
||||
- **itim**: Ensure during testing, fixture vals are copied for Model Service
|
||||
- **base**: on fixture cleanup, only clean if obj exists
|
||||
- **core**: required field must be null for logical chek to function
|
||||
- **itam**: field slug no longer avail, use str
|
||||
- **core**: Include model so content type is created
|
||||
- **settings**: AppSettings requires super user perms
|
||||
- **api**: Convert Django Exceptions to DRF API Exception equivilent
|
||||
- **api**: Ensure if exception DRF, message returned is from that exception
|
||||
- **devops**: git repository is sub-model ViewSet must inherit from SubModel
|
||||
- **access**: entity field `entity_type` is an auto field
|
||||
- **access**: Ensure that if method not allowed, exception is thrown first before perms check
|
||||
- **itam**: Model software must be related linked to organization model
|
||||
- **access**: if user has no orgs, dont filter by for query
|
||||
- **devops**: Ensure mandatory fields are writeable for model GitRepository
|
||||
- **access**: add property organization to Tenant model
|
||||
- **itam**: Add missing import `now`
|
||||
- **core**: notes meta model must add `model_kwargs` fixture
|
||||
- **core**: clean_fields for created_by field belongs in model that contains field
|
||||
- **core**: audit meta model must add `model_kwargs` fixture
|
||||
- model fixture names must match model_name
|
||||
- clean up mock model from django apps
|
||||
- **core**: When obtaining model fields ensure it exists first
|
||||
- **access**: use getattr instead as attribute may exist as None
|
||||
- **assistance**: make kb article field longer for model name
|
||||
- **assistance**: Add missing field `model_notes` to KB serializer
|
||||
- **core**: Before attempting to get model audit data confirm fields dont already exist
|
||||
- **api**: check if model has notes enabled before adding url to body
|
||||
- **api**: Only return View Serialized data if status code is HTTP/2xx
|
||||
- **core**: Conduct kwargs check fr ticket comment serializer during init
|
||||
- **core**: Enable CenturionAudit model to get model history for item being deleted
|
||||
- **core**: When creating the AuditHistory entry for a model, use the user from context
|
||||
- **core**: When collecting AuditHistory cater for models being created
|
||||
- **api**: remove surerflous feature for fetching app_namespace for models metadata
|
||||
- **core**: Correct attribute names for referencing a Centurion Model from an AuditModel
|
||||
- **core**: Correct before lookup for current models audit history
|
||||
- **core**: When deleting a model check if sub-model within delete method
|
||||
- **access**: Tenancy Manager should not attempt to get org as related field if it does not exist
|
||||
- **api**: ensure val returns at least none
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **docker**: update healthcheck interval=10s and start-period=30s
|
||||
- **docker**: when l;aunching gunicorn create a pid file
|
||||
- **devops**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag Again
|
||||
- **devops**: Remove old test suites no longer required model SoftwareEnableFeatureFlag
|
||||
- **devops**: ViewSet Unit Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
|
||||
- **devops**: Serializer Unit Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
|
||||
- **devops**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
|
||||
- **devops**: Model Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
|
||||
- **devops**: API Metadata Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
|
||||
- **devops**: Remove old test suites no longer required model FeatureFlag
|
||||
- **devops**: ViewSet Unit Test Suite re-written to Pytest for model FeatureFlag
|
||||
- **devops**: Serializer Unit Test Suite re-written to Pytest for model FeatureFlag
|
||||
- **devops**: API Fields render Functional Test Suite re-written to Pytest for model FeatureFlag
|
||||
- **devops**: Model Functional Test Suite re-written to Pytest for model FeatureFlag
|
||||
- **devops**: API Metadata Functional Test Suite re-written to Pytest for model FeatureFlag
|
||||
- **api**: Remove old test suites no longer required model AuthToken
|
||||
- **api**: ViewSet Unit Test Suite re-written to Pytest for model AuthToken
|
||||
- **api**: Serializer Unit Test Suite re-written to Pytest for model AuthToken
|
||||
- **api**: API Fields render Functional Test Suite re-written to Pytest for model AuthToken
|
||||
- **api**: Model Functional Test Suite re-written to Pytest for model AuthToken
|
||||
- **api**: API Metadata Functional Test Suite re-written to Pytest for model AuthToken
|
||||
- **access**: Remove old test suites no longer required model Tenant
|
||||
- **access**: Serializer Unit Test Suite re-written to Pytest for model Tenant
|
||||
- **access**: API Fields render Functional Test Suite re-written to Pytest for model Tenant
|
||||
- **access**: Model Functional Test Suite re-written to Pytest for model Tenant
|
||||
- **access**: API Metadata Functional Test Suite re-written to Pytest for model Tenant
|
||||
- **settings**: Remove old test suites no longer required model UserSettings
|
||||
- **settings**: ViewSet Unit Test Suite re-written to Pytest for model UserSettings
|
||||
- **settings**: Serializer Unit Test Suite re-written to Pytest for model UserSettings
|
||||
- **settings**: API Fields render Functional Test Suite re-written to Pytest for model UserSettings
|
||||
- **settings**: Model Functional Test Suite re-written to Pytest for model UserSettings
|
||||
- **settings**: API Metadata Functional Test Suite re-written to Pytest for model UserSettings
|
||||
- **settings**: Remove old test suites no longer required model ExternalLink
|
||||
- **settings**: ViewSet Unit Test Suite re-written to Pytest for model ExternalLink
|
||||
- **settings**: Serializer Unit Test Suite re-written to Pytest for model ExternalLink
|
||||
- **settings**: API Fields render Functional Test Suite re-written to Pytest for model ExternalLink
|
||||
- **settings**: Model Functional Test Suite re-written to Pytest for model ExternalLink
|
||||
- **settings**: API Metadata Functional Test Suite re-written to Pytest for model ExternalLink
|
||||
- **settings**: Remove old test suites no longer required model AppSettings
|
||||
- **settings**: ViewSet Unit Test Suite re-written to Pytest for model AppSettings
|
||||
- **settings**: Serializer Unit Test Suite re-written to Pytest for model AppSettings
|
||||
- **settings**: API Fields render Functional Test Suite re-written to Pytest for model AppSettings
|
||||
- **settings**: Model Functional Test Suite re-written to Pytest for model AppSettings
|
||||
- **settings**: API Metadata Functional Test Suite re-written to Pytest for model AppSettings
|
||||
- **test**: remove xfail during `pytest_generate_tests` before parameterizing
|
||||
- **project_management**: ensure within fixtur kwargs are copied
|
||||
- **project_management**: Remove old test suites no longer required model ProjectType
|
||||
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model ProjectType
|
||||
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model ProjectType
|
||||
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model ProjectType
|
||||
- **project_management**: Model Functional Test Suite re-written to Pytest for model ProjectType
|
||||
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model ProjectType
|
||||
- **project_management**: Remove old test suites no longer required model ProjectState
|
||||
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model ProjectState
|
||||
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model ProjectState
|
||||
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model ProjectState
|
||||
- **project_management**: Model Functional Test Suite re-written to Pytest for model ProjectState
|
||||
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model ProjectState
|
||||
- **project_management**: Remove old test suites no longer required model ProjectMilestone
|
||||
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model ProjectMilestone
|
||||
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model ProjectMilestone
|
||||
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model ProjectMilestone
|
||||
- **project_management**: Model Functional Test Suite re-written to Pytest for model ProjectMilestone
|
||||
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model ProjectMilestone
|
||||
- **project_management**: Remove old test suites no longer required model Project
|
||||
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model Project
|
||||
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model Project
|
||||
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model Project
|
||||
- **project_management**: Model Functional Test Suite re-written to Pytest for model Project
|
||||
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model Project
|
||||
- **itim**: Remove old test suites no longer required model Service
|
||||
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model Service
|
||||
- **itim**: Serializer Unit Test Suite re-written to Pytest for model Service
|
||||
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model Service
|
||||
- **itim**: Model Functional Test Suite re-written to Pytest for model Service
|
||||
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model Service
|
||||
- **itim**: Remove old test suites no longer required model Port
|
||||
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model Port
|
||||
- **itim**: Serializer Unit Test Suite re-written to Pytest for model Port
|
||||
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model Port
|
||||
- **itim**: Model Functional Test Suite re-written to Pytest for model Port
|
||||
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model Port
|
||||
- **itim**: Remove old test suites no longer required model ClusterType
|
||||
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model ClusterType
|
||||
- **itim**: Serializer Unit Test Suite re-written to Pytest for model ClusterType
|
||||
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model ClusterType
|
||||
- **itim**: Model Functional Test Suite re-written to Pytest for model ClusterType
|
||||
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model ClusterType
|
||||
- **itim**: Remove old test suites no longer required model Cluster
|
||||
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model Cluster
|
||||
- **itim**: Serializer Unit Test Suite re-written to Pytest for model Cluster
|
||||
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model Cluster
|
||||
- **itim**: Model Functional Test Suite re-written to Pytest for model Cluster
|
||||
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model Cluster
|
||||
- **itam**: Remove old test suites no longer required model SoftwareVersion
|
||||
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model SoftwareVersion
|
||||
- **itam**: Serializer Unit Test Suite re-written to Pytest for model SoftwareVersion
|
||||
- **itam**: Model Functional Test Suite re-written to Pytest for model SoftwareVersion
|
||||
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareVersion
|
||||
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model SoftwareVersion
|
||||
- **itam**: Remove old test suites no longer required model SoftwareCategory
|
||||
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model SoftwareCategory
|
||||
- **itam**: Serializer Unit Test Suite re-written to Pytest for model SoftwareCategory
|
||||
- **itam**: Model Functional Test Suite re-written to Pytest for model SoftwareCategory
|
||||
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareCategory
|
||||
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model SoftwareCategory
|
||||
- **itam**: Remove old test suites no longer required model Software
|
||||
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model Software
|
||||
- **itam**: Serializer Unit Test Suite re-written to Pytest for model Software
|
||||
- **itam**: Model Functional Test Suite re-written to Pytest for model Software
|
||||
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model Software
|
||||
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model Software
|
||||
- **itam**: Remove old test suites no longer required model OperatingSystemVersion
|
||||
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model OperatingSystemVersion
|
||||
- **itam**: Serializer Unit Test Suite re-written to Pytest for model OperatingSystemVersion
|
||||
- **itam**: Model Functional Test Suite re-written to Pytest for model OperatingSystemVersion
|
||||
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model OperatingSystemVersion
|
||||
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model OperatingSystemVersion
|
||||
- **itam**: Remove old test suites no longer required model OperatingSystem
|
||||
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model OperatingSystem
|
||||
- **itam**: Serializer Unit Test Suite re-written to Pytest for model OperatingSystem
|
||||
- **itam**: Model Functional Test Suite re-written to Pytest for model OperatingSystem
|
||||
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model OperatingSystem
|
||||
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model OperatingSystem
|
||||
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model DeviceType
|
||||
- **itam**: Model Functional Test Suite re-written to Pytest for model DeviceType
|
||||
- **itam**: API Fields render Test Suite re-written to Pytest for model DeviceType
|
||||
- **itam**: Serializer Unit Test Suite re-written to Pytest for model DeviceType
|
||||
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model DeviceModel
|
||||
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model DeviceModel
|
||||
- **itam**: API Field Render Functional Test Suite re-written to PyTest for model Device
|
||||
- **itam**: Metadate Functional Test Suite re-enabled for model Device
|
||||
- **itam**: Viewset Unit Test Suite re-written to pytest for model Device
|
||||
- **itam**: Serializer Unit Test Suite re-enabled for model Device
|
||||
- **core**: API Render Unit Test Suite re-enabled for model Manufacturer
|
||||
- **core**: API Metadata Functional Test Suite re-enabled for model Manufacturer
|
||||
- **core**: Serializer Functional Test Suite re-enabled for model Manufacturer
|
||||
- **core**: ViewSet Test Suite re-written to pytest for model Manufacturer
|
||||
- **config_management**: ViewSet Test Suite re-written to pytest for model ConfigGroupSoftware
|
||||
- **config_management**: API fields Test Suite re-enalbed for model ConfigGroupSoftware
|
||||
- **config_management**: API Metadata Functional Test Suite for model ConfigGroupSoftware
|
||||
- **config_management**: Serializer Functional Test Suite Enabled for model ConfigGroupSoftware
|
||||
- **config_management**: Model Unit Test Suite re-written to pytest for model ConfigGroup
|
||||
- **config_management**: API Metadata Functional Test Suite re-written to pytest for model ConfigGroup
|
||||
- **assistance**: Serializer Unit Test Suite re-written to pytest for model KnowledgeBase
|
||||
- **assistance**: MetaData Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
|
||||
- **assistance**: Serializer Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
|
||||
- **assistance**: ViewSet Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
|
||||
- **assistance**: Serializer Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
|
||||
- **assistance**: Serializer Unit TestSuite re-written to pytest for model KnowledgeBase
|
||||
- **assistance**: ViewSet TestSuite re-written to pytest for model KnowledgeBase
|
||||
- **access**: ViewSet TestSuite re-written to pytest for model Tenant
|
||||
- **access**: ViewSet TestSuite re-written to pytest for model Person
|
||||
- **access**: ViewSet TestSuite re-written to pytest for model Entity
|
||||
- **access**: ViewSet TestSuite re-written to pytest for model Contact
|
||||
- **access**: ViewSet TestSuite re-written to pytest for model Company
|
||||
- **api**: migrate Common ViewSet unittest.mock to mocker
|
||||
- **api**: migrate Common ViewSet Unit Test Suite attribute to use test case `unit_class`
|
||||
- **api**: Converted Common ViewSet Unit Test Suite to use Pytest
|
||||
- **api**: partial conversion to pytest for Common ViewSet Unit Test Suite
|
||||
- **api**: Rename create Serializer unit test to `is_valid`
|
||||
- **base**: normalize empty/not used to be `models.NOT_PROVIDED`
|
||||
- **base**: adjust functional model test to use fixture kwargs
|
||||
- **api**: Update Test Suite for AuthToken model
|
||||
- **tests**: Unskip tests that'll work now due to model inheritance change
|
||||
- **api**: Update Test Suite for AuthToken model
|
||||
- **api**: Update URL route name for Role AuthToken
|
||||
- **api**: Switch to inherit from Centurion model for model AuthToken
|
||||
- **access**: When adding model role via api, status is 201/created
|
||||
- **itim**: Update Test Suite for TicketCommentSolution model
|
||||
- **itim**: Update Test Suite for TicketSLM model
|
||||
- **itim**: Update Test Suite for TicketRequest model
|
||||
- **core**: Update Test Suite for TicketBase model
|
||||
- **core**: Update Test Suite for TicketCommentSolution model
|
||||
- **core**: Update Test Suite for TicketCommentAction model
|
||||
- **core**: Update Test Suite for TicketCommentBase model
|
||||
- **core**: Initial Update Test Suite for TicketCommentBase model
|
||||
- **core**: Update Tests to cater for inheritence changes
|
||||
- **itim**: Update Test Suite for RequestTicket model
|
||||
- **itim**: Update Test Suite for SLMTicket model
|
||||
- **itim**: Update Test Suite for SLMTicket model
|
||||
- **core**: Update Test Suite for TicketBase model
|
||||
- **core**: Update Test Suite for TicketBase model
|
||||
- **core**: Switch to inherit from Centurion model for model TicketBase
|
||||
- **core**: Switch to inherit from Centurion model for model SLMTicketBase
|
||||
- **core**: Update URL route name for Role TicketCommentBase
|
||||
- **core**: Switch to inherit from Centurion model for model TicketCommentBase
|
||||
- **core**: Update URL route name for Role TicketBase
|
||||
- **core**: Switch to inherit from Centurion model for model TicketBase
|
||||
- **core**: Add fn get_organization to centurion mixin
|
||||
- **access**: Adjust add permission test for model Role
|
||||
- **access**: Migrations for Inheritance change for Role model
|
||||
- **access**: Update URL route name for Role model
|
||||
- **access**: Update Test Suite for Role model
|
||||
- **access**: Switch to inherit from Centurion model for model Role
|
||||
- Asset and ITAM Asset must use url kwarg model_name not asset_model
|
||||
- **accounting**: Update existing tests to work due to model inheritance changes
|
||||
- **itam**: Update URL route name for ITAMAssetBase model
|
||||
- **itam**: Update Test Suite for ITAMAssetBase model
|
||||
- **itam**: Switch to inherit from Centurion model for model ITAMAssetBase
|
||||
- **accounting**: Switch to inherit from Centurion model for model AssetBase
|
||||
- **accounting**: Update URL route name for AssetBase model
|
||||
- **accounting**: Update Test Suite for AssetBase model
|
||||
- **accounting**: Switch to inherit from Centurion model for model AssetBase
|
||||
- **api**: dont query db for instance, use existing from response
|
||||
- **api**: additional perms tests if they exist must be inc first
|
||||
- **devops**: remove ViewSet `get_queryset` function
|
||||
- **access**: Update Entity model ViewSet attribute `model_kwarg` to `model_name`
|
||||
- **access**: Update Entity model ViewSet to inherit from submodel-rewrite
|
||||
- **access**: Update Test Suite for Employee model
|
||||
- **access**: Update Test Suite for Person model
|
||||
- **access**: Update Test Suite for Contact model
|
||||
- **access**: Update Test Suite for Company model
|
||||
- **access**: Update URL route name for Entity model
|
||||
- **access**: Update Test Suite for Entity model
|
||||
- **access**: Update is_tenancy_object to check for CenturionModel
|
||||
- **access**: For request middleware, use filter and first object so that testing can occur when mre than one exists
|
||||
- **settings**: Update URL route name for UserSettings model
|
||||
- **settings**: Update Test Suite for ExternalLink model
|
||||
- **settings**: Update URL route name for ExternalLink model
|
||||
- **settings**: Update Test Suite for ExternalLink model
|
||||
- **settings**: Update URL route name for AppSettings model
|
||||
- **settings**: Update Test Suite for AppSettings model
|
||||
- **project_management**: Update URL route name for ProjectType model
|
||||
- **project_management**: Update Test Suite for ProjectType model
|
||||
- **project_management**: Update URL route name for ProjectState model
|
||||
- **project_management**: Update Test Suite for ProjectState model
|
||||
- **project_management**: Update URL route name for ProjectMilestone model
|
||||
- **project_management**: Update Test Suite for ProjectMilestone model
|
||||
- **project_management**: Update URL route name for Project model
|
||||
- **project_management**: Update Test Suite for Project model
|
||||
- **itim**: Update URL route name for Service model
|
||||
- **itim**: Update Test Suite for Service model
|
||||
- **itim**: Update URL route name for Port model
|
||||
- **itim**: Update Test Suite for Port model
|
||||
- **itim**: Update URL route name for ClusterType model
|
||||
- **itim**: Update Test Suite for ClusterType model
|
||||
- **itim**: Update URL route name for Cluster model
|
||||
- **itim**: Update Test Suite for Cluster model
|
||||
- **itam**: Update Test Suite for SoftwareVersion model
|
||||
- **itam**: Update URL route name for SoftwareVersion model
|
||||
- **itam**: Update Test Suite for SoftwareCategory model
|
||||
- **itam**: Update URL route name for SoftwareCategory model
|
||||
- cater for dev that does not exist in test cleanup
|
||||
- **itam**: Update Test Suite for Software model
|
||||
- **itam**: Update URL route name for Software model
|
||||
- **itam**: Update Test Suite for OperatingSystemVersion model
|
||||
- **itam**: Update Test Suite for OperatingSystem model
|
||||
- **itam**: Update URL route name for DeviceSoftware model
|
||||
- **itam**: Update Test Suite for DeviceSoftware model
|
||||
- **itam**: Update Test Suite for DeviceDeviceOperatingSystem model
|
||||
- **itam**: Update URL route for DeviceDeviceOperatingSystem model
|
||||
- **itam**: Migration for updating model inheritance for DeviceDeviceOperatingSystem model
|
||||
- **itam**: Updated Unit model test suite for DeviceType model
|
||||
- **devops**: Updated Unit model test suite for DeviceModel model
|
||||
- **devops**: Migration for updating model inheritance for DeviceModel model
|
||||
- **itam**: Updated Unit model test suite for Device model
|
||||
- **devops**: Updated Unit model test ssuite for SoftwareEnabledFeatureFlag model
|
||||
- **devops**: Migration for updating model inheritance for SoftwareEnabledFeatureFlag model
|
||||
- **devops**: Update url route basename for SoftwareEnabledFeatureFlag model
|
||||
- **tests**: make all `parameterized_` vars properties
|
||||
- **core**: adjust CenturionSubModel to not be it's own inheritable class
|
||||
- **core**: Move CenturionModel logic to Mixin
|
||||
- **core**: rename mixin -> mixins
|
||||
- **base**: model instancxe code de-duplicated
|
||||
- **config_management**: Add ConfigGroupHost Model Tests
|
||||
- **config_management**: Add ConfigGroupSoftware Model Tests
|
||||
- **config_management**: Add ConfigGroup Model Tests
|
||||
- **assistance**: Refactor KnowledgeBaseCategory Unit model tests
|
||||
- **assistance**: Update KnowledgeBase Unit viewset url basename
|
||||
- **assistance**: Refactor KnowledgeBase Unit model tests
|
||||
- **assistance**: Add new history and notes Serializer for KnowledgeBase model
|
||||
- **assistance**: Add new history and notes Serializer for KnowledgeBaseCategory model
|
||||
- **assistance**: Change KnowledgeBaseCategory model inheritance TenancyObject -> CenturionModel
|
||||
- **assistance**: Change KnowledgeBase model inheritance TenancyObject -> CenturionModel
|
||||
- **assistance**: MV kb category model to its own file
|
||||
- **tests**: Create global model fixtures
|
||||
- **devops**: Switch FeatureFlag model unit tests to CenturionModel
|
||||
- **settings**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **project_management**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **itim**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **itam**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **core**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **config_management**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **assistance**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **access**: move url routes from core.urls to own module `urls_api.py`
|
||||
- **api**: Update Common ViewSet methds for re-write
|
||||
- **devops**: Switch GitGroup Model to CenturionModel
|
||||
- **core**: Loading of meta models should not be hidden behind program start ags
|
||||
- **core**: To obtain audit_values loop through model fields
|
||||
- rejig whats in each inherited centurion model
|
||||
- **access**: prefetch org with tenancy object
|
||||
- **core**: Relocate history model class
|
||||
- **base**: rename app to centurion
|
||||
|
||||
### Tests
|
||||
|
||||
- **core**: Notes Meta Models API Permissions Test cases for All Notes Models
|
||||
- Add initial integration tests
|
||||
- **docker**: Add compose setup for integration testing
|
||||
- **itam**: ViewSet Unit Test Suite added for model DeviceType
|
||||
- **itam**: Serializer UnitTest Suite added for model DeviceModel
|
||||
- **itam**: API Fields render Functional Test Suite added for model DeviceModel
|
||||
- **itam**: Model Functional Test Suite added for model DeviceModel
|
||||
- **itam**: Refactor failing tests to cater for uniqueness so they pass
|
||||
- **itam**: Model Functional Test Suite aded for model Device
|
||||
- **config_management**: ViewSet Unit Test Suite re-written to pytest for model ConfigGroup
|
||||
- **config_management**: Serializer Unit Test Suite re-written to pytest for model ConfigGroup
|
||||
- **config_management**: Model Functional Test Suite re-written to pytest for model ConfigGroup
|
||||
- **config_management**: API Field Render Functional Test Suite re-written to pytest for model ConfigGroup
|
||||
- **assistance**: API Field Render Functional Test Suite re-written to pytest for model KnowledgeBaseCategory
|
||||
- **assistance**: Model Functional Test Suite re-written to pytest for model KnowledgeBaseCategory
|
||||
- **assistance**: Model Functional Test Suite re-written to pytest for model KnowledgeBase
|
||||
- **assistance**: API Fields Render Functional Test Suite re-written to pytest for model KnowledgeBase
|
||||
- **api**: SubModel ViewSet Test Suite to test re-written class
|
||||
- **api**: Dont test a django object that has not been customised
|
||||
- **access**: Initial ViewSet Unit Test Suite for Entity Model
|
||||
- **access**: Add Serializer unit test suit for model Role
|
||||
- **access**: Add Serializer unit test suit for model Person
|
||||
- **access**: Add Serializer unit test suit for model Entity
|
||||
- **access**: Add Serializer unit test suit for model Contact
|
||||
- **access**: Add Serializer unit test suit for model Company
|
||||
- **itim**: Refactor TicketSLM model API Fields render test Suite to PyTest
|
||||
- **itim**: Refactor TicketRequest model API Fields render test Suite to PyTest
|
||||
- **core**: Refactor TicketBase model API Fields render test Suite to PyTest
|
||||
- **api**: Refactor Test Suite for API Fields render tests to PyTest
|
||||
- **itam**: Refactor ITAMAssetBase model API Fields render test Suite to PyTest
|
||||
- **accounting**: Refactor AssetBase model API Fields render test Suite to PyTest
|
||||
- **core**: Refactor TicketCommentSolution model API Fields render test Suite to PyTest
|
||||
- **core**: Refactor TicketCommentAction model API Fields render test Suite to PyTest
|
||||
- **core**: Refactor TicketCommentBase model API Fields render test Suite to PyTest
|
||||
- **human_resources**: Refactor Employee model API Fields render test Suite to PyTest
|
||||
- **access**: Refactor Person model API Fields render test Suite to PyTest
|
||||
- **access**: Refactor Entity model API Fields render test Suite to PyTest
|
||||
- **access**: Refactor Contact model API Fields render test Suite to PyTest
|
||||
- **access**: Refactor Company model API Fields render test Suite to PyTest
|
||||
- **devops**: Adjust functional model test to use fixture kwargs
|
||||
- Ensure when obj created via serializer calls full_clean
|
||||
- Ensure Clean methods called
|
||||
- Test case for model field type
|
||||
- **fixture**: if item already exists, when fetching remove modified field from query if not found with
|
||||
- **access**: Model Role is not usable within global org, remove test
|
||||
- **devops**: skip Model History entry test as it should be done as part of serializer and viewset
|
||||
- **devops**: update no_org_serializer test so it works for model SoftwareEnableFeatureFlag
|
||||
- **itam**: Model DeviceOperatingSystem is not multi-org based skip those tests
|
||||
- **settings**: Model UserSettings does not allowing adding rows, skip test
|
||||
- **settings**: Model AppSettings does not allowing adding rows, skip test
|
||||
- **fixture**: Ensure _meta attribute exists when cleaning up models prior to attempting to use
|
||||
- **devops**: SoftwareEnableFeatureFlagging model does not use global org, so dont test global org return
|
||||
- **api**: when testing create object, remove the actual created object prior to testing the add
|
||||
- **fixture**: when creating object and it exists, rtn that object
|
||||
- **devops**: If test publically accessable, dont test by user org only as test is NA
|
||||
- **settings**: UserSettings perms tests are for the user that is accessing them
|
||||
- **settings**: UserSettings perms tests are not org based, skip those tests
|
||||
- **settings**: AppSettings perms tests are not org based, skip those tests
|
||||
- **settings**: for api checks for model AppSettings, make user super_user
|
||||
- **settings**: Exclude inter-org tests for model AppSettings
|
||||
- **settings**: Remove old API Permission tests no longer required
|
||||
- **settings**: Ensure ExternalLink model hasrequired field template added
|
||||
- **api**: if model lacks list endpoint, check if method alllowed for test cases for Functional API perms test suite
|
||||
- **api**: if model lacks organization field, xfail returned orgs test cases for Functional API perms test suite
|
||||
- Ensure service fixture assosiates with device
|
||||
- **api**: if model lacks organization field, xfail returned orgs test cases for Functional API perms test suite
|
||||
- Add depreciated models to be excluded from coverage
|
||||
- **api**: Update Functional API Permission test suite to cater for public RO endpoints
|
||||
- **core**: Ensure model mehod `get_url_kwargs` returns a dict for all Centurion Models
|
||||
- **devops**: Add GitLabRepository Unit Model test suite
|
||||
- **devops**: Add GitHubRepository Unit Model test suite
|
||||
- **devops**: Add GitRepository Unit Model test suite
|
||||
- **devops**: Add Checkin Unit Model test suite
|
||||
- **devops**: correct GitGroup Unit model test suite
|
||||
- **devops**: correct FeatureFlag Unit model test suite
|
||||
- **core**: Add TicketCommentCategory Unit model test suite
|
||||
- **core**: Add TicketCategory Unit model test suite
|
||||
- **core**: Add Manufacturer Unit model test suite
|
||||
- **access**: Add Tenant Unit serializer test suite
|
||||
- Add initial unit serializer test suite
|
||||
- **access**: Update Tenant URL route basename again
|
||||
- **itam**: Updated Unit model test for Device Model
|
||||
- **access**: Update Tenant URL route basename
|
||||
- **access**: Tenant Model Tests
|
||||
- **api**: Update Functional API Permissions to support listview models with kwargs
|
||||
- **api**: exclude model `ConfigGroupHosts` from api permission tests as it has no endpoint
|
||||
- **api**: API Permissions Functional test to supprt name as unique field
|
||||
- **config_management**: Completed ConfigGroupSoftware Model Tests
|
||||
- **config_management**: Completed ConfigGroup Model Tests
|
||||
- **config_management**: Completed ConfigGroupHost Model Tests
|
||||
- **core**: mock the user object within the model context
|
||||
- **core**: creating a model is a functional not unit test
|
||||
- **devops**: re-implement temp removed test suites.
|
||||
- **api**: API Permissions Auto-Creator test suite
|
||||
- **devops**: Add GitGroup API Permissions tests
|
||||
- **core**: Add fixtures for api permission tests
|
||||
- **core**: rewrite api permissions test suite to use pytest and fixtures
|
||||
- **core**: Ensure Method clean_fields functions for CenturionNotesModel
|
||||
- **core**: Function Model test suite for CenturionModelNote Meta Models
|
||||
- **core**: Interim Unit Model test suite for CenturionModelNote Meta Models
|
||||
- **core**: Interim Unit Model test suite for CenturionModelNote
|
||||
- **core**: Dynamic Unit Test Suites for Meta Models AuditHistory
|
||||
- **core**: Unit Test Centurion Model method `__str__`
|
||||
- **core**: Unit Test Centurion Model method `get_url_kwargs`
|
||||
- **core**: Unite Tesxt Centurion Model method `get_url` attr `_is_submodel` set
|
||||
- **core**: Unite Tesxt Centurion Model method `get_url` attr `model_name` set
|
||||
- **core**: Ensure model that has audit enableed has audit model
|
||||
- **core**: Add Functional model Test Suite for CenturionAuditModel
|
||||
- **devops**: Ensure that a Github group cant have a parent/"be nested"
|
||||
- **devops**: Ensure that when create a child git group that the tenancy matches the parent git group
|
||||
- **devops**: Add Functional model Test Suite
|
||||
- **core**: Add Base Centurion model Functional Test Suite
|
||||
- **access**: Add Base Tenancy model Functional Test Suite
|
||||
- **base**: Add Base model Functional Test Suite
|
||||
- **core**: Model Unit Tests for AuditHistory `get_model_history` method
|
||||
- **core**: reset vals so as not to fuck other tests over
|
||||
- **core**: Correct test for method `get_audit_values` for `CenturionAbstractModel`
|
||||
- **devops**: Initial Model Unit tests for GitGroup
|
||||
- **core**: Add field `model_notes` as an excluded field for AuditModels
|
||||
- **core**: Remaining Unit Model Test Cases for CenturionAuditMeta Model
|
||||
- **core**: Initial Unit Model Test Cases for CenturionAuditMeta Model
|
||||
- **core**: Unit Model Test Cases for CenturionSubAbstract model
|
||||
- **core**: Initial Unit Model Test Cases for CenturionAudit Model
|
||||
- **core**: Unit test cases for Centurion get_url relative + non-relative
|
||||
- **access**: Unit Model Tests for TenancyAbstractModel
|
||||
- **base**: Unit Common Model test cases suite
|
||||
- **base**: Unit Common Class test cases suite
|
||||
- **access**: Unit Model Tests for TenancyAbstractModel
|
||||
|
||||
## 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
|
||||
|
@ -1,3 +1,51 @@
|
||||
## Version 1.19.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_ui) must be deployed seperatly.
|
||||
|
||||
- Removed API v1
|
||||
|
||||
|
||||
## Version 1.17.0
|
||||
|
||||
- Added setting for log files.
|
||||
|
||||
Enables user to specify a default path for centurion's logging. Add the following to your settings file `/etc/itsm/settings.py`
|
||||
|
||||
``` py
|
||||
LOG_FILES = {
|
||||
"centurion": "/var/log/centurion.log", # Normal Centurion Operations
|
||||
"weblog": "/var/log/weblog.log", # All web requests made to Centurion
|
||||
"rest_api": "/var/log/rest_api.log", # Rest API
|
||||
"catch_all":"/var/log/catch-all.log" # A catch all log. Note: does not log anything that has already been logged.
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
With this new setting, the previous setting `LOGGING` will no longer function.
|
||||
|
||||
- Renamed `Organization` model to `Tenant` so as to reflect what is actually is.
|
||||
|
||||
- `robots.txt` file now being served from the API container at path `/robots.txt` with `User-agent: *` and `Disallow: /`
|
||||
|
||||
|
||||
## Version 1.16.0
|
||||
|
||||
- Employees model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
||||
|
@ -1,12 +1,15 @@
|
||||
import django
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
admin.site.unregister(Group)
|
||||
|
||||
class TeamInline(admin.TabularInline):
|
||||
|
@ -1,6 +1,7 @@
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
|
||||
|
||||
class AutoCreatedField(models.DateTimeField):
|
||||
"""
|
||||
@ -50,7 +51,7 @@ class AutoLastModifiedField(AutoCreatedField):
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
|
||||
value = now()
|
||||
value = now().replace(microsecond=0)
|
||||
|
||||
setattr(model_instance, self.attname, value)
|
||||
|
||||
|
@ -1,38 +0,0 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
class OrganizationForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = [
|
||||
'name',
|
||||
'manager',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
@ -1,69 +0,0 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from .team_users import TeamUsersForm, TeamUsers
|
||||
|
||||
from access.models.team import Team
|
||||
from access.functions import permissions
|
||||
|
||||
from app import settings
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
TeamUserFormSet = inlineformset_factory(
|
||||
model=TeamUsers,
|
||||
parent_model= Team,
|
||||
extra = 1,
|
||||
fields=[
|
||||
'user',
|
||||
'manager'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TeamFormAdd(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = [
|
||||
'team_name',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = [
|
||||
'team_name',
|
||||
'permissions',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['permissions'].widget.attrs = {'style': "height: 200px;"}
|
||||
|
||||
self.fields['permissions'].queryset = permissions.permission_queryset()
|
@ -1,16 +0,0 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
class TeamUsersForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = TeamUsers
|
||||
fields = [
|
||||
'user',
|
||||
'manager',
|
||||
]
|
@ -1,4 +1,11 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import (
|
||||
ContentType,
|
||||
Permission
|
||||
)
|
||||
from django.db.models import QuerySet
|
||||
|
||||
|
||||
def permission_queryset():
|
||||
"""Filter Permissions to those used within the application
|
||||
@ -7,8 +14,9 @@ def permission_queryset():
|
||||
list: Filtered queryset that only contains the used permissions
|
||||
"""
|
||||
|
||||
apps = [
|
||||
centurion_apps = [
|
||||
'access',
|
||||
'accounting',
|
||||
'assistance',
|
||||
'config_management',
|
||||
'core',
|
||||
@ -36,22 +44,84 @@ 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',
|
||||
]
|
||||
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in=apps,
|
||||
).exclude(
|
||||
content_type__model__in=exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
||||
|
||||
if not settings.RUNNING_TESTS:
|
||||
|
||||
try:
|
||||
# This blocks purpose is to cater for fresh install
|
||||
# so that the app does not crash before the DB is setup.
|
||||
|
||||
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 = centurion_apps,
|
||||
).exclude(
|
||||
content_type__model__in = exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
return QuerySet()
|
||||
|
||||
else:
|
||||
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in = centurion_apps,
|
||||
).exclude(
|
||||
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)'),
|
||||
),
|
||||
]
|
526
app/access/migrations/0011_remove_entitynotes_model_and_more.py
Normal file
526
app/access/migrations/0011_remove_entitynotes_model_and_more.py
Normal file
@ -0,0 +1,526 @@
|
||||
# Generated by Django 5.1.10 on 2025-08-15 03:21
|
||||
|
||||
import access.models.tenancy_abstract
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
|
||||
("core", "0024_centurionaudit_centurionmodelnote_and_more"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="entity",
|
||||
name="is_global",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="tenant",
|
||||
name="slug",
|
||||
),
|
||||
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.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.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="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",),
|
||||
),
|
||||
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",),
|
||||
),
|
||||
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",),
|
||||
),
|
||||
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",),
|
||||
),
|
||||
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",),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="EntityHistory",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="EntityNotes",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="RoleHistory",
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name="RoleNotes",
|
||||
),
|
||||
]
|
@ -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:
|
||||
|
||||
@ -42,10 +46,6 @@ class Contact(
|
||||
|
||||
return self.f_name + ' ' + self.l_name
|
||||
|
||||
documentation = ''
|
||||
|
||||
history_model_name = 'contact'
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
|
@ -1,18 +1,23 @@
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
from access.fields import AutoLastModifiedField
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
from core.lib.feature_not_used import FeatureNotUsed
|
||||
from core.models.centurion import CenturionModel
|
||||
|
||||
|
||||
|
||||
class Entity(
|
||||
TenancyObject
|
||||
CenturionModel
|
||||
):
|
||||
|
||||
model_tag = 'entity'
|
||||
|
||||
documentation = ''
|
||||
|
||||
kb_model_name = 'entity'
|
||||
|
||||
url_model_name = 'entity'
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -29,31 +34,20 @@ class Entity(
|
||||
verbose_name_plural = 'Entities'
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'Primary key of the entry',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
entity_type = models.CharField(
|
||||
blank = False,
|
||||
default = Meta.verbose_name.lower(),
|
||||
help_text = 'Type this entity is',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Entity Type'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
@ -64,22 +58,8 @@ class Entity(
|
||||
return str( related_model )
|
||||
|
||||
|
||||
|
||||
# app_namespace = 'access'
|
||||
|
||||
history_app_label = 'access'
|
||||
|
||||
history_model_name = 'entity'
|
||||
|
||||
kb_model_name = 'entity'
|
||||
|
||||
note_basename = '_api_v2_entity_note'
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'entity_type',
|
||||
@ -89,6 +69,23 @@ class Entity(
|
||||
]
|
||||
|
||||
|
||||
|
||||
def clean_fields(self, exclude = None ):
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
if self.entity_type != str(related_model._meta.verbose_name).lower().replace(' ', '_'):
|
||||
|
||||
self.entity_type = str(related_model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
super().clean_fields( exclude = exclude )
|
||||
|
||||
|
||||
|
||||
def get_related_field_name(self) -> str:
|
||||
|
||||
meta = getattr(self, '_meta')
|
||||
@ -101,13 +98,12 @@ class Entity(
|
||||
|
||||
if getattr(self, related_object.name, None):
|
||||
|
||||
if(
|
||||
if(
|
||||
not str(related_object.name).endswith('history')
|
||||
and not str(related_object.name).endswith('notes')
|
||||
):
|
||||
|
||||
return related_object.name
|
||||
break
|
||||
|
||||
|
||||
return ''
|
||||
@ -145,105 +141,3 @@ class Entity(
|
||||
|
||||
|
||||
return related_model
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
if len(self._meta.parents) == 0 and model is None:
|
||||
|
||||
return {
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
kwargs = {
|
||||
'entity_model': str(model._meta.verbose_name).lower().replace(' ', '_'),
|
||||
}
|
||||
|
||||
if model.pk:
|
||||
|
||||
kwargs.update({
|
||||
'pk': model.id
|
||||
})
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
"""Fetch the models URL
|
||||
|
||||
If URL kwargs are required to generate the URL, define a `get_url_kwargs` that returns them.
|
||||
|
||||
Args:
|
||||
request (object, optional): The request object that was made by the end user. Defaults to None.
|
||||
|
||||
Returns:
|
||||
str: Canonical URL of the model if the `request` object was provided. Otherwise the relative URL.
|
||||
"""
|
||||
|
||||
model = None
|
||||
|
||||
if getattr(self, 'get_related_model', None):
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
|
||||
sub_entity = ''
|
||||
if model._meta.model_name != 'entity':
|
||||
|
||||
sub_entity = '_sub'
|
||||
|
||||
|
||||
kwargs = self.get_url_kwargs()
|
||||
|
||||
view = 'list'
|
||||
if 'pk' in kwargs:
|
||||
|
||||
view = 'detail'
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, request=request, kwargs = kwargs )
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, kwargs = kwargs )
|
||||
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
if self.entity_type != str(related_model._meta.verbose_name).lower().replace(' ', '_'):
|
||||
|
||||
self.entity_type = str(related_model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.entity_history import EntityHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = EntityHistory
|
||||
)
|
||||
|
||||
return history
|
||||
|
@ -1,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:
|
||||
|
||||
@ -36,7 +40,6 @@ class Person(
|
||||
|
||||
m_name = models.CharField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'The persons middle name(s)',
|
||||
max_length = 100,
|
||||
null = True,
|
||||
@ -54,7 +57,6 @@ class Person(
|
||||
|
||||
dob = models.DateField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'The Persons Date of Birth (DOB)',
|
||||
null = True,
|
||||
unique = False,
|
||||
@ -65,10 +67,6 @@ class Person(
|
||||
|
||||
return self.f_name + ' ' + self.l_name + f' (DOB: {self.dob})'
|
||||
|
||||
documentation = ''
|
||||
|
||||
history_model_name = 'person'
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
table_fields: list = [
|
||||
@ -104,7 +102,7 @@ class Person(
|
||||
|
||||
|
||||
for entry in duplicate_entry:
|
||||
|
||||
|
||||
if(
|
||||
entry.f_name == self.f_name
|
||||
and entry.m_name == self.m_name
|
||||
@ -114,8 +112,8 @@ class Person(
|
||||
|
||||
raise ValidationError(
|
||||
detail = {
|
||||
'dob': f'Person {self.f_name} {self.l_name} already exists with this birthday {entry.dob}'
|
||||
'dob': f'Person {self.f_name} {self.l_name}' \
|
||||
f'already exists with this birthday {entry.dob}'
|
||||
},
|
||||
code = 'duplicate_person_on_dob'
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
@ -55,13 +55,13 @@ class Team(Group, TenancyObject):
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Organization this belongs to',
|
||||
help_text = 'Tenant this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Organization'
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
@ -179,12 +179,12 @@ class Team(Group, TenancyObject):
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.team_history import TeamHistory
|
||||
from access.models.team_history import TeamAuditHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = TeamHistory
|
||||
history_model = TeamAuditHistory
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import django
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
@ -9,11 +11,13 @@ from access.fields import (
|
||||
AutoLastModifiedField
|
||||
)
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
from access.models.team import Team
|
||||
|
||||
from core.lib.feature_not_used import FeatureNotUsed
|
||||
from core.mixin.history_save import SaveHistory
|
||||
from core.mixins.history_save import SaveHistory
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
@ -95,7 +99,7 @@ class TeamUsers(SaveHistory):
|
||||
user.groups.remove(group)
|
||||
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
def get_organization(self) -> Tenant:
|
||||
return self.team.organization
|
||||
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
# from django.conf import settings
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
# from django.contrib.auth.models import User, Group
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
# from .fields import *
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
from core.middleware.get_request import get_request
|
||||
from core.mixin.history_save import SaveHistory
|
||||
from core.mixins.history_save import SaveHistory
|
||||
|
||||
|
||||
|
||||
@ -53,6 +51,10 @@ class TenancyManager(models.Manager):
|
||||
|
||||
user_organizations: list(str()) = []
|
||||
|
||||
has_tenant_field = False
|
||||
if getattr(self.model, 'organization', None) is not None:
|
||||
has_tenant_field = True
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
@ -71,29 +73,33 @@ class TenancyManager(models.Manager):
|
||||
|
||||
if team.organization.id not in user_organizations:
|
||||
|
||||
if not user_organizations:
|
||||
# if not user_organizations:
|
||||
|
||||
self.user_organizations = []
|
||||
# self.user_organizations = []
|
||||
|
||||
user_organizations += [ team.organization.id ]
|
||||
|
||||
|
||||
# if len(user_organizations) > 0 and not user.is_superuser and self.model.is_global is not None:
|
||||
if len(user_organizations) > 0 and not user.is_superuser:
|
||||
|
||||
if getattr(self.model, 'is_global', False) is True:
|
||||
if has_tenant_field:
|
||||
|
||||
return super().get_queryset().filter(
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
|
|
||||
models.Q(is_global = True)
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
return super().get_queryset().filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
)
|
||||
# return super().get_queryset().filter(
|
||||
# models.Q(organization__in=user_organizations)
|
||||
# )
|
||||
|
||||
return super().get_queryset().filter()
|
||||
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
@ -137,14 +143,14 @@ class TenancyObject(SaveHistory):
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Organization this belongs to',
|
||||
help_text = 'Tenancy this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = '+',
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Organization'
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
is_global = models.BooleanField(
|
||||
@ -162,7 +168,7 @@ class TenancyObject(SaveHistory):
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
def get_organization(self) -> Tenant:
|
||||
return self.organization
|
||||
|
||||
app_namespace: str = None
|
||||
@ -193,6 +199,16 @@ class TenancyObject(SaveHistory):
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
_log: logging.Logger = None
|
||||
|
||||
def get_log(self):
|
||||
|
||||
if self._log is None:
|
||||
|
||||
self._log = logging.getLogger('centurion.' + self._meta.app_label)
|
||||
|
||||
return self._log
|
||||
|
||||
page_layout: list = None
|
||||
|
||||
note_basename: str = None
|
||||
@ -220,7 +236,7 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
if self.app_namespace:
|
||||
|
||||
app_namespace = self.app_namespace + ':'
|
||||
app_namespace = self.app_namespace
|
||||
|
||||
return str(app_namespace)
|
||||
|
||||
@ -239,12 +255,17 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
model_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
|
||||
|
||||
namespace = f'v2'
|
||||
|
||||
if self.get_app_namespace():
|
||||
namespace = namespace + ':' + self.get_app_namespace()
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:" + self.get_app_namespace() + f"_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
return reverse(f"{namespace}:_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
|
||||
return reverse(f"v2:" + self.get_app_namespace() + f"_api_v2_{model_name}-detail", kwargs = self.get_url_kwargs() )
|
||||
return reverse(f"{namespace}:_api_v2_{model_name}-detail", kwargs = self.get_url_kwargs() )
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
@ -282,7 +303,7 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
raise centurion_exceptions.ValidationError(
|
||||
detail = {
|
||||
'organization': 'Organization is required'
|
||||
'organization': 'Tenant is required'
|
||||
},
|
||||
code = 'required'
|
||||
)
|
||||
|
126
app/access/models/tenancy_abstract.py
Normal file
126
app/access/models/tenancy_abstract.py
Normal file
@ -0,0 +1,126 @@
|
||||
from django.core.exceptions import (
|
||||
ValidationError,
|
||||
)
|
||||
from django.db import models
|
||||
|
||||
from access.models.tenancy import (
|
||||
TenancyManager as TenancyManagerDepreciated
|
||||
)
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
class TenancyManager(
|
||||
models.Manager
|
||||
):
|
||||
"""Multi-Tennant Object Manager
|
||||
|
||||
This manager specifically caters for the multi-tenancy features of Centurion ERP.
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
""" Fetch the data
|
||||
|
||||
When the model contains the user data, the query is filtered to their
|
||||
and the globally defined Tenancy only.
|
||||
|
||||
Returns:
|
||||
(queryset): **super user**: return unfiltered data.
|
||||
(queryset): **not super user**: return data from the stored unique organizations.
|
||||
"""
|
||||
|
||||
user = None # When CenturionUser in use
|
||||
|
||||
if hasattr(self.model, 'context'):
|
||||
|
||||
user = self.model.context['user']
|
||||
|
||||
|
||||
has_tenant_field = False
|
||||
if getattr(self.model, 'organization', None) is not None:
|
||||
has_tenant_field = True
|
||||
|
||||
|
||||
if user:
|
||||
|
||||
tenancies = user.get_tenancies(int_list = True)
|
||||
|
||||
if len(tenancies) > 0 and not request.user.is_superuser:
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in = tenancies)
|
||||
)
|
||||
|
||||
|
||||
return super().get_queryset().filter()
|
||||
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
|
||||
class TenancyAbstractModel(
|
||||
models.Model,
|
||||
):
|
||||
""" Tenancy Model Abstract class.
|
||||
|
||||
This class is for inclusion within **every** model within Centurion ERP.
|
||||
Provides the required fields, functions and methods for multi tennant objects.
|
||||
Unless otherwise stated, **no** object within this class may be overridden.
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization
|
||||
"""
|
||||
|
||||
objects = TenancyManagerDepreciated()
|
||||
""" ~~Multi-Tenant Manager~~
|
||||
|
||||
**Note:** ~~This manager relies upon the model class having `context['user']`
|
||||
set. without a user the manager can not perform multi-tenant queries.~~
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
def validatate_organization_exists(self):
|
||||
"""Ensure that the user did provide an organization
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
raise ValidationError(
|
||||
code = 'required',
|
||||
message = 'You must provide an organization'
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Tenant this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = '+',
|
||||
validators = [
|
||||
validatate_organization_exists
|
||||
],
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
|
||||
|
||||
def get_tenant(self) -> Tenant:
|
||||
""" Return the models Tenancy
|
||||
|
||||
This model can be safely over-ridden as long as it returns the models
|
||||
tenancy
|
||||
"""
|
||||
return self.organization
|
143
app/access/models/tenant.py
Normal file
143
app/access/models/tenant.py
Normal file
@ -0,0 +1,143 @@
|
||||
import django
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from access.fields import (
|
||||
AutoCreatedField,
|
||||
AutoLastModifiedField,
|
||||
)
|
||||
|
||||
from core.mixins.centurion import Centurion
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
class Tenant(
|
||||
Centurion,
|
||||
):
|
||||
|
||||
@property
|
||||
def organization(self):
|
||||
return self
|
||||
|
||||
model_tag = 'tenant'
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = "Tenant"
|
||||
|
||||
verbose_name_plural = "Tenants"
|
||||
|
||||
ordering = [
|
||||
'name'
|
||||
]
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank = False,
|
||||
help_text = 'ID of this item',
|
||||
primary_key = True,
|
||||
unique = True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this Tenancy',
|
||||
max_length = 50,
|
||||
unique = True,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank = True,
|
||||
help_text = 'Manager for this Tenancy',
|
||||
null = True,
|
||||
on_delete = models.PROTECT,
|
||||
verbose_name = 'Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
help_text = 'Tid bits of information',
|
||||
null = True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
def __int__(self):
|
||||
|
||||
return self.id
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
def get_organization(self):
|
||||
return self
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'nbsp',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
'nbsp'
|
||||
]
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'name',
|
||||
'manager',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Teams",
|
||||
"slug": "teams",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "teams"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
Organization = Tenant
|
197
app/access/models/user_proxy.py
Normal file
197
app/access/models/user_proxy.py
Normal file
@ -0,0 +1,197 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
class CenturionUser(
|
||||
User,
|
||||
):
|
||||
"""Centurion User
|
||||
|
||||
A Multi-Tenant User wirh permission Checking.
|
||||
|
||||
ToDo:
|
||||
- Add to Roles user field `related_name = roles`
|
||||
- Add to Roles group field `related_name = roles`
|
||||
# - have group lookup prefetch related roles__permissions
|
||||
- have user lookup prefetch related roles__permissions and groups__roles__permissions
|
||||
|
||||
Args:
|
||||
User (Model): Django Base User
|
||||
"""
|
||||
|
||||
_tenancies: list[Tenant] = None
|
||||
|
||||
_tenancies_int: list[int] = None
|
||||
|
||||
_permissions: list[Permission] = None
|
||||
|
||||
_permissions_by_tenancy: dict[ str, list[ Permission ] ] = None
|
||||
"""Permissions by Tenancy
|
||||
|
||||
`{ 'tenancy_{id}': [ Permission ] }`
|
||||
"""
|
||||
|
||||
# EMAIL_FIELD = 'email' # Update contact email field name so it's different to the user model.
|
||||
|
||||
# REQUIRED_FIELDS = [
|
||||
# EMAIL_FIELD,
|
||||
# 'f_name',
|
||||
# 'l_name',
|
||||
# ]
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
proxy = True # User will be linked to Employee/Customer entity via related_name from the entity.
|
||||
# ToDo: refactory Employee/Customer to inherit from a new model. entity_user
|
||||
|
||||
verbose_name = 'Centurion User'
|
||||
|
||||
verbose_name_plural = 'Centurion Users'
|
||||
|
||||
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
return f'{self.entity_user.f_name} {self.entity_user.l_name}'
|
||||
|
||||
|
||||
|
||||
def get_group_permissions(self, tenancy: bool = True) -> dict[ str, list[ Permission ] ] | list[ Permission ]:
|
||||
""" Get the Users Permissions
|
||||
|
||||
Args:
|
||||
tenancy (bool, optional): Return permission in list. Defaults to True.
|
||||
|
||||
Returns:
|
||||
dict[ str, list[ Permission ] ]: Permissions listed by tenancy
|
||||
list[ Permission ]: All Permissions
|
||||
"""
|
||||
|
||||
for group in self.groups: # pylint: disable=E1133:not-an-iterable
|
||||
|
||||
for role in group.roles:
|
||||
pass
|
||||
|
||||
# role.get_permissions()
|
||||
|
||||
|
||||
|
||||
def get_permissions(self, tenancy: bool = True) -> dict[ str, list[ Permission ] ] | list[ Permission ]:
|
||||
""" Get the Users Permissions
|
||||
|
||||
Args:
|
||||
tenancy (bool, optional): Return permission in list. Defaults to True.
|
||||
|
||||
Returns:
|
||||
dict[ str, list[ Permission ] ]: Permissions listed by tenancy
|
||||
list[ Permission ]: All Permissions
|
||||
"""
|
||||
|
||||
# also get group permissions. self.get_group_permissions()
|
||||
|
||||
for role in self.roles:
|
||||
pass
|
||||
|
||||
# role.get_permissions()
|
||||
|
||||
# also populate `self._tenancies` and `self._tenancies_int`
|
||||
|
||||
return []
|
||||
|
||||
|
||||
|
||||
def get_short_name() -> str:
|
||||
return self.entity_user.f_name
|
||||
|
||||
|
||||
|
||||
def get_tenancies(self, int_list = False) -> list[ Tenant ] | list[ int ]:
|
||||
"""Get the Tenancies the user is in.
|
||||
|
||||
Args:
|
||||
int_list (bool, optional): Return Tenancy list as int values. Defaults to False.
|
||||
|
||||
Returns:
|
||||
list[ Tenant ] | list[ int ]: All Tenancies the user is in.
|
||||
"""
|
||||
|
||||
if self._tenancies is None:
|
||||
|
||||
if self._permissions is None:
|
||||
self.get_permissions
|
||||
|
||||
tenancies: list = []
|
||||
tenancies_int: list = []
|
||||
|
||||
for role in self.roles:
|
||||
|
||||
if role.organization in tenancies:
|
||||
continue
|
||||
|
||||
tenancies += [ role.organization ]
|
||||
tenancies_int += [ role.organization.id ]
|
||||
|
||||
self._tenancies = tenancies
|
||||
self._tenancies_int = tenancies_int
|
||||
|
||||
|
||||
if as_int_list:
|
||||
return self._tenancies_int
|
||||
|
||||
return self._tenancies
|
||||
|
||||
|
||||
|
||||
def has_module_perms(self, app_label): # is this needed?
|
||||
|
||||
# if has app_label in perms
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
|
||||
def has_perm(self, permission: Permission, obj = None, tenancy: Tenant = None) -> bool:
|
||||
|
||||
if(
|
||||
obj is None
|
||||
and tenancy is None
|
||||
):
|
||||
raise ValueError('Both obj and tenancy cant be None')
|
||||
|
||||
if tenancy is None:
|
||||
tenancy = obj.organization
|
||||
|
||||
# if self.has_tenancy_permission(perm, tenancy):
|
||||
# for tenancy, permissions in self.get_permissions().items()
|
||||
|
||||
if tenancy is None:
|
||||
raise ValueError('tenancy cant be None')
|
||||
|
||||
permissions = self.get_permissions()
|
||||
|
||||
if f'tenancy_{tenancy.id}' not in permissions:
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
for tenancy, permissions in self.get_permissions().items():
|
||||
|
||||
if(
|
||||
tenancy == f'tenancy_{tenancy.id}'
|
||||
and perm in permissions
|
||||
):
|
||||
return True
|
||||
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
|
||||
def has_perms(self, permission_list: list[ Permission ], obj = None, tenancy: Tenant = None):
|
||||
|
||||
for perm in perm_list:
|
||||
|
||||
self.has_perm( perm, obj )
|
||||
|
||||
return True
|
56
app/access/serializers/centurionaudit_company.py
Normal file
56
app/access/serializers/centurionaudit_company.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import CompanyAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = CompanyAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_contact.py
Normal file
56
app/access/serializers/centurionaudit_contact.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import ContactAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ContactAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_entity.py
Normal file
56
app/access/serializers/centurionaudit_entity.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import EntityAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_person.py
Normal file
56
app/access/serializers/centurionaudit_person.py
Normal file
@ -0,0 +1,56 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import PersonAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PersonAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
56
app/access/serializers/centurionaudit_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)
|
@ -6,7 +6,7 @@ from access.serializers.entity_person import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -46,7 +46,6 @@ class ModelSerializer(
|
||||
'email',
|
||||
'directory',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
@ -72,4 +71,4 @@ class ViewSerializer(
|
||||
This model inherits from the Person model.
|
||||
"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
@ -1,41 +0,0 @@
|
||||
from core.serializers.model_notes import (
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
from access.models.entity_notes import EntityNotes
|
||||
|
||||
|
||||
|
||||
class EntityNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EntityNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class EntityNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
EntityNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -6,7 +6,7 @@ from access.serializers.entity import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -44,7 +44,6 @@ class ModelSerializer(
|
||||
'l_name',
|
||||
'dob',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
@ -70,4 +69,4 @@ class ViewSerializer(
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
@ -2,15 +2,16 @@ from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
from centurion.serializers.user import UserBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
Organization = Tenant
|
||||
|
||||
|
||||
class OrganizationBaseSerializer(serializers.ModelSerializer):
|
||||
class TenantBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
@ -19,12 +20,12 @@ class OrganizationBaseSerializer(serializers.ModelSerializer):
|
||||
return str( item )
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v2:_api_v2_organization-detail", format="html"
|
||||
view_name="v2:_api_tenant-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Organization
|
||||
model = Tenant
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
@ -42,8 +43,8 @@ class OrganizationBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
|
||||
class OrganizationModelSerializer(
|
||||
OrganizationBaseSerializer
|
||||
class TenantModelSerializer(
|
||||
TenantBaseSerializer
|
||||
):
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
@ -60,13 +61,13 @@ class OrganizationModelSerializer(
|
||||
'model_pk': item.pk
|
||||
}
|
||||
),
|
||||
'notes': reverse(
|
||||
"v2:_api_v2_organization_note-list",
|
||||
request=self._context['view'].request,
|
||||
kwargs={
|
||||
'model_id': item.pk
|
||||
}
|
||||
),
|
||||
# 'notes': reverse(
|
||||
# "v2:_api_v2_organization_note-list",
|
||||
# request=self._context['view'].request,
|
||||
# kwargs={
|
||||
# 'model_id': item.pk
|
||||
# }
|
||||
# ),
|
||||
'teams': reverse("v2:_api_v2_organization_team-list", request=self._context['view'].request, kwargs={'organization_id': item.pk}),
|
||||
}
|
||||
|
||||
@ -74,7 +75,7 @@ class OrganizationModelSerializer(
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Organization
|
||||
model = Tenant
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
@ -98,7 +99,7 @@ class OrganizationModelSerializer(
|
||||
]
|
||||
|
||||
|
||||
class OrganizationViewSerializer(OrganizationModelSerializer):
|
||||
class TenantViewSerializer(TenantModelSerializer):
|
||||
pass
|
||||
|
||||
manager = UserBaseSerializer(many=False, read_only = True)
|
||||
|
@ -1,48 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.organization_notes import OrganizationNotes
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNotes,
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class OrganizationNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = OrganizationNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class OrganizationNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
OrganizationNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -5,11 +5,11 @@ from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.functions.permissions import permission_queryset
|
||||
from access.models.role import Role
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.permission import PermissionBaseSerializer
|
||||
from centurion.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -109,6 +109,6 @@ class ModelSerializer(
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Role Base View Model"""
|
||||
|
||||
organization = OrganizationBaseSerializer( many=False, read_only=True )
|
||||
organization = TenantBaseSerializer( many=False, read_only=True )
|
||||
|
||||
permissions = PermissionBaseSerializer( many=True, read_only=True )
|
||||
|
@ -1,48 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.role_notes import RoleNotes
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNotes,
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class RoleNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RoleNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = RoleNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class RoleNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
RoleNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -1,48 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.team_notes import TeamNotes
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNotes,
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TeamNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TeamNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TeamNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
TeamNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -1,12 +1,10 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
from centurion.serializers.user import UserBaseSerializer
|
||||
|
||||
|
||||
|
||||
@ -55,7 +53,7 @@ class TeamUserModelSerializer(
|
||||
|
||||
get_url = super().get_url( item = item )
|
||||
|
||||
del get_url['history']
|
||||
# del get_url['history']
|
||||
|
||||
del get_url['knowledge_base']
|
||||
|
||||
|
@ -7,9 +7,9 @@ from access.models.team import Team
|
||||
from api.serializers import common
|
||||
|
||||
from access.functions.permissions import permission_queryset
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
from app.serializers.permission import Permission, PermissionBaseSerializer
|
||||
from centurion.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
@ -94,7 +94,6 @@ class TeamModelSerializer(
|
||||
'model_notes',
|
||||
'permissions',
|
||||
'organization',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
@ -127,6 +126,6 @@ class TeamModelSerializer(
|
||||
|
||||
class TeamViewSerializer(TeamModelSerializer):
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
||||
permissions = PermissionBaseSerializer(many = True)
|
||||
|
@ -1,22 +0,0 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block content_header_icon %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
</tr>
|
||||
{% for org in organization_list %}
|
||||
<tr>
|
||||
<td><a href="/organization/{{ org.id }}/">{{ org.name }}</a></td>
|
||||
<td>{{ org.created }}</td>
|
||||
<td>{{ org.modified }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endblock %}
|
@ -1,106 +0,0 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}Organization - {{ organization.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
form div .helptext {
|
||||
background-color: rgb(0, 140, 255);
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.detail-view-field {
|
||||
display:unset;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0px 20px 40px 20px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field label {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
width: 200px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field span {
|
||||
display: inline-block;
|
||||
width: 340px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
||||
<div style="display: inline; width: 40%; margin: 30px;">
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.name.label }}</label>
|
||||
<span>{{ form.name.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.manager.label }}</label>
|
||||
<span>{{ organization.manager }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.created.label }}</label>
|
||||
<span>{{ form.created.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.modified.label }}</label>
|
||||
<span>{{ form.modified.value }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
|
||||
<div>
|
||||
<label style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">{{ form.model_notes.label }}</label>
|
||||
|
||||
<div style="display: inline-block; text-align: left;">{{ form.model_notes.value | markdown | safe }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: block;">
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:Organizations' %}';">
|
||||
<input type="button" value="New Team" onclick="window.location='{% url 'Access:_team_add' organization.id %}';">
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Team Name</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for field in teams %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Access:_team_view' organization_id=organization.id pk=field.id %}">{{ field.team_name }}</a></td>
|
||||
<td>{{ field.created }}</td>
|
||||
<td>{{ field.modified }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
@ -1,48 +0,0 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}Team - {{ team.team_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form.as_div }}
|
||||
|
||||
<input style="display:unset;" type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:_organization_view' pk=organization.id %}';">
|
||||
<input type="button" value="Delete Team"
|
||||
onclick="window.location='{% url 'Access:_team_delete' organization_id=organization.id pk=team.id %}';">
|
||||
<input type="button" value="Assign User"
|
||||
onclick="window.location='{% url 'Access:_team_user_add' organization_id=organization.id pk=team.id %}';">
|
||||
{{ formset.management_form }}
|
||||
|
||||
<table id="formset" class="form">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Manager</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for field in teamusers %}
|
||||
<tr>
|
||||
<td>{{ field.user }}</td>
|
||||
<td><input type="checkbox" {% if field.manager %}checked{% endif %} disabled></td>
|
||||
<td>{{ field.created }}</td>
|
||||
<td>{{ field.modified }}</td>
|
||||
<td><a
|
||||
href="{% url 'Access:_team_user_delete' organization_id=organization.id team_id=field.team_id pk=field.id %}">Delete</a></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
@ -1,32 +0,0 @@
|
||||
|
||||
|
||||
|
||||
class TenancyObject:
|
||||
""" Tests for checking TenancyObject """
|
||||
|
||||
model = None
|
||||
""" Model to be tested """
|
||||
|
||||
should_model_history_be_saved: bool = True
|
||||
""" Should model history be saved.
|
||||
|
||||
By default this should always be 'True', however in special
|
||||
circumstances, this may not be desired.
|
||||
"""
|
||||
|
||||
|
||||
# def test_history_save(self):
|
||||
# """Confirm the desired intent for saving model history."""
|
||||
|
||||
# assert self.model.save_model_history == self.should_model_history_be_saved
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_edit_no_organization_fails(self):
|
||||
# """ Devices must be assigned an organization
|
||||
|
||||
# Must not be able to edit an item without an organization
|
||||
# """
|
||||
# pass
|
||||
|
@ -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,104 @@
|
||||
import pytest
|
||||
|
||||
from django.test import Client
|
||||
|
||||
|
||||
|
||||
class AdditionalTestCases:
|
||||
|
||||
|
||||
def test_permission_change(self, model_instance, api_request_permissions):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change with user who has change permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['change'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['user'],
|
||||
'model': api_request_permissions['tenancy']['user']
|
||||
})
|
||||
|
||||
change_item = model_instance(
|
||||
kwargs_create = kwargs,
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
path = change_item.get_url( many = False ),
|
||||
data = self.change_data,
|
||||
content_type = 'application/json'
|
||||
)
|
||||
|
||||
if response.status_code == 405:
|
||||
pytest.xfail( reason = 'ViewSet does not have this request method.' )
|
||||
|
||||
assert response.status_code == 200, response.content
|
||||
|
||||
|
||||
|
||||
def test_permission_delete(self, model_instance, api_request_permissions):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user with delete permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['delete'] )
|
||||
|
||||
kwargs = self.kwargs_create_item
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['user'],
|
||||
'model': api_request_permissions['tenancy']['user']
|
||||
})
|
||||
|
||||
delete_item = model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
response = client.delete(
|
||||
path = delete_item.get_url( many = False ),
|
||||
)
|
||||
|
||||
if response.status_code == 405:
|
||||
pytest.xfail( reason = 'ViewSet does not have this request method.' )
|
||||
|
||||
assert response.status_code == 204, response.content
|
||||
|
||||
def test_permission_view(self, model_instance, api_request_permissions):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['user'],
|
||||
'model': api_request_permissions['tenancy']['user']
|
||||
})
|
||||
|
||||
view_item = model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
path = view_item.get_url( many = False )
|
||||
)
|
||||
|
||||
if response.status_code == 405:
|
||||
pytest.xfail( reason = 'ViewSet does not have this request method.' )
|
||||
|
||||
assert response.status_code == 200, response.content
|
||||
|
||||
|
||||
@pytest.mark.xfail( reason = 'model is not global based')
|
||||
def test_returned_data_from_user_and_global_organizations_only(self ):
|
||||
assert False
|
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
|
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
|
@ -1,129 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.serializers.entity_contact import (
|
||||
Contact,
|
||||
ModelSerializer
|
||||
)
|
||||
from access.tests.functional.person.test_functional_person_serializer import (
|
||||
PersonSerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class SerializerTestCases(
|
||||
PersonSerializerInheritedCases,
|
||||
):
|
||||
|
||||
duplicate_f_name_l_name_dob = {
|
||||
'email': 'contactentityduplicateone@unit.test',
|
||||
}
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
|
||||
|
||||
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'
|
||||
|
||||
|
||||
|
||||
class ContactSerializerInheritedCases(
|
||||
SerializerTestCases,
|
||||
):
|
||||
|
||||
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"""
|
||||
|
||||
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,
|
||||
):
|
||||
|
||||
pass
|
@ -1,169 +0,0 @@
|
||||
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
|
||||
|
||||
|
||||
|
||||
class ContactViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
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 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
|
@ -1,78 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_history import Entity, EntityHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class HistoryTestCases(
|
||||
HistoryEntriesCommon,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
history_model = EntityHistory
|
||||
|
||||
kwargs_create_obj: dict = {}
|
||||
|
||||
kwargs_delete_obj: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = self.field_value_original,
|
||||
**self.kwargs_create_obj,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'another note',
|
||||
**self.kwargs_delete_obj,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
||||
|
||||
|
||||
|
||||
class EntityHistoryInheritedCases(
|
||||
HistoryTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityHistoryTest(
|
||||
HistoryTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,260 @@
|
||||
import django
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from accounting.models.asset_base import AssetBase
|
||||
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTestCases(
|
||||
MetadataAttributesFunctional,
|
||||
):
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
base_model = Entity
|
||||
"""Base model for this sub model
|
||||
don't change or override this value
|
||||
"""
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({
|
||||
'organization': self.organization.id,
|
||||
})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = self.different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
def test_sanity_is_entity_sub_model(self):
|
||||
"""Sanity Test
|
||||
|
||||
This test ensures that the model being tested `self.model` is a
|
||||
sub-model of `self.base_model`.
|
||||
This test is required as the same viewset is used for all sub-models
|
||||
of `AssetBase`
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, self.base_model)
|
||||
|
||||
|
||||
|
||||
class EntityMetadataInheritedCases(
|
||||
EntityMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
url_name = '_api_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTest(
|
||||
EntityMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
url_name = '_api_entity'
|
@ -1,94 +1,171 @@
|
||||
import django
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.serializers.entity import (
|
||||
Entity,
|
||||
ModelSerializer
|
||||
)
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class SerializerTestCases:
|
||||
class MockView:
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
""" Model kwargs to create item"""
|
||||
_has_import: bool = False
|
||||
"""User Permission
|
||||
|
||||
model = Entity
|
||||
"""Model to test"""
|
||||
get_permission_required() sets this to `True` when user has import permission.
|
||||
"""
|
||||
|
||||
create_model_serializer = ModelSerializer
|
||||
"""Serializer to test"""
|
||||
_has_purge: bool = False
|
||||
"""User Permission
|
||||
|
||||
valid_data: dict = {}
|
||||
get_permission_required() sets this to `True` when user has purge permission.
|
||||
"""
|
||||
|
||||
_has_triage: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has triage permission.
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.skip(reason = 'see #874, tests being refactored')
|
||||
class EntitySerializerTestCases:
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"model_notes": {
|
||||
'will_create': True,
|
||||
}
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'model_notes': 'model notes field'
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.kwargs_create_item.update({
|
||||
'model_notes': 'model notes field'
|
||||
})
|
||||
@pytest.fixture( scope = 'class')
|
||||
def setup_data(self,
|
||||
request,
|
||||
model,
|
||||
django_db_blocker,
|
||||
organization_one,
|
||||
):
|
||||
|
||||
self.valid_data.update({
|
||||
'organization': self.organization.pk,
|
||||
'model_notes': 'model notes field'
|
||||
})
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
**self.kwargs_create_item,
|
||||
)
|
||||
request.cls.organization = organization_one
|
||||
|
||||
valid_data = {}
|
||||
|
||||
for base in reversed(request.cls.__mro__):
|
||||
|
||||
if hasattr(base, 'valid_data'):
|
||||
|
||||
if base.valid_data is None:
|
||||
|
||||
continue
|
||||
|
||||
valid_data.update(**base.valid_data)
|
||||
|
||||
|
||||
if len(valid_data) > 0:
|
||||
|
||||
request.cls.valid_data = valid_data
|
||||
|
||||
|
||||
if 'organization' not in request.cls.valid_data:
|
||||
|
||||
request.cls.valid_data.update({
|
||||
'organization': request.cls.organization.pk
|
||||
})
|
||||
|
||||
|
||||
request.cls.view_user = User.objects.create_user(username="cafs_test_user_view", password="password")
|
||||
|
||||
|
||||
yield
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.view_user.delete()
|
||||
|
||||
del request.cls.valid_data
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data(self):
|
||||
@pytest.fixture( scope = 'class', autouse = True)
|
||||
def class_setup(self,
|
||||
setup_data,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test_serializer_valid_data(self, create_serializer):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with valid data, no validation
|
||||
error occurs.
|
||||
"""
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
view_set = MockView()
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
def test_serializer_validation_no_model_notes(self):
|
||||
|
||||
def test_serializer_valid_data_missing_field_is_valid(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_will_create,
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no model_notes is provided no validation
|
||||
error occurs
|
||||
Ensure that when creating an object with a user with import permission
|
||||
and with valid data, no validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['model_notes']
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
view_set._has_import = True
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
is_valid = serializer.is_valid(raise_exception = False)
|
||||
|
||||
assert (
|
||||
(
|
||||
not param_will_create
|
||||
and param_will_create == is_valid
|
||||
)
|
||||
or param_will_create == is_valid
|
||||
)
|
||||
|
||||
|
||||
|
||||
class EntitySerializerInheritedCases(
|
||||
SerializerTestCases,
|
||||
EntitySerializerTestCases,
|
||||
):
|
||||
|
||||
create_model_serializer = None
|
||||
"""Serializer to test"""
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
""" Model kwargs to create item"""
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
@ -97,10 +174,40 @@ class EntitySerializerInheritedCases(
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
def test_serializer_valid_data_missing_field_raises_exception(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_exception_key,
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
class EntitySerializerTest(
|
||||
SerializerTestCases,
|
||||
TestCase,
|
||||
Ensure that when creating an object with a user with import permission
|
||||
and with valid data, no validation error occurs.
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
is_valid = serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()[param_value][0] == param_exception_key
|
||||
|
||||
|
||||
|
||||
class EntitySerializerPyTest(
|
||||
EntitySerializerTestCases,
|
||||
):
|
||||
|
||||
pass
|
||||
parameterized_test_data: dict = None
|
||||
|
@ -1,504 +0,0 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
|
||||
from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
model_notes = 'some notes',
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
model_notes = 'some more notes',
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({'organization': self.organization.id})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = self.different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
):
|
||||
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
change_data = {'model_notes': 'device'}
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.add_data.update({ 'model_note': 'added model note' })
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a tenancy model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPIInheritedCases(
|
||||
PermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPITest(
|
||||
PermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
|
||||
class EntityViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
ViewSetBase,
|
||||
MetadataAttributesFunctional,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
|
||||
class EntityMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
# def test_method_options_request_detail_data_has_key_urls_back(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data returned has key `urls.back`
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# response = client.options(
|
||||
# reverse(
|
||||
# self.app_namespace + ':' + self.url_name + '-detail',
|
||||
# kwargs=self.url_view_kwargs
|
||||
# ),
|
||||
# content_type='application/json'
|
||||
# )
|
||||
|
||||
# assert 'back' in response.data['urls']
|
||||
|
||||
|
||||
# def test_method_options_request_detail_data_key_urls_back_is_str(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data key `urls.back` is str
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# response = client.options(
|
||||
# reverse(
|
||||
# self.app_namespace + ':' + self.url_name + '-detail',
|
||||
# kwargs=self.url_view_kwargs
|
||||
# ),
|
||||
# content_type='application/json'
|
||||
# )
|
||||
|
||||
# assert type(response.data['urls']['back']) is str
|
||||
|
||||
|
||||
|
||||
# def test_method_options_request_list_data_has_key_urls_return_url(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data returned has key `urls.return_url`
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# if self.url_kwargs:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
# else:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
# response = client.options( url, content_type='application/json' )
|
||||
|
||||
# assert 'return_url' in response.data['urls']
|
||||
|
||||
|
||||
# def test_method_options_request_list_data_key_urls_return_url_is_str(self):
|
||||
# """Test HTTP/Options Method
|
||||
|
||||
# Ensure the request data key `urls.return_url` is str
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# client.force_login(self.view_user)
|
||||
|
||||
# if self.url_kwargs:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
# else:
|
||||
|
||||
# url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
# response = client.options( url, content_type='application/json' )
|
||||
|
||||
# assert type(response.data['urls']['return_url']) is str
|
||||
|
||||
|
@ -1,81 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_notes import Entity, EntityNotes
|
||||
|
||||
from core.tests.abstract.model_notes_api_fields import ModelNotesNotesAPIFields
|
||||
|
||||
|
||||
|
||||
class NotesAPITestCases(
|
||||
ModelNotesNotesAPIFields,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
kwargs_model_create: dict = None
|
||||
|
||||
# url_view_kwargs: dict = None
|
||||
|
||||
view_name: str = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Call parent setup
|
||||
2. Create a model note
|
||||
3. add url kwargs
|
||||
4. make the API request
|
||||
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.entity_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_model_create
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.pk
|
||||
}
|
||||
|
||||
self.make_request()
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPIInheritedCases(
|
||||
NotesAPITestCases,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
kwargs_model_create = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPITest(
|
||||
NotesAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
entity_model = Entity
|
||||
|
||||
kwargs_model_create = {}
|
@ -1,162 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.viewsets.entity_notes import ViewSet
|
||||
|
||||
from core.tests.abstract.test_functional_notes_viewset import (
|
||||
ModelNotesViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
ModelNotesPermissionsAPI,
|
||||
ModelNotesSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase(
|
||||
ModelNotesViewSetBase
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
kwargs_create_model_item: dict = {}
|
||||
|
||||
kwargs_create_model_item_other_org: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.item = self.viewset.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.other_org_item = self.viewset.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item_other_org
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class NotesPermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesPermissionsAPI,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a global model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPIInheritedCases(
|
||||
NotesPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPITest(
|
||||
NotesPermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesSerializerTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesSerializer,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerInheritedCases(
|
||||
NotesSerializerTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerTest(
|
||||
NotesSerializerTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesMetadataTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataInheritedCases(
|
||||
NotesMetadataTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataTest(
|
||||
NotesMetadataTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
@ -1,32 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.organization_history import Organization, OrganizationHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class History(
|
||||
HistoryEntriesCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Organization
|
||||
|
||||
history_model = OrganizationHistory
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
name = self.field_value_original,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
name = self.field_value_delete,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
@ -1,92 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.serializers.organization import (
|
||||
Organization,
|
||||
OrganizationModelSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class OrganizationValidationAPI(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Organization
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an org
|
||||
2. Create an item
|
||||
"""
|
||||
|
||||
self.user = User.objects.create(username = 'org_user', password='random password')
|
||||
|
||||
self.valid_data = {
|
||||
'name': 'valid_org_data',
|
||||
'manager': self.user.id
|
||||
}
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
name = 'random title',
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no name is provided a validation error occurs
|
||||
"""
|
||||
|
||||
serializer = OrganizationModelSerializer(
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_name(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no name is provided a validation error occurs
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['name']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = OrganizationModelSerializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['name'][0] == 'required'
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_manager_optional(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no name is provided a validation error occurs
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['manager']
|
||||
|
||||
serializer = OrganizationModelSerializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
@ -1,318 +0,0 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, Permission, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
model = Organization
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
url_name = '_api_v2_organization'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
delete_data = {}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item = organization
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.different_organization = different_organization
|
||||
|
||||
self.other_org_item = organization
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team_b = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = different_organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
view_team_b.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team_b,
|
||||
user = self.view_user_b
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = { 'pk': self.item.id }
|
||||
|
||||
self.add_data = {
|
||||
'name': 'team_post',
|
||||
}
|
||||
|
||||
|
||||
self.super_add_user = User.objects.create_user(username="test_user_add_super", password="password", is_superuser = True)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
class OrganizationPermissionsAPI(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
TestCase
|
||||
):
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a tenancy model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def test_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
if self.url_kwargs:
|
||||
|
||||
url = reverse( self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs )
|
||||
|
||||
else:
|
||||
|
||||
url = reverse( self.app_namespace + ':' + self.url_name + '-list' )
|
||||
|
||||
|
||||
client.force_login( self.add_user )
|
||||
|
||||
response = client.post( url, data = self.add_data )
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self):
|
||||
"""Returned results check
|
||||
|
||||
This test case is an override of a test of the same name.
|
||||
organizations are not tenancy objects and therefor are supposed to
|
||||
return all items when a user queries them.
|
||||
|
||||
Ensure that a query to the viewset endpoint does not return
|
||||
items that are not part of the users organizations.
|
||||
"""
|
||||
|
||||
|
||||
# Ensure the other org item exists, without test not able to function
|
||||
print('Check that the different organization item has been defined')
|
||||
assert hasattr(self, 'other_org_item')
|
||||
|
||||
# ensure that the variables for the two orgs are different orgs
|
||||
print('checking that the different and user oganizations are different')
|
||||
assert self.different_organization.id != self.organization.id
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
if self.url_kwargs:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
contains_different_org: bool = False
|
||||
|
||||
# for item in response.data['results']:
|
||||
|
||||
# if int(item['id']) != self.organization.id:
|
||||
|
||||
# contains_different_org = True
|
||||
|
||||
assert len(response.data['results']) == 2
|
||||
|
||||
|
||||
def test_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
This test is a duplicate of a test case with the same name.
|
||||
Organizations are not tenancy models so this test does nothing of value
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationViewSet(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
TestCase
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class OrganizationMetadata(
|
||||
ViewSetBase,
|
||||
MetadataAttributesFunctional,
|
||||
MetaDataNavigationEntriesFunctional,
|
||||
TestCase
|
||||
):
|
||||
|
||||
menu_id = 'access'
|
||||
|
||||
menu_entry_id = 'organization'
|
@ -1,116 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.viewsets.organization_notes import ViewSet
|
||||
|
||||
from core.tests.abstract.test_functional_notes_viewset import (
|
||||
ModelNotesViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
ModelNotesPermissionsAPI,
|
||||
ModelNotesSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase(
|
||||
ModelNotesViewSetBase
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
url_name = '_api_v2_organization_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.item = self.viewset.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.other_org_item = self.viewset.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.different_organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.global_org_item = self.viewset.model.objects.create(
|
||||
organization = self.global_organization,
|
||||
content = 'a random comment global_organization',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.global_organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesPermissionsAPI(
|
||||
ViewSetBase,
|
||||
ModelNotesPermissionsAPI,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a global model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesSerializer(
|
||||
ViewSetBase,
|
||||
ModelNotesSerializer,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesMetadata(
|
||||
ViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
pass
|
@ -1,53 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from core.tests.abstract.model_notes_api_fields import ModelNotesNotesAPIFields
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.organization_notes import OrganizationNotes
|
||||
|
||||
|
||||
class OrganizationNotesAPI(
|
||||
ModelNotesNotesAPIFields,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = OrganizationNotes
|
||||
|
||||
view_name: str = '_api_v2_organization_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Call parent setup
|
||||
2. Create a model note
|
||||
3. add url kwargs
|
||||
4. make the API request
|
||||
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = Organization.objects.create(
|
||||
name = 'dev'
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.pk
|
||||
}
|
||||
|
||||
self.make_request()
|
34
app/access/tests/functional/person/conftest.py
Normal file
34
app/access/tests/functional/person/conftest.py
Normal file
@ -0,0 +1,34 @@
|
||||
import pytest
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Person
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity_person import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_person):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_person.copy()
|
||||
|
||||
yield kwargs_person.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user